Skip to content

Commit 933f2ba

Browse files
committed
Change Location coordinates to pointers
Change Location.Latitude and Location.Longitude from float64 to *float64 to distinguish missing coordinates from valid (0,0) location. Add HasCoordinates() method for safe coordinate access. Closes #5.
1 parent e3d74d9 commit 933f2ba

File tree

5 files changed

+43
-19
lines changed

5 files changed

+43
-19
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,13 @@
1212
Country) now use named types like `EnterpriseCityRecord`, `CityTraits`,
1313
`CountryRecord`, etc. This makes it much easier to initialize structs in user
1414
code while maintaining the same JSON serialization behavior.
15+
* **BREAKING CHANGE**: Changed `Location.Latitude` and `Location.Longitude` from
16+
`float64` to `*float64` to properly distinguish between missing coordinates and
17+
the valid location (0, 0). Missing coordinates are now represented as `nil`
18+
and are omitted from JSON output, while valid zero coordinates are preserved.
19+
This fixes the ambiguity where (0, 0) was incorrectly treated as "no data".
20+
Added `Location.HasCoordinates()` method for safe coordinate access. Reported
21+
by Nick Bruun. GitHub #5.
1522

1623
# 2.0.0-beta.1 - 2025-06-22
1724

README.md

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,9 @@ func main() {
8282
fmt.Printf("Russian country name: %v\n", record.Country.Names.Russian)
8383
fmt.Printf("ISO country code: %v\n", record.Country.ISOCode)
8484
fmt.Printf("Time zone: %v\n", record.Location.TimeZone)
85-
fmt.Printf("Coordinates: %v, %v\n", record.Location.Latitude, record.Location.Longitude)
85+
if record.Location.HasCoordinates() {
86+
fmt.Printf("Coordinates: %v, %v\n", *record.Location.Latitude, *record.Location.Longitude)
87+
}
8688
// Output:
8789
// Portuguese (BR) city name: Londres
8890
// English subdivision name: England
@@ -161,7 +163,9 @@ func main() {
161163
fmt.Printf("Country: %v (%v)\n", record.Country.Names.English, record.Country.ISOCode)
162164
fmt.Printf("Continent: %v (%v)\n", record.Continent.Names.English, record.Continent.Code)
163165
fmt.Printf("Postal Code: %v\n", record.Postal.Code)
164-
fmt.Printf("Location: %v, %v\n", record.Location.Latitude, record.Location.Longitude)
166+
if record.Location.HasCoordinates() {
167+
fmt.Printf("Location: %v, %v\n", *record.Location.Latitude, *record.Location.Longitude)
168+
}
165169
fmt.Printf("Time Zone: %v\n", record.Location.TimeZone)
166170
fmt.Printf("Network: %v\n", record.Traits.Network)
167171
fmt.Printf("IP Address: %v\n", record.Traits.IPAddress)
@@ -353,7 +357,9 @@ func main() {
353357
// Basic location information
354358
fmt.Printf("City: %v\n", record.City.Names.English)
355359
fmt.Printf("Country: %v (%v)\n", record.Country.Names.English, record.Country.ISOCode)
356-
fmt.Printf("Location: %v, %v\n", record.Location.Latitude, record.Location.Longitude)
360+
if record.Location.HasCoordinates() {
361+
fmt.Printf("Location: %v, %v\n", *record.Location.Latitude, *record.Location.Longitude)
362+
}
357363

358364
// Enterprise-specific fields
359365
fmt.Printf("ISP: %v\n", record.Traits.ISP)

example_test.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,11 @@ func Example() {
2828
fmt.Printf("Russian country name: %v\n", record.Country.Names.Russian)
2929
fmt.Printf("ISO country code: %v\n", record.Country.ISOCode)
3030
fmt.Printf("Time zone: %v\n", record.Location.TimeZone)
31-
fmt.Printf("Coordinates: %v, %v\n", record.Location.Latitude, record.Location.Longitude)
31+
if record.Location.HasCoordinates() {
32+
fmt.Printf("Coordinates: %v, %v\n", *record.Location.Latitude, *record.Location.Longitude)
33+
} else {
34+
fmt.Println("Coordinates: unavailable")
35+
}
3236
// Output:
3337
// Portuguese (BR) city name: Londres
3438
// English subdivision name: England

models.go

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -62,17 +62,19 @@ func (c Continent) HasData() bool {
6262

6363
// Location contains data for the location record associated with an IP address.
6464
type Location struct {
65-
// TimeZone is the time zone associated with location, as specified by
66-
// the IANA Time Zone Database (e.g., "America/New_York")
67-
TimeZone string `json:"time_zone,omitzero" maxminddb:"time_zone"`
6865
// Latitude is the approximate latitude of the location associated with
6966
// the IP address. This value is not precise and should not be used to
70-
// identify a particular address or household.
71-
Latitude float64 `json:"latitude" maxminddb:"latitude"`
67+
// identify a particular address or household. Will be nil if not present
68+
// in the database.
69+
Latitude *float64 `json:"latitude,omitzero" maxminddb:"latitude"`
7270
// Longitude is the approximate longitude of the location associated with
7371
// the IP address. This value is not precise and should not be used to
74-
// identify a particular address or household.
75-
Longitude float64 `json:"longitude" maxminddb:"longitude"`
72+
// identify a particular address or household. Will be nil if not present
73+
// in the database.
74+
Longitude *float64 `json:"longitude,omitzero" maxminddb:"longitude"`
75+
// TimeZone is the time zone associated with location, as specified by
76+
// the IANA Time Zone Database (e.g., "America/New_York")
77+
TimeZone string `json:"time_zone,omitzero" maxminddb:"time_zone"`
7678
// MetroCode is a metro code for targeting advertisements.
7779
//
7880
// Deprecated: Metro codes are no longer maintained and should not be used.
@@ -89,6 +91,11 @@ func (l Location) HasData() bool {
8991
return l != zeroLocation
9092
}
9193

94+
// HasCoordinates returns true if both latitude and longitude are present.
95+
func (l Location) HasCoordinates() bool {
96+
return l.Latitude != nil && l.Longitude != nil
97+
}
98+
9299
// RepresentedCountry contains data for the represented country associated
93100
// with an IP address. The represented country is the country represented
94101
// by something like a military base or embassy.
@@ -342,15 +349,13 @@ type Enterprise struct {
342349
// Continent contains data for the continent record associated with the IP
343350
// address.
344351
Continent Continent `json:"continent,omitzero" maxminddb:"continent"`
345-
// City contains data for the city record associated with the IP address.
346-
City EnterpriseCityRecord `json:"city,omitzero" maxminddb:"city"`
347-
// Postal contains data for the postal record associated with the IP address.
348-
Postal EnterprisePostal `json:"postal,omitzero" maxminddb:"postal"`
349352
// Subdivisions contains data for the subdivisions associated with the IP
350353
// address. The subdivisions array is ordered from largest to smallest. For
351354
// instance, the response for Oxford in the United Kingdom would have England
352355
// as the first element and Oxfordshire as the second element.
353356
Subdivisions []EnterpriseSubdivision `json:"subdivisions,omitzero" maxminddb:"subdivisions"`
357+
// Postal contains data for the postal record associated with the IP address.
358+
Postal EnterprisePostal `json:"postal,omitzero" maxminddb:"postal"`
354359
// RepresentedCountry contains data for the represented country associated
355360
// with the IP address. The represented country is the country represented
356361
// by something like a military base or embassy.
@@ -363,11 +368,13 @@ type Enterprise struct {
363368
// with the IP address. This record represents the country where the ISP has
364369
// registered the IP block and may differ from the user's country.
365370
RegisteredCountry CountryRecord `json:"registered_country,omitzero" maxminddb:"registered_country"`
366-
// Traits contains various traits associated with the IP address
367-
Traits EnterpriseTraits `json:"traits,omitzero" maxminddb:"traits"`
371+
// City contains data for the city record associated with the IP address.
372+
City EnterpriseCityRecord `json:"city,omitzero" maxminddb:"city"`
368373
// Location contains data for the location record associated with the IP
369374
// address
370375
Location Location `json:"location,omitzero" maxminddb:"location"`
376+
// Traits contains various traits associated with the IP address
377+
Traits EnterpriseTraits `json:"traits,omitzero" maxminddb:"traits"`
371378
}
372379

373380
// HasData returns true if any GeoIP data was found for the IP in the Enterprise database.

reader_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,8 @@ func TestReader(t *testing.T) {
7979
assert.Equal(t, expectedCountryNames, record.Country.Names)
8080

8181
assert.Equal(t, uint16(100), record.Location.AccuracyRadius)
82-
assert.InEpsilon(t, 51.5142, record.Location.Latitude, 1e-10)
83-
assert.InEpsilon(t, -0.0931, record.Location.Longitude, 1e-10)
82+
assert.InEpsilon(t, 51.5142, *record.Location.Latitude, 1e-10)
83+
assert.InEpsilon(t, -0.0931, *record.Location.Longitude, 1e-10)
8484
assert.Equal(t, "Europe/London", record.Location.TimeZone)
8585

8686
assert.Equal(t, uint(6269131), record.Subdivisions[0].GeoNameID)

0 commit comments

Comments
 (0)