From d8d66254788bf480d1356c3c502287fe90b92e96 Mon Sep 17 00:00:00 2001 From: Svilen Markov <7613769+svilenmarkov@users.noreply.github.com> Date: Thu, 2 May 2024 19:54:20 +0100 Subject: [PATCH] Allow specifying state in weather location --- docs/configuration.md | 23 +++++++++++ internal/assets/templates/weather.html | 2 +- internal/feed/openmeteo.go | 56 +++++++++++++++++++++++++- internal/widget/weather.go | 1 + 4 files changed, 79 insertions(+), 3 deletions(-) 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:"-"`