diff --git a/docs/configuration.md b/docs/configuration.md
index b8db5ba..b00933e 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -579,6 +579,15 @@ Example:
location: London, United Kingdom
```
+> [!NOTE]
+>
+> US cities which have common names can have their state specified as the second parameter like such:
+>
+> * Greenville, North Carolina, United States
+> * Greenville, South Carolina, United States
+> * Greenville, Mississippi, United States
+
+
Preview:
![](images/weather-widget-preview.png)
@@ -592,6 +601,7 @@ Each bar represents a 2 hour interval. The yellow background represents sunrise
| location | string | yes | |
| units | string | no | metric |
| hide-location | boolean | no | false |
+| show-area-name | boolean | no | false |
##### `location`
The name of the city and country to fetch weather information for. Attempting to launch the applcation with an invalid location will result in an error. You can use the [gecoding API page](https://open-meteo.com/en/docs/geocoding-api) to search for your specific location. Glance will use the first result from the list if there are multiple.
@@ -602,6 +612,19 @@ Whether to show the temperature in celsius or fahrenheit, possible values are `m
##### `hide-location`
Optionally don't display the location name on the widget.
+##### `show-area-name`
+Whether to display the state/administrative area in the location name. If set to `true` the location will be displayed as:
+
+```
+Greenville, North Carolina, United States
+```
+
+Otherwise, if set to `false` (which is the default) it'll be displayed as:
+
+```
+Greenville, United States
+```
+
### Monitor
Display a list of sites and whether they are reachable (online) or not. This is determined by sending a HEAD request to the specified URL, if the response is 200 then the site is OK. The time it took to receive a response is also shown in milliseconds.
diff --git a/internal/assets/templates/weather.html b/internal/assets/templates/weather.html
index b251186..40fdcb6 100644
--- a/internal/assets/templates/weather.html
+++ b/internal/assets/templates/weather.html
@@ -23,7 +23,7 @@
{{ if not .HideLocation }}
-
{{ .Place.Name }}, {{ .Place.Country }}
+
{{ .Place.Name }},{{ if .ShowAreaName }} {{ .Place.Area }},{{ end }} {{ .Place.Country }}
{{ end }}
{{ end }}
diff --git a/internal/feed/openmeteo.go b/internal/feed/openmeteo.go
index b3bbe49..2a8dfa6 100644
--- a/internal/feed/openmeteo.go
+++ b/internal/feed/openmeteo.go
@@ -6,6 +6,7 @@ import (
"net/http"
"net/url"
"slices"
+ "strings"
"time"
_ "time/tzdata"
@@ -17,6 +18,7 @@ type PlacesResponseJson struct {
type PlaceJson struct {
Name string
+ Area string `json:"admin1"`
Latitude float64
Longitude float64
Timezone string
@@ -48,8 +50,41 @@ type weatherColumn struct {
HasPrecipitation bool
}
+var commonCountryAbbreviations = map[string]string{
+ "US": "United States",
+ "USA": "United States",
+ "UK": "United Kingdom",
+}
+
+func expandCountryAbbreviations(name string) string {
+ if expanded, ok := commonCountryAbbreviations[strings.TrimSpace(name)]; ok {
+ return expanded
+ }
+
+ return name
+}
+
+// Separates the location that Open Meteo accepts from the administrative area
+// which can then be used to filter to the correct place after the list of places
+// has been retrieved. Also expands abbreviations since Open Meteo does not accept
+// country names like "US", "USA" and "UK"
+func parsePlaceName(name string) (string, string) {
+ parts := strings.Split(name, ",")
+
+ if len(parts) == 1 {
+ return name, ""
+ }
+
+ if len(parts) == 2 {
+ return parts[0] + ", " + expandCountryAbbreviations(parts[1]), ""
+ }
+
+ return parts[0] + ", " + expandCountryAbbreviations(parts[2]), strings.TrimSpace(parts[1])
+}
+
func FetchPlaceFromName(location string) (*PlaceJson, error) {
- requestUrl := fmt.Sprintf("https://geocoding-api.open-meteo.com/v1/search?name=%s&count=1&language=en&format=json", url.QueryEscape(location))
+ location, area := parsePlaceName(location)
+ requestUrl := fmt.Sprintf("https://geocoding-api.open-meteo.com/v1/search?name=%s&count=10&language=en&format=json", url.QueryEscape(location))
request, _ := http.NewRequest("GET", requestUrl, nil)
responseJson, err := decodeJsonFromRequest[PlacesResponseJson](defaultClient, request)
@@ -61,7 +96,24 @@ func FetchPlaceFromName(location string) (*PlaceJson, error) {
return nil, fmt.Errorf("no places found for %s", location)
}
- place := &responseJson.Results[0]
+ var place *PlaceJson
+
+ if area != "" {
+ area = strings.ToLower(area)
+
+ for i := range responseJson.Results {
+ if strings.ToLower(responseJson.Results[i].Area) == area {
+ place = &responseJson.Results[i]
+ break
+ }
+ }
+
+ if place == nil {
+ return nil, fmt.Errorf("no place found for %s in %s", location, area)
+ }
+ } else {
+ place = &responseJson.Results[0]
+ }
loc, err := time.LoadLocation(place.Timezone)
diff --git a/internal/widget/weather.go b/internal/widget/weather.go
index a59b9b9..9d90e03 100644
--- a/internal/widget/weather.go
+++ b/internal/widget/weather.go
@@ -12,6 +12,7 @@ import (
type Weather struct {
widgetBase `yaml:",inline"`
Location string `yaml:"location"`
+ ShowAreaName bool `yaml:"show-area-name"`
HideLocation bool `yaml:"hide-location"`
Units string `yaml:"units"`
Place *feed.PlaceJson `yaml:"-"`