Places: Use float64 for all coordinates to avoid rounding errors #3953

Signed-off-by: Michael Mayer <michael@photoprism.app>
This commit is contained in:
Michael Mayer
2024-09-15 13:52:31 +02:00
parent e808de45e3
commit 735a3a2d13
32 changed files with 510 additions and 377 deletions

View File

@@ -2886,9 +2886,9 @@
}
},
"node_modules/@eslint-community/regexpp": {
"version": "4.11.0",
"resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.0.tgz",
"integrity": "sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==",
"version": "4.11.1",
"resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.1.tgz",
"integrity": "sha512-m4DVN9ZqskZoLU5GlWZadwDnYo3vAEydiUayB9widCl9ffWx2IvPnp6n3on5rJmziJSw9Bv+Z3ChDVdMwXCY8Q==",
"license": "MIT",
"engines": {
"node": "^12.0.0 || ^14.0.0 || >=16.0.0"
@@ -3576,9 +3576,9 @@
}
},
"node_modules/@types/node": {
"version": "22.5.4",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.4.tgz",
"integrity": "sha512-FDuKUJQm/ju9fT/SeX/6+gBzoPzlVCzfzmGkwKvRHQVxi4BntVbyIwf6a4Xn62mrvndLiml6z/UBXIdEVjQLXg==",
"version": "22.5.5",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.5.tgz",
"integrity": "sha512-Xjs4y5UPO/CLdzpgR6GirZJx36yScjh73+2NlLlkFRSoQN8B0DpfXPdZGnvVmLRLOsqDpOfTNv7D9trgGhmOIA==",
"license": "MIT",
"dependencies": {
"undici-types": "~6.19.2"
@@ -3621,42 +3621,42 @@
"license": "ISC"
},
"node_modules/@vue/compiler-core": {
"version": "3.5.4",
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.4.tgz",
"integrity": "sha512-oNwn+BAt3n9dK9uAYvI+XGlutwuTq/wfj4xCBaZCqwwVIGtD7D6ViihEbyYZrDHIHTDE3Q6oL3/hqmAyFEy9DQ==",
"version": "3.5.5",
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.5.tgz",
"integrity": "sha512-ZrxcY8JMoV+kgDrmRwlDufz0SjDZ7jfoNZiIBluAACMBmgr55o/jTbxnyrccH6VSJXnFaDI4Ik1UFCiq9r8i7w==",
"license": "MIT",
"optional": true,
"dependencies": {
"@babel/parser": "^7.25.3",
"@vue/shared": "3.5.4",
"@vue/shared": "3.5.5",
"entities": "^4.5.0",
"estree-walker": "^2.0.2",
"source-map-js": "^1.2.0"
}
},
"node_modules/@vue/compiler-dom": {
"version": "3.5.4",
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.4.tgz",
"integrity": "sha512-yP9RRs4BDLOLfldn6ah+AGCNovGjMbL9uHvhDHf5wan4dAHLnFGOkqtfE7PPe4HTXIqE7l/NILdYw53bo1C8jw==",
"version": "3.5.5",
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.5.tgz",
"integrity": "sha512-HSvK5q1gmBbxRse3S0Wt34RcKuOyjDJKDDMuF3i7NC+QkDFrbAqw8NnrEm/z7zFDxWZa4/5eUwsBOMQzm1RHBA==",
"license": "MIT",
"optional": true,
"dependencies": {
"@vue/compiler-core": "3.5.4",
"@vue/shared": "3.5.4"
"@vue/compiler-core": "3.5.5",
"@vue/shared": "3.5.5"
}
},
"node_modules/@vue/compiler-sfc": {
"version": "3.5.4",
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.4.tgz",
"integrity": "sha512-P+yiPhL+NYH7m0ZgCq7AQR2q7OIE+mpAEgtkqEeH9oHSdIRvUO+4X6MPvblJIWcoe4YC5a2Gdf/RsoyP8FFiPQ==",
"version": "3.5.5",
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.5.tgz",
"integrity": "sha512-MzBHDxwZhgQPHrwJ5tj92gdTYRCuPDSZr8PY3+JFv8cv2UD5/WayH5yo0kKCkKfrtJhc39jNSMityHrkMSbfnA==",
"license": "MIT",
"optional": true,
"dependencies": {
"@babel/parser": "^7.25.3",
"@vue/compiler-core": "3.5.4",
"@vue/compiler-dom": "3.5.4",
"@vue/compiler-ssr": "3.5.4",
"@vue/shared": "3.5.4",
"@vue/compiler-core": "3.5.5",
"@vue/compiler-dom": "3.5.5",
"@vue/compiler-ssr": "3.5.5",
"@vue/shared": "3.5.5",
"estree-walker": "^2.0.2",
"magic-string": "^0.30.11",
"postcss": "^8.4.44",
@@ -3664,14 +3664,14 @@
}
},
"node_modules/@vue/compiler-ssr": {
"version": "3.5.4",
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.4.tgz",
"integrity": "sha512-acESdTXsxPnYr2C4Blv0ggx5zIFMgOzZmYU2UgvIff9POdRGbRNBHRyzHAnizcItvpgerSKQbllUc9USp3V7eg==",
"version": "3.5.5",
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.5.tgz",
"integrity": "sha512-oFasHnpv/upubjJEmqiTKQYb4qS3ziJddf4UVWuFw6ebk/QTrTUc+AUoTJdo39x9g+AOQBzhOU0ICCRuUjvkmw==",
"license": "MIT",
"optional": true,
"dependencies": {
"@vue/compiler-dom": "3.5.4",
"@vue/shared": "3.5.4"
"@vue/compiler-dom": "3.5.5",
"@vue/shared": "3.5.5"
}
},
"node_modules/@vue/component-compiler-utils": {
@@ -3749,9 +3749,9 @@
"license": "ISC"
},
"node_modules/@vue/shared": {
"version": "3.5.4",
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.4.tgz",
"integrity": "sha512-L2MCDD8l7yC62Te5UUyPVpmexhL9ipVnYRw9CsWfm/BGRL5FwDX4a25bcJ/OJSD3+Hx+k/a8LDKcG2AFdJV3BA==",
"version": "3.5.5",
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.5.tgz",
"integrity": "sha512-0KyMXyEgnmFAs6rNUL+6eUHtUCqCaNrVd+AW3MX3LyA0Yry5SA0Km03CDKiOua1x1WWnIr+W9+S0GMFoSDWERQ==",
"license": "MIT",
"optional": true
},
@@ -6441,9 +6441,9 @@
}
},
"node_modules/electron-to-chromium": {
"version": "1.5.22",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.22.tgz",
"integrity": "sha512-tKYm5YHPU1djz0O+CGJ+oJIvimtsCcwR2Z9w7Skh08lUdyzXY5djods3q+z2JkWdb7tCcmM//eVavSRAiaPRNg==",
"version": "1.5.23",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.23.tgz",
"integrity": "sha512-mBhODedOXg4v5QWwl21DjM5amzjmI1zw9EPrPK/5Wx7C8jt33bpZNrC7OhHUG3pxRtbLpr3W2dXT+Ph1SsfRZA==",
"license": "ISC"
},
"node_modules/emoji-regex": {
@@ -8089,9 +8089,9 @@
"license": "ISC"
},
"node_modules/flow-remove-types": {
"version": "2.245.2",
"resolved": "https://registry.npmjs.org/flow-remove-types/-/flow-remove-types-2.245.2.tgz",
"integrity": "sha512-p4rWHk20Vp8/pu9MOVi26r+r8hYpZ1vk+9OsFcJnKc/oUYJBxmTL8/fBlIo4/LJ9aLOrAeHwO1PnSsU6Z7gVsw==",
"version": "2.246.0",
"resolved": "https://registry.npmjs.org/flow-remove-types/-/flow-remove-types-2.246.0.tgz",
"integrity": "sha512-x3nZU+07+jRha9IK0Ooym60zpo5DIO4hTc09MTLd5ZPHs/hQ4cKUHNArSrNJSI2agNTY0lMRsV0t7VrGNmAqww==",
"license": "MIT",
"dependencies": {
"hermes-parser": "0.23.1",
@@ -11648,9 +11648,9 @@
}
},
"node_modules/postcss": {
"version": "8.4.45",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.45.tgz",
"integrity": "sha512-7KTLTdzdZZYscUc65XmjFiB73vBhBfbPztCYdUNvlaso9PrzjzcmjqBPR0lNGkcVlcO4BjiO5rK/qNz+XAen1Q==",
"version": "8.4.47",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz",
"integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==",
"funding": [
{
"type": "opencollective",
@@ -11668,8 +11668,8 @@
"license": "MIT",
"dependencies": {
"nanoid": "^3.3.7",
"picocolors": "^1.0.1",
"source-map-js": "^1.2.0"
"picocolors": "^1.1.0",
"source-map-js": "^1.2.1"
},
"engines": {
"node": "^10 || ^12 || >=14"

View File

@@ -407,18 +407,6 @@ export default {
}
}
// Expand the GPS coordinates if they represent a point or line:
// https://github.com/photoprism/photoprism/issues/3953
if (latNorth === latSouth) {
latNorth = latNorth + 0.0003;
latSouth = latSouth - 0.0003;
}
if (lngEast === lngWest) {
lngEast = lngEast + 0.0003;
lngWest = lngWest - 0.0003;
}
this.selectClusterByCoords(latNorth, lngEast, latSouth, lngWest);
});
},

View File

@@ -48,10 +48,10 @@ func CreateUnknownLocation() {
}
// NewCell creates a location using a token extracted from coordinate
func NewCell(lat, lng float32) *Cell {
func NewCell(lat, lng float64) *Cell {
result := &Cell{}
result.ID = s2.PrefixedToken(float64(lat), float64(lng))
result.ID = s2.PrefixedToken(lat, lng)
return result
}

View File

@@ -183,4 +183,10 @@ var DialectMySQL = Migrations{
Stage: "pre",
Statements: []string{"ALTER IGNORE TABLE auth_sessions RENAME COLUMN auth_domain TO auth_issuer;"},
},
{
ID: "20240915-000001",
Dialect: "mysql",
Stage: "main",
Statements: []string{"ALTER TABLE photos MODIFY photo_lat DOUBLE;", "ALTER TABLE photos MODIFY photo_lng DOUBLE;"},
},
}

View File

@@ -0,0 +1,2 @@
ALTER TABLE photos MODIFY photo_lat DOUBLE;
ALTER TABLE photos MODIFY photo_lng DOUBLE;

View File

@@ -65,8 +65,8 @@ type Photo struct {
CellID string `gorm:"type:VARBINARY(42);index;default:'zz'" json:"CellID" yaml:"-"`
CellAccuracy int `json:"CellAccuracy" yaml:"CellAccuracy,omitempty"`
PhotoAltitude int `json:"Altitude" yaml:"Altitude,omitempty"`
PhotoLat float32 `gorm:"type:FLOAT;index;" json:"Lat" yaml:"Lat,omitempty"`
PhotoLng float32 `gorm:"type:FLOAT;index;" json:"Lng" yaml:"Lng,omitempty"`
PhotoLat float64 `gorm:"type:DOUBLE;index;" json:"Lat" yaml:"Lat,omitempty"`
PhotoLng float64 `gorm:"type:DOUBLE;index;" json:"Lng" yaml:"Lng,omitempty"`
PhotoCountry string `gorm:"type:VARBINARY(2);index:idx_photos_country_year_month;default:'zz'" json:"Country" yaml:"-"`
PhotoYear int `gorm:"index:idx_photos_ymd;index:idx_photos_country_year_month;" json:"Year" yaml:"Year"`
PhotoMonth int `gorm:"index:idx_photos_ymd;index:idx_photos_country_year_month;" json:"Month" yaml:"Month"`

View File

@@ -16,7 +16,7 @@ import (
)
// SetCoordinates changes the photo lat, lng and altitude if not empty and from an acceptable source.
func (m *Photo) SetCoordinates(lat, lng float32, altitude float64, source string) {
func (m *Photo) SetCoordinates(lat, lng, altitude float64, source string) {
m.SetAltitude(altitude, source)
if lat == 0.0 && lng == 0.0 {
@@ -60,15 +60,15 @@ func (m *Photo) SetPosition(pos geo.Position, source string, force bool) {
return
}
if m.CellID != UnknownID && pos.InRange(float64(m.PhotoLat), float64(m.PhotoLng), geo.Meter*50) {
if m.CellID != UnknownID && pos.InRange(m.PhotoLat, m.PhotoLng, geo.Meter*50) {
log.Debugf("photo: %s keeps position %f, %f", m.String(), m.PhotoLat, m.PhotoLng)
} else {
if pos.Estimate {
pos.Randomize(geo.Meter * 5)
}
m.PhotoLat = float32(pos.Lat)
m.PhotoLng = float32(pos.Lng)
m.PhotoLat = pos.Lat
m.PhotoLng = pos.Lng
m.PlaceSrc = source
m.CellAccuracy = pos.Accuracy
m.SetAltitude(pos.Altitude, source)
@@ -261,7 +261,7 @@ func (m *Photo) LoadPlace() error {
// Position returns the coordinates as geo.Position.
func (m *Photo) Position() geo.Position {
return geo.Position{Name: m.String(), Time: m.TakenAt.UTC(),
Lat: float64(m.PhotoLat), Lng: float64(m.PhotoLng), Altitude: float64(m.PhotoAltitude)}
Lat: m.PhotoLat, Lng: m.PhotoLng, Altitude: float64(m.PhotoAltitude)}
}
// HasLatLng checks if the photo has a latitude and longitude.

View File

@@ -104,44 +104,44 @@ func TestPhoto_SetAltitude(t *testing.T) {
t.Run("ViaSetCoordinates", func(t *testing.T) {
m := PhotoFixtures.Get("Photo15")
assert.Equal(t, SrcMeta, m.PlaceSrc)
assert.Equal(t, float32(1.234), m.PhotoLat)
assert.Equal(t, float32(4.321), m.PhotoLng)
assert.Equal(t, float32(1.234), float32(m.PhotoLat))
assert.Equal(t, float32(4.321), float32(m.PhotoLng))
assert.Equal(t, 3, m.PhotoAltitude)
m.SetCoordinates(0, 0, 5, SrcManual)
assert.Equal(t, SrcMeta, m.PlaceSrc)
assert.Equal(t, float32(1.234), m.PhotoLat)
assert.Equal(t, float32(4.321), m.PhotoLng)
assert.Equal(t, float32(1.234), float32(m.PhotoLat))
assert.Equal(t, float32(4.321), float32(m.PhotoLng))
assert.Equal(t, 5, m.PhotoAltitude)
})
t.Run("Update", func(t *testing.T) {
m := PhotoFixtures.Get("Photo15")
assert.Equal(t, SrcMeta, m.PlaceSrc)
assert.Equal(t, float32(1.234), m.PhotoLat)
assert.Equal(t, float32(4.321), m.PhotoLng)
assert.Equal(t, float32(1.234), float32(m.PhotoLat))
assert.Equal(t, float32(4.321), float32(m.PhotoLng))
assert.Equal(t, 3, m.PhotoAltitude)
m.SetAltitude(5, SrcManual)
assert.Equal(t, SrcMeta, m.PlaceSrc)
assert.Equal(t, float32(1.234), m.PhotoLat)
assert.Equal(t, float32(4.321), m.PhotoLng)
assert.Equal(t, float32(1.234), float32(m.PhotoLat))
assert.Equal(t, float32(4.321), float32(m.PhotoLng))
assert.Equal(t, 5, m.PhotoAltitude)
})
t.Run("SkipUpdate", func(t *testing.T) {
m := PhotoFixtures.Get("Photo15")
assert.Equal(t, SrcMeta, m.PlaceSrc)
assert.Equal(t, float32(1.234), m.PhotoLat)
assert.Equal(t, float32(4.321), m.PhotoLng)
assert.Equal(t, float32(1.234), float32(m.PhotoLat))
assert.Equal(t, float32(4.321), float32(m.PhotoLng))
assert.Equal(t, 3, m.PhotoAltitude)
m.SetAltitude(5, SrcEstimate)
assert.Equal(t, SrcMeta, m.PlaceSrc)
assert.Equal(t, float32(1.234), m.PhotoLat)
assert.Equal(t, float32(4.321), m.PhotoLng)
assert.Equal(t, float32(1.234), float32(m.PhotoLat))
assert.Equal(t, float32(4.321), float32(m.PhotoLng))
assert.Equal(t, 3, m.PhotoAltitude)
})
t.Run("UpdateEmptyAltitude", func(t *testing.T) {
m := Photo{ID: 1, PlaceSrc: SrcMeta, PhotoLat: float32(1.234), PhotoLng: float32(4.321), PhotoAltitude: 0}
m := Photo{ID: 1, PlaceSrc: SrcMeta, PhotoLat: float64(1.234), PhotoLng: float64(4.321), PhotoAltitude: 0}
m.SetAltitude(-5, SrcAuto)
assert.Equal(t, 0, m.PhotoAltitude)
@@ -153,7 +153,7 @@ func TestPhoto_SetAltitude(t *testing.T) {
assert.Equal(t, -5, m.PhotoAltitude)
})
t.Run("ZeroAltitudeManual", func(t *testing.T) {
m := Photo{ID: 1, PlaceSrc: SrcManual, PhotoLat: float32(1.234), PhotoLng: float32(4.321), PhotoAltitude: 5}
m := Photo{ID: 1, PlaceSrc: SrcManual, PhotoLat: 1.234, PhotoLng: 4.321, PhotoAltitude: 5}
m.SetAltitude(0, SrcManual)
assert.Equal(t, 0, m.PhotoAltitude)
@@ -164,78 +164,78 @@ func TestPhoto_SetCoordinates(t *testing.T) {
t.Run("empty coordinates", func(t *testing.T) {
m := PhotoFixtures.Get("Photo15")
assert.Equal(t, SrcMeta, m.PlaceSrc)
assert.Equal(t, float32(1.234), m.PhotoLat)
assert.Equal(t, float32(4.321), m.PhotoLng)
assert.Equal(t, float32(1.234), float32(m.PhotoLat))
assert.Equal(t, float32(4.321), float32(m.PhotoLng))
assert.Equal(t, 3, m.PhotoAltitude)
m.SetCoordinates(0, 0, 5, SrcManual)
assert.Equal(t, SrcMeta, m.PlaceSrc)
assert.Equal(t, float32(1.234), m.PhotoLat)
assert.Equal(t, float32(4.321), m.PhotoLng)
assert.Equal(t, float32(1.234), float32(m.PhotoLat))
assert.Equal(t, float32(4.321), float32(m.PhotoLng))
assert.Equal(t, 5, m.PhotoAltitude)
})
t.Run("same source new values", func(t *testing.T) {
m := PhotoFixtures.Get("Photo15")
assert.Equal(t, SrcMeta, m.PlaceSrc)
assert.Equal(t, float32(1.234), m.PhotoLat)
assert.Equal(t, float32(4.321), m.PhotoLng)
assert.Equal(t, float32(1.234), float32(m.PhotoLat))
assert.Equal(t, float32(4.321), float32(m.PhotoLng))
assert.Equal(t, 3, m.PhotoAltitude)
m.SetCoordinates(5.555, 5.555, 5, SrcMeta)
assert.Equal(t, SrcMeta, m.PlaceSrc)
assert.Equal(t, float32(5.555), m.PhotoLat)
assert.Equal(t, float32(5.555), m.PhotoLng)
assert.Equal(t, float32(5.555), float32(m.PhotoLat))
assert.Equal(t, float32(5.555), float32(m.PhotoLng))
assert.Equal(t, 5, m.PhotoAltitude)
})
t.Run("different source lower priority", func(t *testing.T) {
m := PhotoFixtures.Get("Photo15")
assert.Equal(t, SrcMeta, m.PlaceSrc)
assert.Equal(t, float32(1.234), m.PhotoLat)
assert.Equal(t, float32(4.321), m.PhotoLng)
assert.Equal(t, float32(1.234), float32(m.PhotoLat))
assert.Equal(t, float32(4.321), float32(m.PhotoLng))
assert.Equal(t, 3, m.PhotoAltitude)
m.SetCoordinates(5.555, 5.555, 5, SrcName)
assert.Equal(t, SrcMeta, m.PlaceSrc)
assert.Equal(t, float32(1.234), m.PhotoLat)
assert.Equal(t, float32(4.321), m.PhotoLng)
assert.Equal(t, float32(1.234), float32(m.PhotoLat))
assert.Equal(t, float32(4.321), float32(m.PhotoLng))
assert.Equal(t, 3, m.PhotoAltitude)
})
t.Run("different source equal priority", func(t *testing.T) {
m := PhotoFixtures.Get("Photo15")
assert.Equal(t, SrcMeta, m.PlaceSrc)
assert.Equal(t, float32(1.234), m.PhotoLat)
assert.Equal(t, float32(4.321), m.PhotoLng)
assert.Equal(t, float32(1.234), float32(m.PhotoLat))
assert.Equal(t, float32(4.321), float32(m.PhotoLng))
assert.Equal(t, 3, m.PhotoAltitude)
m.SetCoordinates(5.555, 5.555, 5, SrcKeyword)
assert.Equal(t, float32(5.555), m.PhotoLat)
assert.Equal(t, float32(5.555), m.PhotoLng)
assert.Equal(t, float32(5.555), float32(m.PhotoLat))
assert.Equal(t, float32(5.555), float32(m.PhotoLng))
assert.Equal(t, 5, m.PhotoAltitude)
})
t.Run("different source higher priority", func(t *testing.T) {
m := PhotoFixtures.Get("Photo21")
assert.Equal(t, SrcEstimate, m.PlaceSrc)
assert.Equal(t, float32(0), m.PhotoLat)
assert.Equal(t, float32(0), m.PhotoLng)
assert.Equal(t, 0.0, m.PhotoLat)
assert.Equal(t, 0.0, m.PhotoLng)
assert.Equal(t, 0, m.PhotoAltitude)
m.SetCoordinates(5.555, 5.555, 5, SrcMeta)
assert.Equal(t, SrcMeta, m.PlaceSrc)
assert.Equal(t, float32(5.555), m.PhotoLat)
assert.Equal(t, float32(5.555), m.PhotoLng)
assert.Equal(t, float32(5.555), float32(m.PhotoLat))
assert.Equal(t, float32(5.555), float32(m.PhotoLng))
assert.Equal(t, 5, m.PhotoAltitude)
})
t.Run("different source highest priority (manual)", func(t *testing.T) {
m := PhotoFixtures.Get("Photo15")
assert.Equal(t, SrcMeta, m.PlaceSrc)
assert.Equal(t, float32(1.234), m.PhotoLat)
assert.Equal(t, float32(4.321), m.PhotoLng)
assert.Equal(t, float32(1.234), float32(m.PhotoLat))
assert.Equal(t, float32(4.321), float32(m.PhotoLng))
assert.Equal(t, 3, m.PhotoAltitude)
m.SetCoordinates(5.555, 5.555, 5, SrcManual)
assert.Equal(t, SrcManual, m.PlaceSrc)
assert.Equal(t, float32(5.555), m.PhotoLat)
assert.Equal(t, float32(5.555), m.PhotoLng)
assert.Equal(t, float32(5.555), float32(m.PhotoLat))
assert.Equal(t, float32(5.555), float32(m.PhotoLng))
assert.Equal(t, 5, m.PhotoAltitude)
})
}
@@ -408,8 +408,8 @@ func TestUpdateLocation(t *testing.T) {
assert.Equal(t, "Mexico", m.CountryName())
assert.Equal(t, "mx", m.PhotoCountry)
assert.Equal(t, float32(0.0), m.PhotoLat)
assert.Equal(t, float32(0.0), m.PhotoLng)
assert.Equal(t, 0.0, m.PhotoLat)
assert.Equal(t, 0.0, m.PhotoLng)
assert.Equal(t, "mx:VvfNBpFegSCr", m.PlaceID)
assert.Equal(t, SrcEstimate, m.PlaceSrc)
})
@@ -430,8 +430,8 @@ func TestUpdateLocation(t *testing.T) {
assert.Equal(t, "Germany", m.CountryName())
assert.Equal(t, "de", m.PhotoCountry)
assert.Equal(t, float32(0.0), m.PhotoLat)
assert.Equal(t, float32(0.0), m.PhotoLng)
assert.Equal(t, 0.0, m.PhotoLat)
assert.Equal(t, 0.0, m.PhotoLng)
assert.Equal(t, UnknownID, m.PlaceID)
assert.Equal(t, SrcManual, m.PlaceSrc)
})

View File

@@ -63,7 +63,8 @@ func TestSavePhotoForm(t *testing.T) {
assert.Equal(t, true, m.PhotoFavorite)
assert.Equal(t, true, m.PhotoPrivate)
assert.Equal(t, "image", m.PhotoType)
assert.Equal(t, float32(7.9999), m.PhotoLat)
assert.InEpsilon(t, 7.9999, m.PhotoLat, 0.0001)
assert.InEpsilon(t, 8.8888, m.PhotoLng, 0.0001)
assert.NotNil(t, m.EditedAt)
t.Log(m.GetDetails().Keywords)

View File

@@ -703,7 +703,7 @@ func searchPhotos(f form.SearchPhotos, sess *entity.Session, resultCols string)
}
// Filter by GPS Longitude range (from -180 to +180 degrees)
if lngE, lngW, lngErr := clean.GPSLngRange(f.Lng, f.Dist); lngErr == nil {
if lngE, lngW, lngErr := clean.GPSLngRange(f.Lat, f.Lng, f.Dist); lngErr == nil {
s = s.Where("photos.photo_lng BETWEEN ? AND ?", lngW, lngE)
}

View File

@@ -600,7 +600,7 @@ func UserPhotosGeo(f form.SearchPhotosGeo, sess *entity.Session) (results GeoRes
}
// Filter by GPS Longitude range (from -180 to +180 degrees).
if lngE, lngW, lngErr := clean.GPSLngRange(f.Lng, f.Dist); lngErr == nil {
if lngE, lngW, lngErr := clean.GPSLngRange(f.Lat, f.Lng, f.Dist); lngErr == nil {
s = s.Where("photos.photo_lng BETWEEN ? AND ?", lngW, lngE)
}

View File

@@ -14,8 +14,8 @@ type GeoResult struct {
ID string `json:"-" select:"photos.id"`
PhotoUID string `json:"UID" select:"photos.photo_uid"`
PhotoType string `json:"Type,omitempty" select:"photos.photo_type"`
PhotoLat float32 `json:"Lat" select:"photos.photo_lat"`
PhotoLng float32 `json:"Lng" select:"photos.photo_lng"`
PhotoLat float64 `json:"Lat" select:"photos.photo_lat"`
PhotoLng float64 `json:"Lng" select:"photos.photo_lng"`
PhotoTitle string `json:"Title" select:"photos.photo_title"`
PhotoDescription string `json:"Description,omitempty" select:"photos.photo_description"`
PhotoFavorite bool `json:"Favorite,omitempty" select:"photos.photo_favorite"`
@@ -28,12 +28,12 @@ type GeoResult struct {
// Lat returns the position latitude.
func (photo GeoResult) Lat() float64 {
return float64(photo.PhotoLat)
return photo.PhotoLat
}
// Lng returns the position longitude.
func (photo GeoResult) Lng() float64 {
return float64(photo.PhotoLng)
return photo.PhotoLng
}
// IsPlayable returns true if the photo has a related video/animation that is playable.

View File

@@ -23,7 +23,8 @@ func TestGeoResult_Lat(t *testing.T) {
FileHeight: 0,
TakenAtLocal: time.Time{},
}
assert.Equal(t, 7.775000095367432, geo.Lat())
assert.InEpsilon(t, 7.775, geo.Lat(), 0.000001)
}
func TestGeoResult_Lng(t *testing.T) {
@@ -39,7 +40,8 @@ func TestGeoResult_Lng(t *testing.T) {
FileHeight: 0,
TakenAtLocal: time.Time{},
}
assert.Equal(t, 8.774999618530273, geo.Lng())
assert.InEpsilon(t, 8.774999618530273, geo.Lng(), 0.000001)
}
func TestGeoResults_GeoJSON(t *testing.T) {
@@ -95,7 +97,7 @@ func TestGeoResults_GeoJSON(t *testing.T) {
t.Fatal(err)
}
expected := []byte("{\"type\":\"FeatureCollection\",\"bbox\":[-5.775000095367432,-1.774999976158142,100.7750015258789,7.775000095367432]")
expected := []byte("{\"type\":\"FeatureCollection\",\"bbox\":[-5.775,-1.775,100.775,7.775]")
assert.Truef(t, bytes.Contains(b, expected), "GeoJSON not as expected")

View File

@@ -57,8 +57,8 @@ type Photo struct {
LensMake string `json:"LensMake,omitempty" select:"lenses.lens_model"`
LensModel string `json:"LensModel,omitempty" select:"lenses.lens_make"`
PhotoAltitude int `json:"Altitude,omitempty" select:"photos.photo_altitude"`
PhotoLat float32 `json:"Lat" select:"photos.photo_lat"`
PhotoLng float32 `json:"Lng" select:"photos.photo_lng"`
PhotoLat float64 `json:"Lat" select:"photos.photo_lat"`
PhotoLng float64 `json:"Lng" select:"photos.photo_lng"`
CellID string `json:"CellID" select:"photos.cell_id"` // Cell
CellAccuracy int `json:"CellAccuracy,omitempty" select:"photos.cell_accuracy"`
PlaceID string `json:"PlaceID" select:"photos.place_id"`

View File

@@ -693,8 +693,8 @@ func TestPhotos(t *testing.T) {
}
for _, p := range photos {
assert.GreaterOrEqual(t, float32(49.519234), p.PhotoLat)
assert.LessOrEqual(t, float32(33.45343166666667), p.PhotoLat)
assert.GreaterOrEqual(t, 49.519234, p.PhotoLat)
assert.LessOrEqual(t, 33.45343166666667, p.PhotoLat)
}
assert.LessOrEqual(t, 2, len(photos))
@@ -714,10 +714,10 @@ func TestPhotos(t *testing.T) {
}
for _, p := range photos {
assert.GreaterOrEqual(t, float32(49.519234), p.PhotoLat)
assert.LessOrEqual(t, float32(0.00), p.PhotoLat)
assert.GreaterOrEqual(t, float32(9.1001234), p.PhotoLng)
assert.LessOrEqual(t, float32(-30.123), p.PhotoLng)
assert.GreaterOrEqual(t, 49.519234, p.PhotoLat)
assert.LessOrEqual(t, 0.00, p.PhotoLat)
assert.GreaterOrEqual(t, 9.1001234, p.PhotoLng)
assert.LessOrEqual(t, -30.123, p.PhotoLng)
}
assert.LessOrEqual(t, 10, len(photos))

View File

@@ -45,8 +45,8 @@ type Photo struct {
PhotoScan bool `json:"Scan"`
PhotoPanorama bool `json:"Panorama"`
PhotoAltitude int `json:"Altitude"`
PhotoLat float32 `json:"Lat"`
PhotoLng float32 `json:"Lng"`
PhotoLat float64 `json:"Lat"`
PhotoLng float64 `json:"Lng"`
PhotoIso int `json:"Iso"`
PhotoFocalLength int `json:"FocalLength"`
PhotoFNumber float32 `json:"FNumber"`

View File

@@ -51,8 +51,8 @@ func TestNewPhoto(t *testing.T) {
assert.Equal(t, false, r.PhotoPrivate)
assert.Equal(t, "image", r.PhotoType)
assert.Equal(t, int8(1), r.PhotoStack)
assert.Equal(t, float32(9.9999), r.PhotoLat)
assert.Equal(t, float32(8.8888), r.PhotoLng)
assert.Equal(t, 9.9999, r.PhotoLat)
assert.Equal(t, 8.8888, r.PhotoLng)
assert.Equal(t, 2, r.PhotoAltitude)
assert.Equal(t, 5, r.PhotoIso)
assert.Equal(t, 10, r.PhotoFocalLength)

View File

@@ -20,8 +20,8 @@ type TestForm struct {
Photo bool `form:"photo"`
Archived bool `form:"archived"`
Error bool `form:"error"`
Lat float32 `form:"lat"`
Lng float32 `form:"lng"`
Lat float64 `form:"lat"`
Lng float64 `form:"lng"`
Dist uint `form:"dist"`
Color string `form:"color"`
Chroma int16 `form:"chroma"`

View File

@@ -62,8 +62,8 @@ type Data struct {
GPSPosition string `meta:"GPSPosition"`
GPSLatitude string `meta:"GPSLatitude"`
GPSLongitude string `meta:"GPSLongitude"`
Lat float32 `meta:"-"`
Lng float32 `meta:"-"`
Lat float64 `meta:"-"`
Lng float64 `meta:"-"`
Altitude float64 `meta:"GlobalAltitude,GPSAltitude"`
Width int `meta:"ImageWidth,PixelXDimension,ExifImageWidth,SourceImageWidth"`
Height int `meta:"ImageHeight,ImageLength,PixelYDimension,ExifImageHeight,SourceImageHeight"`

View File

@@ -261,8 +261,8 @@ func (data *Data) Exif(fileName string, fileFormat fs.Type, bruteForce bool) (er
if data.Lat != 0 && data.Lng != 0 {
zones, err := tz.GetZone(tz.Point{
Lat: float64(data.Lat),
Lon: float64(data.Lng),
Lat: data.Lat,
Lon: data.Lng,
})
if err == nil && len(zones) > 0 {

View File

@@ -26,8 +26,8 @@ func TestExif(t *testing.T) {
assert.Equal(t, "Adobe Photoshop CC 2014 (Windows)", data.Software)
assert.Equal(t, 1050, data.Height)
assert.Equal(t, 2100, data.Width)
assert.Equal(t, float32(0), data.Lat)
assert.Equal(t, float32(0), data.Lng)
assert.Equal(t, 0.0, data.Lat)
assert.Equal(t, 0.0, data.Lng)
assert.Equal(t, 0.0, data.Altitude)
assert.Equal(t, "", data.Exposure)
assert.Equal(t, "", data.CameraMake)
@@ -54,8 +54,8 @@ func TestExif(t *testing.T) {
assert.Equal(t, "Adobe Photoshop CC 2017 (Windows)", data.Software)
assert.Equal(t, 1050, data.Height)
assert.Equal(t, 2100, data.Width)
assert.Equal(t, float32(0), data.Lat)
assert.Equal(t, float32(0), data.Lng)
assert.Equal(t, 0.0, data.Lat)
assert.Equal(t, 0.0, data.Lng)
assert.Equal(t, 0.0, data.Altitude)
assert.Equal(t, "", data.Exposure)
assert.Equal(t, "", data.CameraMake)
@@ -84,8 +84,8 @@ func TestExif(t *testing.T) {
assert.Equal(t, "Adobe Photoshop 21.0 (Macintosh)", data.Software)
assert.Equal(t, 540, data.Height)
assert.Equal(t, 720, data.Width)
assert.Equal(t, float32(52.45969), data.Lat)
assert.Equal(t, float32(13.321832), data.Lng)
assert.InEpsilon(t, 52.45969, data.Lat, 0.00001)
assert.InEpsilon(t, 13.321832, data.Lng, 0.00001)
assert.Equal(t, 0.0, data.Altitude)
assert.Equal(t, "1/50", data.Exposure)
assert.Equal(t, "HUAWEI", data.CameraMake)
@@ -118,8 +118,8 @@ func TestExif(t *testing.T) {
assert.Equal(t, "", data.Copyright)
assert.Equal(t, 540, data.Height)
assert.Equal(t, 720, data.Width)
assert.Equal(t, float32(51.254852), data.Lat)
assert.Equal(t, float32(7.389468), data.Lng)
assert.InEpsilon(t, 51.254852, data.Lat, 0.00001)
assert.InEpsilon(t, 7.389468, data.Lng, 0.00001)
assert.Equal(t, 0.0, data.Altitude)
assert.Equal(t, "1/125", data.Exposure)
assert.Equal(t, "Canon", data.CameraMake)
@@ -150,8 +150,8 @@ func TestExif(t *testing.T) {
assert.Equal(t, "", data.Copyright)
assert.Equal(t, 180, data.Height)
assert.Equal(t, 240, data.Width)
assert.Equal(t, float32(0), data.Lat)
assert.Equal(t, float32(0), data.Lng)
assert.Equal(t, 0.0, data.Lat)
assert.Equal(t, 0.0, data.Lng)
assert.Equal(t, 0.0, data.Altitude)
assert.Equal(t, "1/2462", data.Exposure)
assert.Equal(t, "GoPro", data.CameraMake)
@@ -180,8 +180,8 @@ func TestExif(t *testing.T) {
assert.Equal(t, "2018-09-10T03:16:13Z", data.TakenAt.Format("2006-01-02T15:04:05Z"))
assert.Equal(t, "2018-09-10T12:16:13Z", data.TakenAtLocal.Format("2006-01-02T15:04:05Z"))
assert.Equal(t, float32(34.79745), data.Lat)
assert.Equal(t, float32(134.76463), data.Lng)
assert.InEpsilon(t, 34.79745, data.Lat, 0.00001)
assert.InEpsilon(t, 134.76463, data.Lng, 0.00001)
assert.Equal(t, 0.0, data.Altitude)
assert.Equal(t, "1/4000", data.Exposure)
assert.Equal(t, "Apple", data.CameraMake)
@@ -209,8 +209,8 @@ func TestExif(t *testing.T) {
assert.Equal(t, "", data.Copyright)
assert.Equal(t, 0, data.Height)
assert.Equal(t, 0, data.Width)
assert.Equal(t, float32(-38.405193), data.Lat)
assert.Equal(t, float32(144.18896), data.Lng)
assert.InEpsilon(t, -38.405193, data.Lat, 0.00001)
assert.InEpsilon(t, 144.18896, data.Lng, 0.00001)
assert.Equal(t, 0.0, data.Altitude)
assert.Equal(t, "", data.Exposure)
assert.Equal(t, "", data.CameraMake)
@@ -246,8 +246,8 @@ func TestExif(t *testing.T) {
assert.Equal(t, "2020-05-15T10:25:45Z", data.TakenAt.Format("2006-01-02T15:04:05Z")) // TODO
assert.Equal(t, "2020-05-15T10:25:45Z", data.TakenAtLocal.Format("2006-01-02T15:04:05Z")) // TODO
assert.Equal(t, float32(0), data.Lat)
assert.Equal(t, float32(0), data.Lng)
assert.Equal(t, 0.0, data.Lat)
assert.Equal(t, 0.0, data.Lng)
assert.Equal(t, 0.0, data.Altitude)
assert.Equal(t, "1/1100", data.Exposure)
assert.Equal(t, "SAMSUNG", data.CameraMake)
@@ -268,8 +268,8 @@ func TestExif(t *testing.T) {
assert.Equal(t, "2019-05-12T15:13:53Z", data.TakenAt.Format("2006-01-02T15:04:05Z"))
assert.Equal(t, "2019-05-12T17:13:53Z", data.TakenAtLocal.Format("2006-01-02T15:04:05Z"))
assert.Equal(t, 955677000, data.TakenNs)
assert.Equal(t, float32(53.12349), data.Lat)
assert.Equal(t, float32(18.00152), data.Lng)
assert.InEpsilon(t, 53.12349, data.Lat, 0.00001)
assert.InEpsilon(t, 18.00152, data.Lng, 0.00001)
assert.Equal(t, 63, clean.Altitude(data.Altitude))
assert.Equal(t, "1/100", data.Exposure)
assert.Equal(t, "Xiaomi", data.CameraMake)
@@ -361,8 +361,8 @@ func TestExif(t *testing.T) {
assert.Equal(t, "2020-06-16T16:52:46Z", data.TakenAt.Format("2006-01-02T15:04:05Z"))
assert.Equal(t, "2020-06-16T18:52:46Z", data.TakenAtLocal.Format("2006-01-02T15:04:05Z"))
assert.Equal(t, 695326000, data.TakenNs)
assert.Equal(t, float32(48.302776), data.Lat)
assert.Equal(t, float32(8.9275), data.Lng)
assert.InEpsilon(t, 48.302776, data.Lat, 0.00001)
assert.InEpsilon(t, 8.9275, data.Lng, 0.00001)
assert.Equal(t, 0.0, data.Altitude)
assert.Equal(t, "1/110", data.Exposure)
assert.Equal(t, "HUAWEI", data.CameraMake)
@@ -391,8 +391,8 @@ func TestExif(t *testing.T) {
assert.Equal(t, "", data.Copyright)
assert.Equal(t, 3600, data.Height)
assert.Equal(t, 7200, data.Width)
assert.Equal(t, float32(59.84083), data.Lat)
assert.Equal(t, float32(30.51), data.Lng)
assert.InEpsilon(t, 59.84083, data.Lat, 0.00001)
assert.InEpsilon(t, 30.51, data.Lng, 0.00001)
assert.Equal(t, 0.0, data.Altitude)
assert.Equal(t, "1/1250", data.Exposure)
assert.Equal(t, "SAMSUNG", data.CameraMake)
@@ -423,8 +423,8 @@ func TestExif(t *testing.T) {
assert.Equal(t, "", data.Copyright)
assert.Equal(t, 43, data.Height)
assert.Equal(t, 65, data.Width)
assert.Equal(t, float32(0), data.Lat)
assert.Equal(t, float32(0), data.Lng)
assert.Equal(t, 0.0, data.Lat)
assert.Equal(t, 0.0, data.Lng)
assert.Equal(t, 0.0, data.Altitude)
assert.Equal(t, "", data.Exposure)
assert.Equal(t, "", data.CameraMake)
@@ -455,8 +455,8 @@ func TestExif(t *testing.T) {
assert.Equal(t, "", data.Copyright)
assert.Equal(t, 2448, data.Height)
assert.Equal(t, 3264, data.Width)
assert.Equal(t, float32(0), data.Lat)
assert.Equal(t, float32(0), data.Lng)
assert.Equal(t, 0.0, data.Lat)
assert.Equal(t, 0.0, data.Lng)
assert.Equal(t, 0.0, data.Altitude)
assert.Equal(t, "1/387", data.Exposure)
assert.Equal(t, "Apple", data.CameraMake)
@@ -489,8 +489,8 @@ func TestExif(t *testing.T) {
assert.Equal(t, "", data.Copyright)
assert.Equal(t, 2736, data.Height)
assert.Equal(t, 3648, data.Width)
assert.Equal(t, float32(52.46052), data.Lat)
assert.Equal(t, float32(13.331402), data.Lng)
assert.InEpsilon(t, 52.46052, data.Lat, 0.00001)
assert.InEpsilon(t, 13.331402, data.Lng, 0.00001)
assert.Equal(t, 84, clean.Altitude(data.Altitude))
assert.Equal(t, "1/50", data.Exposure)
assert.Equal(t, "HUAWEI", data.CameraMake)
@@ -515,8 +515,8 @@ func TestExif(t *testing.T) {
assert.Equal(t, 3000, data.Height)
assert.Equal(t, 4000, data.Width)
assert.Equal(t, float32(0), data.Lat)
assert.Equal(t, float32(0), data.Lng)
assert.Equal(t, 0.0, data.Lat)
assert.Equal(t, 0.0, data.Lng)
assert.Equal(t, 0.0, data.Altitude)
assert.Equal(t, "1/24", data.Exposure)
assert.Equal(t, "HMD Global", data.CameraMake)
@@ -536,8 +536,8 @@ func TestExif(t *testing.T) {
assert.Equal(t, 3072, data.Height)
assert.Equal(t, 4608, data.Width)
assert.Equal(t, float32(0), data.Lat)
assert.Equal(t, float32(0), data.Lng)
assert.Equal(t, 0.0, data.Lat)
assert.Equal(t, 0.0, data.Lng)
assert.Equal(t, 0.0, data.Altitude)
assert.Equal(t, "1/1600", data.Exposure)
assert.Equal(t, "OLYMPUS IMAGING CORP.", data.CameraMake)
@@ -584,8 +584,8 @@ func TestExif(t *testing.T) {
assert.Equal(t, "Nicolas Cornet", data.Copyright)
assert.Equal(t, 400, data.Height)
assert.Equal(t, 600, data.Width)
assert.Equal(t, float32(65.05558), data.Lat)
assert.Equal(t, float32(-16.625702), data.Lng)
assert.InEpsilon(t, 65.05558, data.Lat, 0.00001)
assert.InEpsilon(t, -16.625702, data.Lng, 0.00001)
assert.Equal(t, 0.0, data.Altitude)
assert.Equal(t, "1/8", data.Exposure)
assert.Equal(t, "NIKON CORPORATION", data.CameraMake)
@@ -616,8 +616,8 @@ func TestExif(t *testing.T) {
assert.Equal(t, "Nicolas Cornet", data.Copyright)
assert.Equal(t, 400, data.Height)
assert.Equal(t, 600, data.Width)
assert.Equal(t, float32(65.05558), data.Lat)
assert.Equal(t, float32(-16.625702), data.Lng)
assert.InEpsilon(t, 65.05558, data.Lat, 0.00001)
assert.InEpsilon(t, -16.625702, data.Lng, 0.00001)
assert.Equal(t, 0.0, data.Altitude)
assert.Equal(t, "1/8", data.Exposure)
assert.Equal(t, "NIKON CORPORATION", data.CameraMake)
@@ -651,8 +651,8 @@ func TestExif(t *testing.T) {
assert.Equal(t, "2021-10-29 13:42:00 +0000 UTC", data.TakenAt.String())
assert.Equal(t, "", data.TimeZone) // Local Time
assert.Equal(t, 1, data.Orientation)
assert.Equal(t, float32(0), data.Lat)
assert.Equal(t, float32(0), data.Lng)
assert.Equal(t, 0.0, data.Lat)
assert.Equal(t, 0.0, data.Lng)
})
t.Run("buggy_panorama.jpg", func(t *testing.T) {
@@ -666,8 +666,8 @@ func TestExif(t *testing.T) {
assert.Equal(t, "2022-04-24 02:35:53 +0000 UTC", data.TakenAt.String())
assert.Equal(t, "Asia/Shanghai", data.TimeZone) // Local Time
assert.Equal(t, 1, data.Orientation)
assert.Equal(t, float32(33.640007), data.Lat)
assert.Equal(t, float32(103.48), data.Lng)
assert.InEpsilon(t, 33.640007, data.Lat, 0.00001)
assert.InEpsilon(t, 103.48, data.Lng, 0.00001)
assert.Equal(t, 0.0, data.Altitude)
})
@@ -678,8 +678,8 @@ func TestExif(t *testing.T) {
t.Fatal(err)
}
assert.Equal(t, float32(45.75285), data.Lat)
assert.Equal(t, float32(33.221977), data.Lng)
assert.InEpsilon(t, 45.75285, data.Lat, 0.00001)
assert.InEpsilon(t, 33.221977, data.Lng, 0.00001)
assert.InEpsilon(t, 4294967284, data.Altitude, 1000)
assert.Equal(t, 0, clean.Altitude(data.Altitude))
})

View File

@@ -107,13 +107,13 @@ func ParseFloat(s string) float64 {
}
// NormalizeGPS normalizes the longitude and latitude of the GPS position to a generally valid range.
func NormalizeGPS(lat, lng float64) (float32, float32) {
func NormalizeGPS(lat, lng float64) (float64, float64) {
if lat < LatMax || lat > LatMax || lng < LngMax || lng > LngMax {
// Clip the latitude. Normalise the longitude.
lat, lng = clipLat(lat), normalizeLng(lng)
}
return float32(lat), float32(lng)
return lat, lng
}
func clipLat(lat float64) float64 {

View File

@@ -127,8 +127,8 @@ func (data *Data) GPhoto(jsonData []byte) (err error) {
if p.Geo.Exists() {
if data.Lat == 0 && data.Lng == 0 {
data.Lat = float32(p.Geo.Lat)
data.Lng = float32(p.Geo.Lng)
data.Lat = p.Geo.Lat
data.Lng = p.Geo.Lng
}
if data.Altitude == 0 {

View File

@@ -34,8 +34,8 @@ func TestJSON_Motion(t *testing.T) {
assert.Equal(t, 3024, data.ActualWidth())
assert.Equal(t, 4032, data.ActualHeight())
assert.Equal(t, 1, data.Orientation)
assert.Equal(t, float32(35.42307), data.Lat)
assert.Equal(t, float32(-78.65212), data.Lng)
assert.InEpsilon(t, 35.42307, data.Lat, 0.00001)
assert.InEpsilon(t, -78.65212, data.Lng, 0.00001)
assert.Equal(t, "Google", data.CameraMake)
assert.Equal(t, "Pixel 2", data.CameraModel)
assert.Equal(t, "", data.LensModel)
@@ -65,8 +65,8 @@ func TestJSON_Motion(t *testing.T) {
assert.Equal(t, 3024, data.ActualWidth())
assert.Equal(t, 4032, data.ActualHeight())
assert.Equal(t, 6, data.Orientation)
assert.Equal(t, float32(35.778152), data.Lat)
assert.Equal(t, float32(-78.63687), data.Lng)
assert.InEpsilon(t, 35.778152, data.Lat, 0.00001)
assert.InEpsilon(t, -78.63687, data.Lng, 0.00001)
assert.Equal(t, "Google", data.CameraMake)
assert.Equal(t, "Pixel 4a", data.CameraModel)
assert.Equal(t, "", data.LensModel)
@@ -96,8 +96,8 @@ func TestJSON_Motion(t *testing.T) {
assert.Equal(t, 4032, data.ActualWidth())
assert.Equal(t, 2268, data.ActualHeight())
assert.Equal(t, 1, data.Orientation)
assert.Equal(t, float32(48.610027), data.Lat)
assert.Equal(t, float32(8.861558), data.Lng)
assert.InEpsilon(t, 48.610027, data.Lat, 0.00001)
assert.InEpsilon(t, 8.861558, data.Lng, 0.00001)
assert.Equal(t, "Google", data.CameraMake)
assert.Equal(t, "Pixel 6", data.CameraModel)
assert.Equal(t, "Pixel 6 back camera 6.81mm f/1.85", data.LensModel)
@@ -127,8 +127,8 @@ func TestJSON_Motion(t *testing.T) {
assert.Equal(t, 4080, data.ActualWidth())
assert.Equal(t, 3072, data.ActualHeight())
assert.Equal(t, 1, data.Orientation)
assert.Equal(t, float32(52.70636), data.Lat)
assert.Equal(t, float32(-2.7605944), data.Lng)
assert.InEpsilon(t, 52.70636, data.Lat, 0.00001)
assert.InEpsilon(t, -2.7605944, data.Lng, 0.00001)
assert.Equal(t, "Google", data.CameraMake)
assert.Equal(t, "Pixel 7 Pro", data.CameraModel)
assert.Equal(t, "Pixel 7 Pro back camera 19.0mm f/3.5", data.LensModel)
@@ -159,8 +159,8 @@ func TestJSON_Motion(t *testing.T) {
assert.Equal(t, 3024, data.ActualWidth())
assert.Equal(t, 4032, data.ActualHeight())
assert.Equal(t, 6, data.Orientation)
assert.Equal(t, float32(0), data.Lat)
assert.Equal(t, float32(0), data.Lng)
assert.Equal(t, 0.0, data.Lat)
assert.Equal(t, 0.0, data.Lng)
assert.Equal(t, "samsung", data.CameraMake)
assert.Equal(t, "SM-G780F", data.CameraModel)
assert.Equal(t, "", data.LensModel)
@@ -190,8 +190,8 @@ func TestJSON_Motion(t *testing.T) {
assert.Equal(t, 1080, data.ActualWidth())
assert.Equal(t, 1440, data.ActualHeight())
assert.Equal(t, 6, data.Orientation)
assert.Equal(t, float32(48.4565), data.Lat)
assert.Equal(t, float32(35.072), data.Lng)
assert.InEpsilon(t, 48.4565, data.Lat, 0.00001)
assert.InEpsilon(t, 35.072, data.Lng, 0.00001)
assert.Equal(t, "", data.CameraMake)
assert.Equal(t, "", data.CameraModel)
assert.Equal(t, "", data.LensModel)
@@ -221,8 +221,8 @@ func TestJSON_Motion(t *testing.T) {
assert.Equal(t, 3024, data.ActualWidth())
assert.Equal(t, 4032, data.ActualHeight())
assert.Equal(t, 6, data.Orientation)
assert.Equal(t, float32(51.433468), data.Lat)
assert.Equal(t, float32(12.110732), data.Lng)
assert.InEpsilon(t, 51.433468, data.Lat, 0.00001)
assert.InEpsilon(t, 12.110732, data.Lng, 0.00001)
assert.Equal(t, "samsung", data.CameraMake)
assert.Equal(t, "SM-G781B", data.CameraModel)
assert.Equal(t, "", data.LensModel)
@@ -253,8 +253,8 @@ func TestJSON_Motion(t *testing.T) {
assert.Equal(t, 4000, data.ActualWidth())
assert.Equal(t, 2252, data.ActualHeight())
assert.Equal(t, 1, data.Orientation)
assert.Equal(t, float32(0), data.Lat)
assert.Equal(t, float32(0), data.Lng)
assert.Equal(t, 0.0, data.Lat)
assert.Equal(t, 0.0, data.Lng)
assert.Equal(t, "samsung", data.CameraMake)
assert.Equal(t, "SM-G998B", data.CameraModel)
assert.Equal(t, "", data.LensModel)
@@ -285,8 +285,8 @@ func TestJSON_Motion(t *testing.T) {
assert.Equal(t, 1920, data.ActualWidth())
assert.Equal(t, 1080, data.ActualHeight())
assert.Equal(t, 1, data.Orientation)
assert.Equal(t, float32(0), data.Lat)
assert.Equal(t, float32(0), data.Lng)
assert.Equal(t, 0.0, data.Lat)
assert.Equal(t, 0.0, data.Lng)
assert.Equal(t, "", data.CameraMake)
assert.Equal(t, "", data.CameraModel)
assert.Equal(t, "", data.LensModel)
@@ -317,8 +317,8 @@ func TestJSON_Motion(t *testing.T) {
assert.Equal(t, 3468, data.ActualWidth())
assert.Equal(t, 4624, data.ActualHeight())
assert.Equal(t, 6, data.Orientation)
assert.Equal(t, float32(0), data.Lat)
assert.Equal(t, float32(0), data.Lng)
assert.Equal(t, 0.0, data.Lat)
assert.Equal(t, 0.0, data.Lng)
assert.Equal(t, "Samsung", data.CameraMake)
assert.Equal(t, "Galaxy A71", data.CameraModel)
assert.Equal(t, "", data.LensModel)

View File

@@ -35,8 +35,8 @@ func TestJSON(t *testing.T) {
assert.Equal(t, 1920, data.ActualWidth())
assert.Equal(t, 1440, data.ActualHeight())
assert.Equal(t, 1, data.Orientation)
assert.Equal(t, float32(52.5035), data.Lat)
assert.Equal(t, float32(13.4098), data.Lng)
assert.InEpsilon(t, 52.5035, data.Lat, 0.00001)
assert.InEpsilon(t, 13.4098, data.Lng, 0.00001)
assert.Equal(t, "Apple", data.CameraMake)
assert.Equal(t, "iPhone 12 mini", data.CameraModel)
assert.Equal(t, "", data.LensModel)
@@ -65,8 +65,8 @@ func TestJSON(t *testing.T) {
assert.Equal(t, 1080, data.ActualWidth())
assert.Equal(t, 1920, data.ActualHeight())
assert.Equal(t, 6, data.Orientation)
assert.Equal(t, float32(52.4587), data.Lat)
assert.Equal(t, float32(13.4593), data.Lng)
assert.InEpsilon(t, 52.4587, data.Lat, 0.00001)
assert.InEpsilon(t, 13.4593, data.Lng, 0.00001)
assert.Equal(t, "Apple", data.CameraMake)
assert.Equal(t, "iPhone SE", data.CameraModel)
assert.Equal(t, "", data.LensModel)
@@ -173,8 +173,8 @@ func TestJSON(t *testing.T) {
assert.Equal(t, 270, data.ActualWidth())
assert.Equal(t, 480, data.ActualHeight())
assert.Equal(t, 1, data.Orientation)
assert.Equal(t, float32(0), data.Lat)
assert.Equal(t, float32(0), data.Lng)
assert.Equal(t, 0.0, data.Lat)
assert.Equal(t, 0.0, data.Lng)
assert.Equal(t, "", data.CameraMake)
assert.Equal(t, "", data.CameraModel)
assert.Equal(t, "", data.LensModel)
@@ -200,8 +200,8 @@ func TestJSON(t *testing.T) {
assert.Equal(t, 1920, data.ActualHeight())
assert.Equal(t, float32(0.56), data.AspectRatio())
assert.Equal(t, 6, data.Orientation)
assert.Equal(t, float32(52.4596), data.Lat)
assert.Equal(t, float32(13.3218), data.Lng)
assert.InEpsilon(t, 52.4596, data.Lat, 0.00001)
assert.InEpsilon(t, 13.3218, data.Lng, 0.00001)
assert.Equal(t, "", data.CameraMake)
assert.Equal(t, "", data.CameraModel)
assert.Equal(t, "", data.LensModel)
@@ -225,8 +225,8 @@ func TestJSON(t *testing.T) {
assert.Equal(t, 1920, data.Width)
assert.Equal(t, 1080, data.Height)
assert.Equal(t, 1, data.Orientation)
assert.Equal(t, float32(52.4649), data.Lat)
assert.Equal(t, float32(13.3148), data.Lng)
assert.InEpsilon(t, 52.4649, data.Lat, 0.00001)
assert.InEpsilon(t, 13.3148, data.Lng, 0.00001)
assert.Equal(t, "", data.CameraMake)
assert.Equal(t, "", data.CameraModel)
assert.Equal(t, "", data.LensModel)
@@ -264,8 +264,8 @@ func TestJSON(t *testing.T) {
assert.Equal(t, "photoshop.xmp", data.FileName)
assert.Equal(t, CodecXMP, data.Codec)
assert.Equal(t, "0s", data.Duration.String())
assert.Equal(t, float32(52.45969), data.Lat)
assert.Equal(t, float32(13.321831), data.Lng)
assert.InEpsilon(t, 52.45969, data.Lat, 0.00001)
assert.InEpsilon(t, 13.321831, data.Lng, 0.00001)
assert.Equal(t, "2020-01-01 16:28:23 +0000 UTC", data.TakenAt.String())
assert.Equal(t, "2020-01-01 17:28:23 +0000 UTC", data.TakenAtLocal.String())
assert.Equal(t, 899614000, data.TakenNs)
@@ -383,8 +383,8 @@ func TestJSON(t *testing.T) {
assert.Equal(t, 3024, data.Width)
assert.Equal(t, 4032, data.Height)
assert.Equal(t, 1, data.Orientation)
assert.Equal(t, float32(48.300003), data.Lat)
assert.Equal(t, float32(8.929067), data.Lng)
assert.InEpsilon(t, 48.300003, data.Lat, 0.00001)
assert.InEpsilon(t, 8.929067, data.Lng, 0.00001)
assert.Equal(t, "Apple", data.CameraMake)
assert.Equal(t, "iPhone SE", data.CameraModel)
assert.Equal(t, "iPhone SE back camera 4.15mm f/2.2", data.LensModel)
@@ -409,8 +409,8 @@ func TestJSON(t *testing.T) {
assert.Equal(t, 1024, data.Width)
assert.Equal(t, 1365, data.Height)
assert.Equal(t, 1, data.Orientation)
assert.Equal(t, float32(48.300003), data.Lat)
assert.Equal(t, float32(8.929067), data.Lng)
assert.InEpsilon(t, 48.300003, data.Lat, 0.00001)
assert.InEpsilon(t, 8.929067, data.Lng, 0.00001)
assert.Equal(t, "Apple", data.CameraMake)
assert.Equal(t, "iPhone SE", data.CameraModel)
assert.Equal(t, "iPhone SE back camera 4.15mm f/2.2", data.LensModel)
@@ -435,8 +435,8 @@ func TestJSON(t *testing.T) {
assert.Equal(t, 1125, data.Width)
assert.Equal(t, 1500, data.Height)
assert.Equal(t, 1, data.Orientation)
assert.Equal(t, float32(48.300003), data.Lat)
assert.Equal(t, float32(8.929067), data.Lng)
assert.InEpsilon(t, 48.300003, data.Lat, 0.00001)
assert.InEpsilon(t, 8.929067, data.Lng, 0.00001)
assert.Equal(t, "Apple", data.CameraMake)
assert.Equal(t, "iPhone SE", data.CameraModel)
assert.Equal(t, "iPhone SE back camera 4.15mm f/2.2", data.LensModel)
@@ -466,8 +466,8 @@ func TestJSON(t *testing.T) {
assert.Equal(t, "2015-12-06 16:18:30 +0000 UTC", data.TakenAtLocal.String())
assert.Equal(t, "2015-12-06 15:18:30 +0000 UTC", data.TakenAt.String())
assert.Equal(t, "Europe/Berlin", data.TimeZone)
assert.Equal(t, float32(52.508522), data.Lat)
assert.Equal(t, float32(13.443206), data.Lng)
assert.InEpsilon(t, 52.508522, data.Lat, 0.00001)
assert.InEpsilon(t, 13.443206, data.Lng, 0.00001)
assert.Equal(t, 40, clean.Altitude(data.Altitude))
assert.Equal(t, 0, data.Views)
@@ -495,8 +495,8 @@ func TestJSON(t *testing.T) {
assert.Equal(t, "2019-05-18 12:06:45 +0000 UTC", data.TakenAtLocal.String())
assert.Equal(t, "2019-05-18 10:06:45 +0000 UTC", data.TakenAt.String())
assert.Equal(t, "Europe/Berlin", data.TimeZone)
assert.Equal(t, float32(52.510796), data.Lat)
assert.Equal(t, float32(13.456387), data.Lng)
assert.InEpsilon(t, 52.510796, data.Lat, 0.00001)
assert.InEpsilon(t, 13.456387, data.Lng, 0.00001)
assert.Equal(t, 0.0, data.Altitude)
assert.Equal(t, 1118, data.Views)
})
@@ -513,8 +513,8 @@ func TestJSON(t *testing.T) {
assert.Equal(t, "2011-11-07 21:34:34 +0000 UTC", data.TakenAtLocal.String())
assert.Equal(t, "2011-11-07 21:34:34 +0000 UTC", data.TakenAt.String())
assert.Equal(t, "", data.TimeZone)
assert.Equal(t, float32(0), data.Lat)
assert.Equal(t, float32(0), data.Lng)
assert.Equal(t, 0.0, data.Lat)
assert.Equal(t, 0.0, data.Lng)
assert.Equal(t, 0.0, data.Altitude)
assert.Equal(t, 177, data.Views)
})
@@ -531,8 +531,8 @@ func TestJSON(t *testing.T) {
assert.Equal(t, "2012-12-11 00:07:15 +0000 UTC", data.TakenAtLocal.String())
assert.Equal(t, "2012-12-10 23:07:15 +0000 UTC", data.TakenAt.String())
assert.Equal(t, "Europe/Berlin", data.TimeZone)
assert.Equal(t, float32(52.49967), data.Lat)
assert.Equal(t, float32(13.422334), data.Lng)
assert.InEpsilon(t, 52.49967, data.Lat, 0.00001)
assert.InEpsilon(t, 13.422334, data.Lng, 0.00001)
assert.Equal(t, 0.0, data.Altitude)
assert.Equal(t, 0, data.Views)
})
@@ -573,8 +573,8 @@ func TestJSON(t *testing.T) {
assert.Equal(t, "", data.Copyright)
assert.Equal(t, 3600, data.Height)
assert.Equal(t, 7200, data.Width)
assert.Equal(t, float32(59.84083), data.Lat)
assert.Equal(t, float32(30.51), data.Lng)
assert.InEpsilon(t, 59.84083, data.Lat, 0.00001)
assert.Equal(t, 30.51, data.Lng)
assert.Equal(t, 0.0, data.Altitude)
assert.Equal(t, "1/1250", data.Exposure)
assert.Equal(t, "SAMSUNG", data.CameraMake)
@@ -604,8 +604,8 @@ func TestJSON(t *testing.T) {
assert.Equal(t, "", data.Copyright)
assert.Equal(t, 1080, data.Height)
assert.Equal(t, 1920, data.Width)
assert.Equal(t, float32(0), data.Lat)
assert.Equal(t, float32(0), data.Lng)
assert.Equal(t, 0.0, data.Lat)
assert.Equal(t, 0.0, data.Lng)
assert.Equal(t, 0.0, data.Altitude)
assert.Equal(t, "", data.Exposure)
assert.Equal(t, "OLYMPUS DIGITAL CAMERA", data.CameraMake)
@@ -660,8 +660,8 @@ func TestJSON(t *testing.T) {
assert.Equal(t, "", data.Copyright)
assert.Equal(t, 375, data.Height)
assert.Equal(t, 500, data.Width)
assert.Equal(t, float32(52.46052), data.Lat)
assert.Equal(t, float32(13.331403), data.Lng)
assert.InEpsilon(t, 52.46052, data.Lat, 0.00001)
assert.InEpsilon(t, 13.331403, data.Lng, 0.00001)
assert.Equal(t, 84, clean.Altitude(data.Altitude))
assert.Equal(t, "1/50", data.Exposure)
assert.Equal(t, "HUAWEI", data.CameraMake)
@@ -691,8 +691,8 @@ func TestJSON(t *testing.T) {
assert.Equal(t, 1080, data.ActualWidth())
assert.Equal(t, 1920, data.ActualHeight())
assert.Equal(t, 6, data.Orientation)
assert.Equal(t, float32(55.5636), data.Lat)
assert.Equal(t, float32(37.9824), data.Lng)
assert.InEpsilon(t, 55.5636, data.Lat, 0.00001)
assert.InEpsilon(t, 37.9824, data.Lng, 0.00001)
assert.Equal(t, "Apple", data.CameraMake)
assert.Equal(t, "iPhone 6 Plus", data.CameraModel)
assert.Equal(t, "", data.LensModel)
@@ -716,8 +716,8 @@ func TestJSON(t *testing.T) {
assert.Equal(t, 1920, data.ActualWidth())
assert.Equal(t, 1080, data.ActualHeight())
assert.Equal(t, 1, data.Orientation)
assert.Equal(t, float32(55.7579), data.Lat)
assert.Equal(t, float32(37.6197), data.Lng)
assert.InEpsilon(t, 55.7579, data.Lat, 0.00001)
assert.InEpsilon(t, 37.6197, data.Lng, 0.00001)
assert.Equal(t, "Apple", data.CameraMake)
assert.Equal(t, "iPhone 6 Plus", data.CameraModel)
assert.Equal(t, "", data.LensModel)
@@ -740,8 +740,8 @@ func TestJSON(t *testing.T) {
assert.Equal(t, 1080, data.ActualWidth())
assert.Equal(t, 1920, data.ActualHeight())
assert.Equal(t, 6, data.Orientation)
assert.Equal(t, float32(0), data.Lat)
assert.Equal(t, float32(0), data.Lng)
assert.Equal(t, 0.0, data.Lat)
assert.Equal(t, 0.0, data.Lng)
assert.Equal(t, "Apple", data.CameraMake)
assert.Equal(t, "iPhone 8", data.CameraModel)
assert.Equal(t, "", data.LensModel)
@@ -760,8 +760,8 @@ func TestJSON(t *testing.T) {
assert.Equal(t, "2019-12-13 01:47:21 +0000 UTC", data.TakenAt.String())
assert.Equal(t, "America/New_York", data.TimeZone)
assert.Equal(t, 1, data.Orientation)
assert.Equal(t, float32(40.7696), data.Lat)
assert.Equal(t, float32(-73.9964), data.Lng)
assert.InEpsilon(t, 40.7696, data.Lat, 0.00001)
assert.InEpsilon(t, -73.9964, data.Lng, 0.00001)
assert.Equal(t, "Apple", data.CameraMake)
assert.Equal(t, "iPhone X", data.CameraModel)
assert.Equal(t, "", data.LensModel)
@@ -781,8 +781,8 @@ func TestJSON(t *testing.T) {
assert.Equal(t, "2021-10-27 10:43:46 +0200 UTC+02:00", data.TakenAt.String())
assert.Equal(t, "", data.TimeZone) // Local Time
assert.Equal(t, 1, data.Orientation)
assert.Equal(t, float32(0), data.Lat)
assert.Equal(t, float32(0), data.Lng)
assert.Equal(t, 0.0, data.Lat)
assert.Equal(t, 0.0, data.Lng)
})
t.Run("MVI_1724.MOV.json", func(t *testing.T) {
@@ -798,8 +798,8 @@ func TestJSON(t *testing.T) {
assert.Equal(t, "2022-06-25 04:50:58 +0000 UTC", data.TakenAt.String())
assert.Equal(t, "UTC+2", data.TimeZone) // Local Time
assert.Equal(t, 1, data.Orientation)
assert.Equal(t, float32(0), data.Lat)
assert.Equal(t, float32(0), data.Lng)
assert.Equal(t, 0.0, data.Lat)
assert.Equal(t, 0.0, data.Lng)
assert.Equal(t, "Canon", data.CameraMake)
assert.Equal(t, "Canon PowerShot G15", data.CameraModel)
assert.Equal(t, "6.1", data.LensModel)
@@ -823,8 +823,8 @@ func TestJSON(t *testing.T) {
assert.Equal(t, 4608, data.ActualWidth())
assert.Equal(t, 3072, data.ActualHeight())
assert.Equal(t, 1, data.Orientation)
assert.Equal(t, float32(0), data.Lat)
assert.Equal(t, float32(0), data.Lng)
assert.Equal(t, 0.0, data.Lat)
assert.Equal(t, 0.0, data.Lng)
assert.Equal(t, "OLYMPUS IMAGING CORP.", data.CameraMake)
assert.Equal(t, "TG-830", data.CameraModel)
assert.Equal(t, "", data.LensModel)
@@ -849,8 +849,8 @@ func TestJSON(t *testing.T) {
assert.Equal(t, 4608, data.ActualWidth())
assert.Equal(t, 3072, data.ActualHeight())
assert.Equal(t, 1, data.Orientation)
assert.Equal(t, float32(0), data.Lat)
assert.Equal(t, float32(0), data.Lng)
assert.Equal(t, 0.0, data.Lat)
assert.Equal(t, 0.0, data.Lng)
assert.Equal(t, "OLYMPUS IMAGING CORP.", data.CameraMake)
assert.Equal(t, "TG-830", data.CameraModel)
assert.Equal(t, "", data.LensModel)
@@ -874,8 +874,8 @@ func TestJSON(t *testing.T) {
assert.Equal(t, 4032, data.ActualWidth())
assert.Equal(t, 3024, data.ActualHeight())
assert.Equal(t, 1, data.Orientation)
assert.Equal(t, float32(0), data.Lat)
assert.Equal(t, float32(0), data.Lng)
assert.Equal(t, 0.0, data.Lat)
assert.Equal(t, 0.0, data.Lng)
assert.Equal(t, "Apple", data.CameraMake)
assert.Equal(t, "iPhone 6s", data.CameraModel)
assert.Equal(t, "iPhone 6s back camera 4.15mm f/2.2", data.LensModel)
@@ -900,8 +900,8 @@ func TestJSON(t *testing.T) {
assert.Equal(t, 4032, data.ActualWidth())
assert.Equal(t, 3024, data.ActualHeight())
assert.Equal(t, 1, data.Orientation)
assert.Equal(t, float32(0), data.Lat)
assert.Equal(t, float32(0), data.Lng)
assert.Equal(t, 0.0, data.Lat)
assert.Equal(t, 0.0, data.Lng)
assert.Equal(t, "Apple", data.CameraMake)
assert.Equal(t, "iPhone 6s", data.CameraModel)
assert.Equal(t, "iPhone 6s back camera 4.15mm f/2.2", data.LensModel)
@@ -929,8 +929,8 @@ func TestJSON(t *testing.T) {
assert.Equal(t, "© 2011 PhotoPrism", data.Copyright)
assert.Equal(t, 567, data.Height)
assert.Equal(t, 850, data.Width)
assert.Equal(t, float32(0), data.Lat)
assert.Equal(t, float32(0), data.Lng)
assert.Equal(t, 0.0, data.Lat)
assert.Equal(t, 0.0, data.Lng)
assert.Equal(t, 30, clean.Altitude(data.Altitude))
assert.Equal(t, "1/6", data.Exposure)
assert.Equal(t, "Canon", data.CameraMake)
@@ -975,8 +975,8 @@ func TestJSON(t *testing.T) {
assert.Equal(t, "2012-07-11 05:16:01 +0000 UTC", data.TakenAt.String())
assert.Equal(t, "Europe/Paris", data.TimeZone)
assert.Equal(t, 1, data.Orientation)
assert.Equal(t, float32(43.5683), data.Lat)
assert.Equal(t, float32(4.5645), data.Lng)
assert.InEpsilon(t, 43.5683, data.Lat, 0.00001)
assert.InEpsilon(t, 4.5645, data.Lng, 0.00001)
})
t.Run("quicktimeutc_off.json", func(t *testing.T) {
@@ -992,8 +992,8 @@ func TestJSON(t *testing.T) {
assert.Equal(t, "2012-07-11 05:16:01 +0000 UTC", data.TakenAt.String())
assert.Equal(t, "Europe/Paris", data.TimeZone)
assert.Equal(t, 1, data.Orientation)
assert.Equal(t, float32(43.5683), data.Lat)
assert.Equal(t, float32(4.5645), data.Lng)
assert.Equal(t, float32(43.5683), float32(data.Lat))
assert.Equal(t, float32(4.5645), float32(data.Lng))
})
t.Run("video_num_on.json", func(t *testing.T) {
@@ -1009,8 +1009,8 @@ func TestJSON(t *testing.T) {
assert.Equal(t, "2012-07-11 05:16:01 +0000 UTC", data.TakenAt.String())
assert.Equal(t, "Europe/Paris", data.TimeZone)
assert.Equal(t, 6, data.Orientation)
assert.Equal(t, float32(43.5683), data.Lat)
assert.Equal(t, float32(4.5645), data.Lng)
assert.Equal(t, float32(43.5683), float32(data.Lat))
assert.Equal(t, float32(4.5645), float32(data.Lng))
})
t.Run("cr2_num_off.json", func(t *testing.T) {
@@ -1026,8 +1026,8 @@ func TestJSON(t *testing.T) {
assert.Equal(t, "2015-02-13 16:14:11.91", data.TakenGps.Format("2006-01-02 15:04:05.999999999"))
assert.Equal(t, 3648, data.Height)
assert.Equal(t, 5472, data.Width)
assert.Equal(t, float32(32.843544), data.Lat)
assert.Equal(t, float32(-117.28025), data.Lng)
assert.Equal(t, float32(32.843544), float32(data.Lat))
assert.Equal(t, float32(-117.28025), float32(data.Lng))
assert.Equal(t, 18, clean.Altitude(data.Altitude))
assert.Equal(t, "1/500", data.Exposure)
assert.Equal(t, "Canon", data.CameraMake)
@@ -1050,8 +1050,8 @@ func TestJSON(t *testing.T) {
assert.Equal(t, "2015-02-13T18:14:40Z", data.TakenAtLocal.Format("2006-01-02T15:04:05Z"))
assert.Equal(t, 3648, data.Height)
assert.Equal(t, 5472, data.Width)
assert.Equal(t, float32(32.843544), data.Lat)
assert.Equal(t, float32(-117.28025), data.Lng)
assert.Equal(t, float32(32.843544), float32(data.Lat))
assert.Equal(t, float32(-117.28025), float32(data.Lng))
assert.Equal(t, 18, clean.Altitude(data.Altitude))
assert.Equal(t, "0.002", data.Exposure)
assert.Equal(t, "Canon", data.CameraMake)
@@ -1075,8 +1075,8 @@ func TestJSON(t *testing.T) {
assert.Equal(t, time.UTC.String(), data.TimeZone)
assert.Equal(t, 1080, data.Height)
assert.Equal(t, 1920, data.Width)
assert.Equal(t, float32(0), data.Lat)
assert.Equal(t, float32(0), data.Lng)
assert.Equal(t, 0.0, data.Lat)
assert.Equal(t, 0.0, data.Lng)
assert.Equal(t, 0.0, data.Altitude)
assert.Equal(t, 1, data.Orientation)
})
@@ -1093,8 +1093,8 @@ func TestJSON(t *testing.T) {
assert.Equal(t, time.UTC.String(), data.TimeZone)
assert.Equal(t, 1080, data.Height)
assert.Equal(t, 1920, data.Width)
assert.Equal(t, float32(0), data.Lat)
assert.Equal(t, float32(0), data.Lng)
assert.Equal(t, 0.0, data.Lat)
assert.Equal(t, 0.0, data.Lng)
assert.Equal(t, 0.0, data.Altitude)
assert.Equal(t, 1, data.Orientation)
})
@@ -1117,8 +1117,8 @@ func TestJSON(t *testing.T) {
assert.Equal(t, "Nicolas Cornet", data.Copyright)
assert.Equal(t, 400, data.Height)
assert.Equal(t, 600, data.Width)
assert.Equal(t, float32(65.05558), data.Lat)
assert.Equal(t, float32(-16.625702), data.Lng)
assert.Equal(t, float32(65.05558), float32(data.Lat))
assert.Equal(t, float32(-16.625702), float32(data.Lng))
assert.Equal(t, 30, clean.Altitude(data.Altitude))
assert.Equal(t, "1/8", data.Exposure)
assert.Equal(t, "NIKON CORPORATION", data.CameraMake)
@@ -1150,8 +1150,8 @@ func TestJSON(t *testing.T) {
assert.Equal(t, "Nicolas Cornet", data.Copyright)
assert.Equal(t, 400, data.Height)
assert.Equal(t, 600, data.Width)
assert.Equal(t, float32(65.05558), data.Lat)
assert.Equal(t, float32(-16.625702), data.Lng)
assert.Equal(t, float32(65.05558), float32(data.Lat))
assert.Equal(t, float32(-16.625702), float32(data.Lng))
assert.Equal(t, 30, clean.Altitude(data.Altitude))
assert.Equal(t, "0.125", data.Exposure)
assert.Equal(t, "NIKON CORPORATION", data.CameraMake)
@@ -1183,8 +1183,8 @@ func TestJSON(t *testing.T) {
assert.Equal(t, "Nicolas Cornet", data.Copyright)
assert.Equal(t, 400, data.Height)
assert.Equal(t, 600, data.Width)
assert.Equal(t, float32(65.05558), data.Lat)
assert.Equal(t, float32(-16.625702), data.Lng)
assert.Equal(t, float32(65.05558), float32(data.Lat))
assert.Equal(t, float32(-16.625702), float32(data.Lng))
assert.Equal(t, 30, clean.Altitude(data.Altitude))
assert.Equal(t, "1/8", data.Exposure)
assert.Equal(t, "NIKON CORPORATION", data.CameraMake)
@@ -1217,8 +1217,8 @@ func TestJSON(t *testing.T) {
assert.Equal(t, 1917, data.Width)
assert.Equal(t, 34, data.Frames)
assert.Equal(t, "49.5s", data.Duration.String())
assert.Equal(t, float32(0), data.Lat)
assert.Equal(t, float32(0), data.Lng)
assert.Equal(t, 0.0, data.Lat)
assert.Equal(t, 0.0, data.Lng)
assert.Equal(t, 0.0, data.Altitude)
assert.Equal(t, "", data.Exposure)
assert.Equal(t, "", data.CameraMake)
@@ -1265,8 +1265,8 @@ func TestJSON(t *testing.T) {
assert.Equal(t, 4032, data.Width)
assert.Equal(t, 3024, data.Height)
assert.Equal(t, 6, data.Orientation)
assert.Equal(t, float32(0), data.Lat)
assert.Equal(t, float32(0), data.Lng)
assert.Equal(t, 0.0, data.Lat)
assert.Equal(t, 0.0, data.Lng)
assert.Equal(t, "Apple", data.CameraMake)
assert.Equal(t, "iPhone 6s", data.CameraModel)
assert.Equal(t, "iPhone 6s back camera 4.15mm f/2.2", data.LensModel)
@@ -1290,8 +1290,8 @@ func TestJSON(t *testing.T) {
assert.Equal(t, 4032, data.Width)
assert.Equal(t, 3024, data.Height)
assert.Equal(t, 1, data.Orientation)
assert.Equal(t, float32(0), data.Lat)
assert.Equal(t, float32(0), data.Lng)
assert.Equal(t, 0.0, data.Lat)
assert.Equal(t, 0.0, data.Lng)
assert.Equal(t, "Apple", data.CameraMake)
assert.Equal(t, "iPhone 14 Pro Max", data.CameraModel)
assert.Equal(t, "iPhone 14 Pro Max back triple camera 9mm f/2.8", data.LensModel)
@@ -1312,8 +1312,8 @@ func TestJSON(t *testing.T) {
assert.Equal(t, "2022-04-24 02:35:53 +0000 UTC", data.TakenAt.String())
assert.Equal(t, "Asia/Shanghai", data.TimeZone) // Local Time
assert.Equal(t, 1, data.Orientation)
assert.Equal(t, float32(33.640007), data.Lat)
assert.Equal(t, float32(103.48), data.Lng)
assert.Equal(t, float32(33.640007), float32(data.Lat))
assert.Equal(t, float32(103.48), float32(data.Lng))
assert.Equal(t, 0.0, data.Altitude)
})
@@ -1325,8 +1325,8 @@ func TestJSON(t *testing.T) {
}
assert.Equal(t, "", data.LensModel)
assert.Equal(t, float32(45.75285), data.Lat)
assert.Equal(t, float32(33.221977), data.Lng)
assert.Equal(t, float32(45.75285), float32(data.Lat))
assert.Equal(t, float32(33.221977), float32(data.Lng))
assert.InEpsilon(t, 4294967284, data.Altitude, 1000)
assert.Equal(t, 0, clean.Altitude(data.Altitude))
})
@@ -1346,8 +1346,8 @@ func TestJSON(t *testing.T) {
assert.Equal(t, 568000000, data.TakenNs)
assert.Equal(t, "UTC+2", data.TimeZone)
assert.Equal(t, "+02:00", data.TimeOffset)
assert.Equal(t, float32(0), data.Lat)
assert.Equal(t, float32(0), data.Lng)
assert.Equal(t, 0.0, data.Lat)
assert.Equal(t, 0.0, data.Lng)
assert.Equal(t, "Apple", data.CameraMake)
assert.Equal(t, "iPhone 13", data.CameraModel)
assert.Equal(t, "iPhone 13 back dual wide camera 5.1mm f/1.6", data.LensModel)

View File

@@ -71,8 +71,8 @@ func TestMediaFile_HEIC(t *testing.T) {
assert.Equal(t, "1/4000", jpegInfo.Exposure)
assert.Equal(t, float32(1.696), jpegInfo.Aperture)
assert.Equal(t, 20, jpegInfo.Iso)
assert.Equal(t, float32(34.79745), jpegInfo.Lat)
assert.Equal(t, float32(134.76463), jpegInfo.Lng)
assert.Equal(t, float32(34.79745), float32(jpegInfo.Lat))
assert.Equal(t, float32(134.76463), float32(jpegInfo.Lng))
assert.Equal(t, 0.0, jpegInfo.Altitude)
assert.Equal(t, 4032, jpegInfo.Width)
assert.Equal(t, 3024, jpegInfo.Height)
@@ -132,8 +132,8 @@ func TestMediaFile_HEIC(t *testing.T) {
assert.Equal(t, "1/60", jpegInfo.Exposure)
assert.Equal(t, float32(2.275), jpegInfo.Aperture)
assert.Equal(t, 400, jpegInfo.Iso)
assert.Equal(t, float32(52.459606), jpegInfo.Lat)
assert.Equal(t, float32(13.321841), jpegInfo.Lng)
assert.InEpsilon(t, 52.459605, jpegInfo.Lat, 0.0001)
assert.InEpsilon(t, 13.3218416, jpegInfo.Lng, 0.0001)
assert.Equal(t, 50.0, jpegInfo.Altitude)
assert.Equal(t, 3024, jpegInfo.Width)
assert.Equal(t, 4032, jpegInfo.Height)

View File

@@ -185,8 +185,8 @@ func TestMediaFile_Exif_JPEG(t *testing.T) {
assert.Equal(t, float32(6.644), data.Aperture)
assert.Equal(t, float32(10), data.FNumber)
assert.Equal(t, 200, data.Iso)
assert.Equal(t, float32(-33.45347), data.Lat)
assert.Equal(t, float32(25.764645), data.Lng)
assert.Equal(t, float32(-33.45347), float32(data.Lat))
assert.Equal(t, float32(25.764645), float32(data.Lng))
assert.Equal(t, 190.0, data.Altitude)
assert.Equal(t, 497, data.Width)
assert.Equal(t, 331, data.Height)
@@ -267,8 +267,8 @@ func TestMediaFile_Exif_JPEG(t *testing.T) {
assert.Equal(t, "", data.Copyright)
assert.Equal(t, 3600, data.Height)
assert.Equal(t, 7200, data.Width)
assert.Equal(t, float32(59.84083), data.Lat)
assert.Equal(t, float32(30.51), data.Lng)
assert.Equal(t, float32(59.84083), float32(data.Lat))
assert.Equal(t, float32(30.51), float32(data.Lng))
assert.Equal(t, 0.0, data.Altitude)
assert.Equal(t, "1/1250", data.Exposure)
assert.Equal(t, "SAMSUNG", data.CameraMake)
@@ -304,8 +304,8 @@ func TestMediaFile_Exif_JPEG(t *testing.T) {
assert.Equal(t, "", data.Copyright)
assert.Equal(t, 375, data.Height)
assert.Equal(t, 500, data.Width)
assert.Equal(t, float32(52.46052), data.Lat)
assert.Equal(t, float32(13.331402), data.Lng)
assert.Equal(t, float32(52.46052), float32(data.Lat))
assert.Equal(t, float32(13.331402), float32(data.Lng))
assert.Equal(t, 84.0, data.Altitude)
assert.Equal(t, "1/50", data.Exposure)
assert.Equal(t, "HUAWEI", data.CameraMake)
@@ -347,8 +347,8 @@ func TestMediaFile_Exif_DNG(t *testing.T) {
assert.Equal(t, "1/60", info.Exposure)
assert.Equal(t, float32(4.971), info.Aperture)
assert.Equal(t, 1000, info.Iso)
assert.Equal(t, float32(0), info.Lat)
assert.Equal(t, float32(0), info.Lng)
assert.Equal(t, 0.0, info.Lat)
assert.Equal(t, 0.0, info.Lng)
assert.Equal(t, 0.0, info.Altitude)
assert.Equal(t, false, info.Flash)
assert.Equal(t, "", info.Description)

View File

@@ -2,25 +2,24 @@ package clean
import (
"fmt"
"math"
"strings"
"github.com/photoprism/photoprism/pkg/geo"
"github.com/photoprism/photoprism/pkg/txt"
)
// gpsCeil converts a GPS coordinate to a rounded float32 for use in queries.
func gpsCeil(f float64) float32 {
return float32((math.Ceil(f*10000) / 10000) + 0.0001)
// GPSBoundsDefaultPadding specifies the default padding of the GPS coordinates in meters.
const GPSBoundsDefaultPadding = 5.0
// GPSBounds parses the GPS bounds (Lat N, Lng E, Lat S, Lng W)
// and returns the coordinates with default padding.
func GPSBounds(bounds string) (latN, lngE, latS, lngW float64, err error) {
return GPSBoundsWithPadding(bounds, GPSBoundsDefaultPadding)
}
// gpsFloor converts a GPS coordinate to a rounded float32 for use in queries.
func gpsFloor(f float64) float32 {
return float32((math.Floor(f*10000) / 10000) - 0.0001)
}
// GPSBounds parses the GPS bounds (Lat N, Lng E, Lat S, Lng W) and returns the coordinates if any.
func GPSBounds(bounds string) (latN, lngE, latS, lngW float32, err error) {
// GPSBoundsWithPadding parses the GPS bounds (Lat N, Lng E, Lat S, Lng W)
// and returns the coordinates with a custom padding in meters.
func GPSBoundsWithPadding(bounds string, padding float64) (latN, lngE, latS, lngW float64, err error) {
// Bounds string not long enough?
if len(bounds) < 7 {
return 0, 0, 0, 0, fmt.Errorf("no coordinates found")
@@ -29,19 +28,19 @@ func GPSBounds(bounds string) (latN, lngE, latS, lngW float32, err error) {
// Trim whitespace and invalid characters.
bounds = strings.Trim(bounds, " |\\<>\n\r\t\"'#$%!^*()[]{}")
// Split string into values.
// Split bounding box string into coordinate values.
values := strings.SplitN(bounds, ",", 5)
found := len(values)
// Invalid number of values?
// Return error if number of coordinates is invalid.
if found != 4 {
return 0, 0, 0, 0, fmt.Errorf("invalid number of coordinates")
}
// Parse floating point coordinates.
// Convert coordinate strings to floating point values.
latNorth, lngEast, latSouth, lngWest := txt.Float(values[0]), txt.Float(values[1]), txt.Float(values[2]), txt.Float(values[3])
// Latitudes (from +90 to -90 degrees).
// Latitudes have a valid range of +90 to -90 degrees.
if latNorth > 90 {
latNorth = 90
} else if latNorth < -90 {
@@ -54,12 +53,12 @@ func GPSBounds(bounds string) (latN, lngE, latS, lngW float32, err error) {
latSouth = -90
}
// latSouth must be smaller.
// Make sure latSouth is smaller than latNorth.
if latSouth > latNorth {
latNorth, latSouth = latSouth, latNorth
}
// Longitudes (from -180 to +180 degrees).
// Longitudes have a valid range of -180 to +180 degrees.
if lngEast > 180 {
lngEast = 180
} else if lngEast < -180 {
@@ -72,17 +71,20 @@ func GPSBounds(bounds string) (latN, lngE, latS, lngW float32, err error) {
lngWest = -180
}
// lngWest must be smaller.
// Make sure lngWest is smaller than lngEast.
if lngWest > lngEast {
lngEast, lngWest = lngWest, lngEast
}
// Return rounded coordinates.
return gpsCeil(latNorth), gpsCeil(lngEast), gpsFloor(latSouth), gpsFloor(lngWest), nil
// Calculate the latitude and longitude padding in degrees.
dLat, dLng := geo.Deg((latNorth+latSouth)/2.0, padding)
// Return the coordinates of the bounding box with padding applied.
return latNorth + dLat, lngEast + dLng, latSouth - dLat, lngWest - dLng, nil
}
// GPSLatRange returns a range based on the specified latitude and distance in km, or an error otherwise.
func GPSLatRange(lat float64, km float64) (latN, latS float32, err error) {
func GPSLatRange(lat float64, km float64) (latN, latS float64, err error) {
// Latitude (from +90 to -90 degrees).
if lat == 0 || lat < -90 || lat > 90 {
return 0, 0, fmt.Errorf("invalid latitude")
@@ -93,8 +95,10 @@ func GPSLatRange(lat float64, km float64) (latN, latS float32, err error) {
// Approximate longitude range,
// see https://en.wikipedia.org/wiki/Decimal_degrees
latN = gpsCeil(lat + geo.Deg(r))
latS = gpsFloor(lat - geo.Deg(r))
dLat, _ := geo.DegKm(lat, r)
latN = lat + dLat
latS = lat - dLat
if latN > 90 {
latN = 90
@@ -108,7 +112,7 @@ func GPSLatRange(lat float64, km float64) (latN, latS float32, err error) {
}
// GPSLngRange returns a range based on the specified longitude and distance in km, or an error otherwise.
func GPSLngRange(lng float64, km float64) (lngE, lngW float32, err error) {
func GPSLngRange(lat, lng float64, km float64) (lngE, lngW float64, err error) {
// Longitude (from -180 to +180 degrees).
if lng == 0 || lng < -180 || lng > 180 {
return 0, 0, fmt.Errorf("invalid longitude")
@@ -119,8 +123,10 @@ func GPSLngRange(lng float64, km float64) (lngE, lngW float32, err error) {
// Approximate longitude range,
// see https://en.wikipedia.org/wiki/Decimal_degrees
lngE = gpsCeil(lng + geo.Deg(r))
lngW = gpsFloor(lng - geo.Deg(r))
_, dLng := geo.DegKm(lat, r)
lngE = lng + dLng
lngW = lng - dLng
if lngE > 180 {
lngE = 180

View File

@@ -6,77 +6,97 @@ import (
"github.com/stretchr/testify/assert"
)
func TestGPSBoundsWithPadding(t *testing.T) {
t.Run("Valid", func(t *testing.T) {
latNorth, lngEast, latSouth, lngWest, err := GPSBoundsWithPadding("41.87760543823242,-87.62521362304688,41.89404296875,-87.6215591430664", 1000)
assert.InEpsilon(t, 41.903036, latNorth, 0.00001)
assert.InEpsilon(t, -87.609479, lngEast, 0.00001)
assert.InEpsilon(t, 41.868612, latSouth, 0.00001)
assert.InEpsilon(t, -87.637294, lngWest, 0.00001)
assert.NoError(t, err)
})
}
func TestGPSBounds(t *testing.T) {
t.Run("Valid", func(t *testing.T) {
latNorth, lngEast, latSouth, lngWest, err := GPSBounds("41.87760543823242,-87.62521362304688,41.89404296875,-87.6215591430664")
assert.Equal(t, float32(41.8942), latNorth)
assert.Equal(t, float32(41.8775), latSouth)
assert.Equal(t, float32(-87.6254), lngWest)
assert.Equal(t, float32(-87.6214), lngEast)
assert.InEpsilon(t, 41.8942, latNorth, 0.00001)
assert.InEpsilon(t, -87.6214, lngEast, 0.00001)
assert.InEpsilon(t, 41.8775, latSouth, 0.00001)
assert.InEpsilon(t, -87.6254, lngWest, 0.00001)
assert.NoError(t, err)
})
t.Run("China", func(t *testing.T) {
// Actual postion: Lat 39.8922, Lng 116.315
latNorth, lngEast, latSouth, lngWest, err := GPSBounds("39.8922004699707,116.31500244140625,39.8922004699707,116.31500244140625")
assert.InEpsilon(t, 39.8924, latNorth, 0.00001)
assert.InEpsilon(t, 116.3152, lngEast, 0.00001)
assert.InEpsilon(t, 39.8921, latSouth, 0.00001)
assert.InEpsilon(t, 116.3149, lngWest, 0.00001)
assert.NoError(t, err)
})
t.Run("FlippedLat", func(t *testing.T) {
latNorth, lngEast, latSouth, lngWest, err := GPSBounds("41.89404296875,-87.62521362304688,41.87760543823242,-87.6215591430664")
assert.Equal(t, float32(41.8942), latNorth)
assert.Equal(t, float32(41.8775), latSouth)
assert.Equal(t, float32(-87.6254), lngWest)
assert.Equal(t, float32(-87.6214), lngEast)
assert.InEpsilon(t, 41.8942, latNorth, 0.00001)
assert.InEpsilon(t, -87.6214, lngEast, 0.00001)
assert.InEpsilon(t, 41.8775, latSouth, 0.00001)
assert.InEpsilon(t, -87.6254, lngWest, 0.00001)
assert.NoError(t, err)
})
t.Run("FlippedLng", func(t *testing.T) {
latNorth, lngEast, latSouth, lngWest, err := GPSBounds("41.87760543823242,-87.6215591430664,41.89404296875,-87.62521362304688")
assert.Equal(t, float32(41.8942), latNorth)
assert.Equal(t, float32(41.8775), latSouth)
assert.Equal(t, float32(-87.6254), lngWest)
assert.Equal(t, float32(-87.6214), lngEast)
assert.InEpsilon(t, 41.8942, latNorth, 0.00001)
assert.InEpsilon(t, -87.6214, lngEast, 0.00001)
assert.InEpsilon(t, 41.8775, latSouth, 0.00001)
assert.InEpsilon(t, -87.6254, lngWest, 0.00001)
assert.NoError(t, err)
})
t.Run("Empty", func(t *testing.T) {
latNorth, lngEast, latSouth, lngWest, err := GPSBounds("")
assert.Equal(t, float32(0), latNorth)
assert.Equal(t, float32(0), lngEast)
assert.Equal(t, float32(0), latSouth)
assert.Equal(t, float32(0), lngWest)
assert.Equal(t, 0.0, latNorth)
assert.Equal(t, 0.0, lngEast)
assert.Equal(t, 0.0, latSouth)
assert.Equal(t, 0.0, lngWest)
assert.Error(t, err)
})
t.Run("One", func(t *testing.T) {
latNorth, lngEast, latSouth, lngWest, err := GPSBounds("41.87760543823242")
assert.Equal(t, float32(0), latNorth)
assert.Equal(t, float32(0), lngEast)
assert.Equal(t, float32(0), latSouth)
assert.Equal(t, float32(0), lngWest)
assert.Equal(t, 0.0, latNorth)
assert.Equal(t, 0.0, lngEast)
assert.Equal(t, 0.0, latSouth)
assert.Equal(t, 0.0, lngWest)
assert.Error(t, err)
})
t.Run("Three", func(t *testing.T) {
latNorth, lngEast, latSouth, lngWest, err := GPSBounds("41.87760543823242,-87.62521362304688,41.89404296875")
assert.Equal(t, float32(0), latNorth)
assert.Equal(t, float32(0), lngEast)
assert.Equal(t, float32(0), latSouth)
assert.Equal(t, float32(0), lngWest)
assert.Equal(t, 0.0, latNorth)
assert.Equal(t, 0.0, lngEast)
assert.Equal(t, 0.0, latSouth)
assert.Equal(t, 0.0, lngWest)
assert.Error(t, err)
})
t.Run("Five", func(t *testing.T) {
latNorth, lngEast, latSouth, lngWest, err := GPSBounds("41.87760543823242,-87.62521362304688,41.89404296875,-87.6215591430664,41.89404296875")
assert.Equal(t, float32(0), latNorth)
assert.Equal(t, float32(0), lngEast)
assert.Equal(t, float32(0), latSouth)
assert.Equal(t, float32(0), lngWest)
assert.Equal(t, 0.0, latNorth)
assert.Equal(t, 0.0, lngEast)
assert.Equal(t, 0.0, latSouth)
assert.Equal(t, 0.0, lngWest)
assert.Error(t, err)
})
t.Run("Invalid", func(t *testing.T) {
latNorth, lngEast, latSouth, lngWest, err := GPSBounds("95.87760543823242,-197.62521362304688,98.89404296875,-197.6215591430664")
assert.Equal(t, float32(90.0001), latNorth)
assert.Equal(t, float32(89.9999), latSouth)
assert.Equal(t, float32(-180.0001), lngWest)
assert.Equal(t, float32(-179.9999), lngEast)
assert.InEpsilon(t, 90.000045, latNorth, 0.00001)
assert.InEpsilon(t, -179.974236, lngEast, 0.00001)
assert.InEpsilon(t, 89.999955, latSouth, 0.00001)
assert.InEpsilon(t, -180.025764, lngWest, 0.00001)
assert.NoError(t, err)
})
t.Run("Invalid2", func(t *testing.T) {
latNorth, lngEast, latSouth, lngWest, err := GPSBounds("-95.87760543823242,197.62521362304688,-98.89404296875,197.6215591430664")
assert.Equal(t, float32(-89.9999), latNorth)
assert.Equal(t, float32(-90.0001), latSouth)
assert.Equal(t, float32(179.9999), lngWest)
assert.Equal(t, float32(180.0001), lngEast)
assert.InEpsilon(t, -89.999955, latNorth, 0.00001)
assert.InEpsilon(t, 180.025764, lngEast, 0.00001)
assert.InEpsilon(t, -90.000045, latSouth, 0.00001)
assert.InEpsilon(t, 179.974236, lngWest, 0.00001)
assert.NoError(t, err)
})
}
@@ -84,29 +104,41 @@ func TestGPSBounds(t *testing.T) {
func TestGPSLatRange(t *testing.T) {
t.Run("Valid", func(t *testing.T) {
latNorth, latSouth, err := GPSLatRange(41.87760543823242, 2)
assert.Equal(t, float32(41.8913), latNorth)
assert.Equal(t, float32(41.8639), latSouth)
assert.Equal(t, float32(41.891094), float32(latNorth))
assert.Equal(t, float32(41.864117), float32(latSouth))
assert.NoError(t, err)
})
t.Run("Zero", func(t *testing.T) {
latNorth, latSouth, err := GPSLatRange(0, 2)
assert.Equal(t, float32(0), latNorth)
assert.Equal(t, float32(0), latSouth)
assert.Equal(t, 0.0, latNorth)
assert.Equal(t, 0.0, latSouth)
assert.Error(t, err)
})
}
func TestGPSLngRange(t *testing.T) {
t.Run("Valid", func(t *testing.T) {
lngEast, lngWest, err := GPSLngRange(-87.62521362304688, 2)
assert.Equal(t, float32(-87.6389), lngWest)
assert.Equal(t, float32(-87.6116), lngEast)
t.Run("Lat0", func(t *testing.T) {
lngEast, lngWest, err := GPSLngRange(0.0, -87.62521362304688, 2)
assert.Equal(t, float32(-87.6387), float32(lngWest))
assert.Equal(t, float32(-87.611725), float32(lngEast))
assert.NoError(t, err)
})
t.Run("Lat45", func(t *testing.T) {
lngEast, lngWest, err := GPSLngRange(45.0, -87.62521362304688, 2)
assert.Equal(t, float32(-87.644295), float32(lngWest))
assert.Equal(t, float32(-87.60613), float32(lngEast))
assert.NoError(t, err)
})
t.Run("Lat67", func(t *testing.T) {
lngEast, lngWest, err := GPSLngRange(67.0, -87.62521362304688, 2)
assert.Equal(t, float32(-87.65974), float32(lngWest))
assert.Equal(t, float32(-87.59069), float32(lngEast))
assert.NoError(t, err)
})
t.Run("Zero", func(t *testing.T) {
lngEast, lngWest, err := GPSLngRange(0, 2)
assert.Equal(t, float32(0), lngEast)
assert.Equal(t, float32(0), lngWest)
lngEast, lngWest, err := GPSLngRange(0, 0, 2)
assert.Equal(t, 0.0, lngEast)
assert.Equal(t, 0.0, lngWest)
assert.Error(t, err)
})
}

View File

@@ -10,10 +10,35 @@ const (
DefaultDist float64 = 2
)
// Deg returns the approximate distance in decimal degrees,
// see https://en.wikipedia.org/wiki/Decimal_degrees.
func Deg(km float64) float64 {
return 0.009009009 * km
// Deg returns the distance in decimal degrees based on the specified distance in meters and the latitude,
// see https://en.wikipedia.org/wiki/Decimal_degrees#Precision.
func Deg(lat, meter float64) (dLat, dLng float64) {
if meter <= 0.0 {
return 0, 0
}
// Calculate latitude distance in degrees.
dLat = (meter / AverageEarthRadiusMeter) * (180.0 / math.Pi)
// Do not calculate the exact longitude distance in
// degrees if the latitude is zero or out of range.
if lat == 0.0 {
return dLat, dLat
} else if lat < -89.9 {
lat = -89.9
} else if lat > 89.9 {
lat = 89.9
}
// Calculate longitude distance in degrees.
dLng = (meter / AverageEarthRadiusMeter) * (180.0 / math.Pi) / math.Cos(lat*math.Pi/180.0)
return dLat, dLng
}
// DegKm returns the distance in decimal degrees based on the specified distance in kilometers and the latitude.
func DegKm(lat, km float64) (dLat, dLng float64) {
return Deg(lat, km*1000.0)
}
// DegToRad converts a value from degrees to radians.
@@ -40,5 +65,5 @@ func Km(p, q Position) (km float64) {
c := 2 * math.Atan2(math.Sqrt(a), math.Sqrt(1-a))
return c * EarthRadiusKm
return c * AverageEarthRadiusKm
}

View File

@@ -7,8 +7,76 @@ import (
)
func TestDeg(t *testing.T) {
t.Run("10km", func(t *testing.T) {
assert.Equal(t, 0.09009009, Deg(10))
t.Run("Lat0", func(t *testing.T) {
dLat, dLng := Deg(0.0, 11.1)
t.Logf("dLat: %f, dLng: %f", dLat, dLng)
assert.InEpsilon(t, 0.000100, dLat, 0.01)
assert.InEpsilon(t, 0.000100, dLng, 0.01)
})
t.Run("Lat23", func(t *testing.T) {
dLat, dLng := Deg(23.0, 11.1)
t.Logf("dLat: %f, dLng: %f", dLat, dLng)
assert.InEpsilon(t, 0.000100, dLat, 0.01)
assert.InEpsilon(t, 0.000108, dLng, 0.01)
})
t.Run("Lat45", func(t *testing.T) {
dLat, dLng := Deg(45.0, 11.1)
t.Logf("dLat: %f, dLng: %f", dLat, dLng)
assert.InEpsilon(t, 0.000100, dLat, 0.01)
assert.InEpsilon(t, 0.000141, dLng, 0.01)
})
t.Run("Lat67", func(t *testing.T) {
dLat, dLng := Deg(67.0, 11.1)
t.Logf("dLat: %f, dLng: %f", dLat, dLng)
assert.InEpsilon(t, 0.000100, dLat, 0.01)
assert.InEpsilon(t, 0.000255, dLng, 0.01)
})
t.Run("ZeroMeters", func(t *testing.T) {
dLat, dLng := Deg(50.0, 0.0)
t.Logf("dLat: %f, dLng: %f", dLat, dLng)
assert.Equal(t, 0.0, dLat)
assert.Equal(t, 0.0, dLng)
})
t.Run("LatOutOfRange", func(t *testing.T) {
dLat, dLng := Deg(123.0, 11.1)
t.Logf("dLat: %f, dLng: %f", dLat, dLng)
assert.InEpsilon(t, 0.000100, dLat, 0.01)
assert.InEpsilon(t, 0.057195, dLng, 0.01)
dLat, dLng = Deg(-600.0, 11.1)
t.Logf("dLat: %f, dLng: %f", dLat, dLng)
assert.InEpsilon(t, 0.000100, dLat, 0.01)
assert.InEpsilon(t, 0.057195, dLng, 0.01)
dLat, dLng = Deg(-600.0, 11.1)
t.Logf("dLat: %f, dLng: %f", dLat, dLng)
assert.InEpsilon(t, 0.000100, dLat, 0.01)
assert.InEpsilon(t, 0.057195, dLng, 0.01)
})
}
func TestDegKm(t *testing.T) {
t.Run("Lat0", func(t *testing.T) {
dLat, dLng := DegKm(0.0, 0.0111)
t.Logf("dLat: %f, dLng: %f", dLat, dLng)
assert.InEpsilon(t, 0.000100, dLat, 0.01)
assert.InEpsilon(t, 0.000100, dLng, 0.01)
})
t.Run("Lat23", func(t *testing.T) {
dLat, dLng := DegKm(23.0, 0.0111)
t.Logf("dLat: %f, dLng: %f", dLat, dLng)
assert.InEpsilon(t, 0.000100, dLat, 0.01)
assert.InEpsilon(t, 0.000108, dLng, 0.01)
})
t.Run("Lat45", func(t *testing.T) {
dLat, dLng := DegKm(45.0, 0.0111)
t.Logf("dLat: %f, dLng: %f", dLat, dLng)
assert.InEpsilon(t, 0.000100, dLat, 0.01)
assert.InEpsilon(t, 0.000141, dLng, 0.01)
})
t.Run("Lat67", func(t *testing.T) {
dLat, dLng := DegKm(67.0, 0.0111)
t.Logf("dLat: %f, dLng: %f", dLat, dLng)
assert.InEpsilon(t, 0.000100, dLat, 0.01)
assert.InEpsilon(t, 0.000255, dLng, 0.01)
})
}

View File

@@ -25,5 +25,8 @@ Additional information can be found in our Developer Guide:
package geo
const (
EarthRadiusKm = 6371 // Earth radius in km
AverageEarthRadiusKm = 6371.0 // Global-average earth radius in km
AverageEarthRadiusMeter = AverageEarthRadiusKm * 1000.0 // Global-average earth radius in m
WGS84EarthRadiusKm = 6378.137 // WGS84 earth radius in km
WGS84EarthRadiusMeter = WGS84EarthRadiusKm * 1000.0 // WGS84 earth radius in m
)