diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 59fcdb5..a8060dd 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -31,6 +31,12 @@ jobs: with: node-version: '16' + - name: Install just + uses: extractions/setup-just@v1 + + - name: Download assets + run: just assets + - name: Login to GitHub Container Registry uses: docker/login-action@v2 with: diff --git a/.goreleaser.yml b/.goreleaser.yml index 684cba9..89e0f56 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -19,6 +19,7 @@ builds: goarch: "386" tags: - embedstatic + - embedgeo dockers: - dockerfile: Dockerfile-goreleaser image_templates: diff --git a/Dockerfile b/Dockerfile index 3060cc2..92a247f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -33,9 +33,10 @@ COPY internal ./internal COPY io ./io COPY db ./db COPY fonts ./fonts +COPY data/geo ./data/geo # RUN go install -tags libjpeg . COPY --from=node-builder /ui/dist/ ./ui/dist -RUN go install -tags embedstatic . +RUN go install -tags embedstatic,embedgeo . diff --git a/README.md b/README.md index 9108344..9e7e775 100644 --- a/README.md +++ b/README.md @@ -116,11 +116,11 @@ layouts. * [ ] **Face recognition**. Photos could be automatically tagged with the person's name. This would be a great way to search for photos of a specific person. + * **Reverse geolocation**. Local, embedded reverse geolocation via [tinygpkg]. + Does not need any API calls, has negligible performance impact, and supports + ~50 thousand places. Currently only supported for photos with GPS coordinates + in the EXIF data and the Timeline view. - - - Persistent photo - selection is also implemented using tags. Tags are * **Flexible media/thumbnail system**. Do you have hundreds of gigabytes of existing thumbnails from an existing system? Me too! Let's reuse those. Don't have any? No worries, they will be generated automatically to speed up display. Here are @@ -383,6 +383,8 @@ Distributed under the MIT License. See `LICENSE` for more information. ## Acknowledgements * [Open Images Dataset][open-images-dataset] +* [geoBoundaries](https://www.geoboundaries.org/) for geographic boundary data used for reverse geolocation +* [sams96/rgeo](https://github.com/sams96/rgeo) for previous reverse geolocation implementation and inspiration * [Best-README-Template](https://github.com/othneildrew/Best-README-Template) * [readme.so](https://readme.so/) @@ -409,3 +411,4 @@ Distributed under the MIT License. See `LICENSE` for more information. [Vue 3]: https://v3.vuejs.org/ [BalmUI]: https://next-material.balmjs.com/ [photofield-ai]: https://github.com/smilyorg/photofield-ai +[tinygpkg]: https://github.com/smilyorg/tinygpkg diff --git a/defaults.yaml b/defaults.yaml index 5095719..3096c9f 100644 --- a/defaults.yaml +++ b/defaults.yaml @@ -63,7 +63,29 @@ geo: # Can delay startup by up to a minute as the local geolocation # database is loaded. # - # reverse_geocode: true + reverse_geocode: true + geopackage: + # Path to the GeoPackage file containing features + # WKB and TWKB (via tinygpkg) are supported. + # + # See https://github.com/SmilyOrg/tinygpkg-data + # + # If empty, the database embedded into the executable + # via embed-geo.go is used. + # + # path: data/geo/geoBoundariesCGAZ_ADM2_s5_twkb_p3.gpkg + + # Table in the GeoPackage file to be used in case there is + # more than one + # + # If empty, the first table is used. + # + # table: globalADM2 + + # The column to use for reverse geocoding the location name + name_col: shapeName # geoBoundaries CGAZ + # name_col: name_conve # Natural Earth urban areas + # name_col: NAME_LONG # Natural Earth countries media: # Extract metadata from this many files concurrently diff --git a/docker/grafana/dashboards/photofield.json b/docker/grafana/dashboards/photofield.json index 2f71504..6416f92 100644 --- a/docker/grafana/dashboards/photofield.json +++ b/docker/grafana/dashboards/photofield.json @@ -1474,7 +1474,8 @@ "mode": "absolute", "steps": [ { - "color": "green" + "color": "green", + "value": null }, { "color": "yellow", @@ -1540,7 +1541,8 @@ "mode": "absolute", "steps": [ { - "color": "green" + "color": "green", + "value": null }, { "color": "#EAB839", @@ -1606,7 +1608,8 @@ "mode": "absolute", "steps": [ { - "color": "green" + "color": "green", + "value": null } ] }, @@ -1682,7 +1685,8 @@ "mode": "absolute", "steps": [ { - "color": "green" + "color": "green", + "value": null }, { "color": "#EAB839", @@ -1759,7 +1763,8 @@ "mode": "absolute", "steps": [ { - "color": "green" + "color": "green", + "value": null }, { "color": "#EAB839", @@ -1829,7 +1834,8 @@ "mode": "absolute", "steps": [ { - "color": "text" + "color": "text", + "value": null }, { "color": "green", @@ -1913,7 +1919,8 @@ "mode": "absolute", "steps": [ { - "color": "green" + "color": "green", + "value": null } ] }, @@ -2526,9 +2533,14 @@ "selected": false, "text": "path", "value": "path" + }, + { + "selected": false, + "text": "geometry", + "value": "geometry" } ], - "query": "scene,image,image_info,path", + "query": "scene,image,image_info,path,geometry", "queryValue": "", "skipUrlSync": false, "type": "custom" @@ -2674,6 +2686,6 @@ "timezone": "", "title": "Photofield", "uid": "9sQ5hGGnk", - "version": 24, + "version": 29, "weekStart": "" } \ No newline at end of file diff --git a/embed-geo-stub.go b/embed-geo-stub.go new file mode 100644 index 0000000..342a4bf --- /dev/null +++ b/embed-geo-stub.go @@ -0,0 +1,8 @@ +//go:build !embedgeo +// +build !embedgeo + +package main + +import "embed" + +var GeoFs embed.FS diff --git a/embed-geo.go b/embed-geo.go new file mode 100644 index 0000000..3baddba --- /dev/null +++ b/embed-geo.go @@ -0,0 +1,10 @@ +//go:build embedgeo +// +build embedgeo + +package main + +import "embed" + +// tinygpkg-data release: v0.2.0 +//go:embed data/geo/geoBoundariesCGAZ_ADM2_s5_twkb_p3.gpkg +var GeoFs embed.FS diff --git a/static-noembed.go b/embed-static-stub.go similarity index 100% rename from static-noembed.go rename to embed-static-stub.go diff --git a/static-embed.go b/embed-static.go similarity index 100% rename from static-embed.go rename to embed-static.go diff --git a/go.mod b/go.mod index f498c1b..99c76d7 100644 --- a/go.mod +++ b/go.mod @@ -15,8 +15,9 @@ require ( github.com/go-chi/render v1.0.1 github.com/goccy/go-yaml v1.7.17 github.com/golang-migrate/migrate/v4 v4.15.0-beta.1 - github.com/golang/geo v0.0.0-20200730024412-e86565bf3f35 + github.com/golang/geo v0.0.0-20230421003525-6adc56603217 github.com/gosimple/slug v1.10.0 + github.com/grafana/pyroscope-go v1.0.4 github.com/hako/durafmt v0.0.0-20200605151348-3a43fc422dd9 github.com/imdario/mergo v0.3.13 github.com/joho/godotenv v1.3.0 @@ -26,18 +27,19 @@ require ( github.com/matoous/go-nanoid/v2 v2.0.0 github.com/mostlygeek/go-exiftool v0.0.0-20190130212521-a0e5de16f760 github.com/petar/GoLLRB v0.0.0-20210522233825-ae3b015fd3e9 + github.com/peterstace/simplefeatures v0.44.0 github.com/pixiv/go-libjpeg v0.0.0-20190822045933-3da21a74767d github.com/prometheus/client_golang v1.11.0 github.com/prometheus/client_model v0.2.0 - github.com/pyroscope-io/client v0.7.0 github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd - github.com/sams96/rgeo v1.2.0 github.com/sheerun/queue v1.0.1 + github.com/smilyorg/tinygpkg v0.2.0 github.com/tdewolff/canvas v0.0.0-20200504121106-e2600b35c365 github.com/x448/float16 v0.8.4 golang.org/x/image v0.0.0-20191214001246-9130b4cfad52 - golang.org/x/sync v0.0.0-20210220032951-036812b2e83c - zombiezen.com/go/sqlite v0.10.1 + golang.org/x/sync v0.1.0 + modernc.org/sqlite v1.21.1 + zombiezen.com/go/sqlite v0.13.0 ) require ( @@ -49,15 +51,16 @@ require ( github.com/cespare/xxhash v1.1.0 // indirect github.com/cespare/xxhash/v2 v2.1.1 // indirect github.com/dsnet/compress v0.0.1 // indirect + github.com/dustin/go-humanize v1.0.0 // indirect github.com/fatih/color v1.9.0 // indirect github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90 // indirect github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect github.com/golang/gddo v0.0.0-20210115222349-20d68f94ee1f // indirect github.com/golang/protobuf v1.5.2 // indirect - github.com/google/go-cmp v0.5.7 // indirect - github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5 // indirect + github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 // indirect github.com/google/uuid v1.3.0 // indirect github.com/gosimple/unidecode v1.0.0 // indirect + github.com/grafana/pyroscope-go/godeltaprof v0.1.4 // indirect github.com/hashicorp/errwrap v1.0.0 // indirect github.com/hashicorp/go-multierror v1.1.0 // indirect github.com/hexops/gotextdiff v1.0.3 // indirect @@ -65,35 +68,34 @@ require ( github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect github.com/lucasb-eyer/go-colorful v1.0.3 // indirect github.com/mattn/go-colorable v0.1.9 // indirect - github.com/mattn/go-isatty v0.0.16 // indirect + github.com/mattn/go-isatty v0.0.17 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect github.com/oliamb/cutter v0.2.2 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/prometheus/common v0.26.0 // indirect github.com/prometheus/procfs v0.6.0 // indirect - github.com/pyroscope-io/godeltaprof v0.1.0 // indirect - github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect + github.com/stretchr/testify v1.8.1 // indirect github.com/tdewolff/minify/v2 v2.7.1-0.20200112204046-70870d25a935 // indirect github.com/tdewolff/parse/v2 v2.4.2 // indirect - github.com/twpayne/go-geom v1.4.4 // indirect github.com/wcharczuk/go-chart v2.0.2-0.20191206192251-962b9abdec2b+incompatible // indirect go.uber.org/atomic v1.7.0 // indirect golang.org/x/mod v0.4.2 // indirect - golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab // indirect + golang.org/x/sys v0.5.0 // indirect golang.org/x/text v0.3.7 // indirect golang.org/x/tools v0.1.2 // indirect golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect gonum.org/v1/plot v0.0.0-20190410204940-3a5f52653745 // indirect google.golang.org/protobuf v1.26.0 // indirect - lukechampine.com/uint128 v1.1.1 // indirect - modernc.org/cc/v3 v3.38.1 // indirect - modernc.org/ccgo/v3 v3.16.9 // indirect - modernc.org/libc v1.19.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + lukechampine.com/uint128 v1.2.0 // indirect + modernc.org/cc/v3 v3.40.0 // indirect + modernc.org/ccgo/v3 v3.16.13 // indirect + modernc.org/libc v1.22.3 // indirect modernc.org/mathutil v1.5.0 // indirect - modernc.org/memory v1.4.0 // indirect + modernc.org/memory v1.5.0 // indirect modernc.org/opt v0.1.3 // indirect - modernc.org/sqlite v1.19.1 // indirect modernc.org/strutil v1.1.3 // indirect modernc.org/token v1.0.1 // indirect ) diff --git a/go.sum b/go.sum index ddd65a8..b9afb93 100644 --- a/go.sum +++ b/go.sum @@ -37,7 +37,6 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -crawshaw.io/iox v0.0.0-20181124134642-c51c3df30797/go.mod h1:sXBiorCo8c46JlQV3oXPKINnZ8mcqnye1EkVkqsectk= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k= github.com/Azure/azure-storage-blob-go v0.13.0/go.mod h1:pA9kNqtjUeQF2zOSu4s//nUdBD+e64lEuc4sVnuOfNs= @@ -209,7 +208,6 @@ github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.6.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg= github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0= github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY= github.com/gobuffalo/depgen v0.1.0/go.mod h1:+ifsuy7fhi15RWncXQQKjWS9JPkdah5sZvtHc2RXGlg= @@ -250,8 +248,8 @@ github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF0 github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/gddo v0.0.0-20210115222349-20d68f94ee1f h1:16RtHeWGkJMc80Etb8RPCcKevXGldr57+LOyZt8zOlg= github.com/golang/gddo v0.0.0-20210115222349-20d68f94ee1f/go.mod h1:ijRvpgDJDI262hYq/IQVYgf8hd8IHUs93Ol0kvMBAx4= -github.com/golang/geo v0.0.0-20200730024412-e86565bf3f35 h1:enTowfyfjtomBQhxX9mhUD+0tZhpe4rIzStO4aNlou8= -github.com/golang/geo v0.0.0-20200730024412-e86565bf3f35/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= +github.com/golang/geo v0.0.0-20230421003525-6adc56603217 h1:HKlyj6in2JV6wVkmQ4XmG/EIm+SCYlPZ+V4GWit7Z+I= +github.com/golang/geo v0.0.0-20230421003525-6adc56603217/go.mod h1:8wI0hitZ3a1IxZfeH3/5I97CI8i5cLGsYe7xNhQGs9U= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -304,8 +302,7 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= -github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-github/v35 v35.2.0/go.mod h1:s0515YVTI+IMrDoy9Y4pHt9ShGpzHvHO8rZ7L7acgvs= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -323,8 +320,9 @@ github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5 h1:zIaiqGYDQwa4HVx5wGRTXbx38Pqxjemn4BP98wpzpXo= github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ= +github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -344,6 +342,10 @@ github.com/gosimple/slug v1.10.0 h1:3XbiQua1IpCdrvuntWvGBxVm+K99wCSxJjlxkP49GGQ= github.com/gosimple/slug v1.10.0/go.mod h1:MICb3w495l9KNdZm+Xn5b6T2Hn831f9DMxiJ1r+bAjw= github.com/gosimple/unidecode v1.0.0 h1:kPdvM+qy0tnk4/BrnkrbdJ82xe88xn7c9hcaipDz4dQ= github.com/gosimple/unidecode v1.0.0/go.mod h1:CP0Cr1Y1kogOtx0bJblKzsVWrqYaqfNOnHzpgWw4Awc= +github.com/grafana/pyroscope-go v1.0.4 h1:oyQX0BOkL+iARXzHuCdIF5TQ7/sRSel1YFViMHC7Bm0= +github.com/grafana/pyroscope-go v1.0.4/go.mod h1:0d7ftwSMBV/Awm7CCiYmHQEG8Y44Ma3YSjt+nWcWztY= +github.com/grafana/pyroscope-go/godeltaprof v0.1.4 h1:mDsJ3ngul7UfrHibGQpV66PbZ3q1T8glz/tK3bQKKEk= +github.com/grafana/pyroscope-go/godeltaprof v0.1.4/go.mod h1:1HSPtjU8vLG0jE9JrTdzjgFqdJ/VgN7fvxBNq3luJko= github.com/gregjones/httpcache v0.0.0-20170920190843-316c5e0ff04e/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4= github.com/hako/durafmt v0.0.0-20200605151348-3a43fc422dd9 h1:IEhIezS5kcD4ZzOwVl8dAyJ9JCi4Xo6tg44Vj/z7UsI= @@ -501,12 +503,11 @@ github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2y github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= +github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= -github.com/mattn/go-sqlite3 v1.14.12/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= -github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI= +github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mitchellh/mapstructure v0.0.0-20170523030023-d0303fe80992/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= @@ -541,6 +542,8 @@ github.com/pelletier/go-toml v1.0.1-0.20170904195809-1d6b12b7cb29/go.mod h1:5z9K github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= github.com/petar/GoLLRB v0.0.0-20210522233825-ae3b015fd3e9 h1:1/WtZae0yGtPq+TI6+Tv1WTxkukpXeMlviSxvL7SRgk= github.com/petar/GoLLRB v0.0.0-20210522233825-ae3b015fd3e9/go.mod h1:x3N5drFsm2uilKKuuYo6LdyD8vZAW55sH/9w+pbo1sw= +github.com/peterstace/simplefeatures v0.44.0 h1:wST0EhmdLlVU8IKE24beNpjn3B0v3G1IaJcSKboI6tY= +github.com/peterstace/simplefeatures v0.44.0/go.mod h1:ub+e2WFVeYzriHxqjmSzW5yqp67KXPAgcUhtQb26ay0= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pierrec/lz4/v4 v4.1.4/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pierrec/lz4/v4 v4.1.7/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= @@ -573,14 +576,11 @@ github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsT github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/pyroscope-io/client v0.7.0 h1:LWuuqPQ1oa6x7BnmUOuo/aGwdX85QGhWZUBYWWW3zdk= -github.com/pyroscope-io/client v0.7.0/go.mod h1:4h21iOU4pUOq0prKyDlvYRL+SCKsBc5wKiEtV+rJGqU= -github.com/pyroscope-io/godeltaprof v0.1.0 h1:UBqtjt0yZi4jTxqZmLAs34XG6ycS3vUTlhEUSq4NHLE= -github.com/pyroscope-io/godeltaprof v0.1.0/go.mod h1:psMITXp90+8pFenXkKIpNhrfmI9saQnPbba27VIaiQE= github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M= github.com/remyoudompheng/bigfft v0.0.0-20190728182440-6a916e37a237/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= -github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= @@ -589,8 +589,6 @@ github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OK github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd h1:CmH9+J6ZSsIjUK3dcGsnCnO41eRBOnY12zwkn5qVwgc= github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk= -github.com/sams96/rgeo v1.2.0 h1:49cBHeXSADHGHYUsNByhzrzZXkOwYU4+/Vxv7lQn/mY= -github.com/sams96/rgeo v1.2.0/go.mod h1:ngWuABNhG1zT7afDMW5JGZHQgWeNUfPf8Ag5gaojKjA= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sheerun/queue v1.0.1 h1:TIAQyN0aRRvrJcNa2beZFfxwuxrfXBc9Mj+UWDNH7Ao= github.com/sheerun/queue v1.0.1/go.mod h1:YtjrWT5jymvCLo/lEWDk3sv7A1Kgj0qcl3SZx7Zmcfo= @@ -604,6 +602,8 @@ github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6Mwd github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/smilyorg/tinygpkg v0.2.0 h1:1yqvZG6AzLDvpR/wwqaLgGY131syuozj3DyQQLiiPyE= +github.com/smilyorg/tinygpkg v0.2.0/go.mod h1:nDvVmk7GEET7pDdkAAtFXq7RBFCoOzw/nW0xFgqbqAY= github.com/snowflakedb/gosnowflake v1.4.3/go.mod h1:1kyg2XEduwti88V11PKRHImhXLK5WpGiayY6lFNYb98= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= @@ -617,13 +617,18 @@ github.com/spf13/viper v1.0.0/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7Sr github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.0/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/tdewolff/canvas v0.0.0-20200504121106-e2600b35c365 h1:iNyEAGvN8yNc5/maTbLhStXRIqp+PSxpjG68KRtU3Y4= github.com/tdewolff/canvas v0.0.0-20200504121106-e2600b35c365/go.mod h1:DCuQBGs+Nm73wH9S/z1tlUKDbAPCGa6W7A/DHU1ENmQ= github.com/tdewolff/minify/v2 v2.7.1-0.20200112204046-70870d25a935 h1:nRG5jPGtwJpQ8KtrqhGVdLAuOnk4YWfNxh4Kx9XMuAw= @@ -633,8 +638,6 @@ github.com/tdewolff/parse/v2 v2.4.2/go.mod h1:WzaJpRSbwq++EIQHYIRTpbYKNA3gn9it1I github.com/tdewolff/test v1.0.6 h1:76mzYJQ83Op284kMT+63iCNCI7NEERsIN8dLM+RiKr4= github.com/tdewolff/test v1.0.6/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= -github.com/twpayne/go-geom v1.4.4 h1:bcCPAvvNSzjmpUqR0Uqh39ClCKtPx6kZVR7EakQaVJI= -github.com/twpayne/go-geom v1.4.4/go.mod h1:Kz4sX4LtdesDQgkhsMERazLlH/NiCg90s6FPaNr0KNI= github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= @@ -802,8 +805,9 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180224232135-f6cff0780e54/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -871,9 +875,9 @@ golang.org/x/sys v0.0.0-20210412220455-f1c623a9e750/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210521090106-6ca3eb03dfc2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab h1:2QkjZIsXupsJbJIdSjjUOgWK3aEtzyuh2mPt3l/CkeU= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1102,6 +1106,7 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gorm.io/driver/postgres v1.0.8/go.mod h1:4eOzrI1MUfm6ObJU/UcmbXyiHSs8jSwH95G5P5dxcAg= gorm.io/gorm v1.20.12/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw= gorm.io/gorm v1.21.4/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw= @@ -1113,24 +1118,17 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -lukechampine.com/uint128 v1.1.1 h1:pnxCASz787iMf+02ssImqk6OLt+Z5QHMoZyUXR4z6JU= -lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= +lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI= +lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= modernc.org/b v1.0.0/go.mod h1:uZWcZfRj1BpYzfN9JTerzlNUnnPsV9O2ZA8JsRcubNg= modernc.org/cc v1.0.0/go.mod h1:1Sk4//wdnYJiUIxnW8ddKpaOJCF37yAdqYnkxUpaYxw= modernc.org/cc/v3 v3.32.4/go.mod h1:0R6jl1aZlIl2avnYfbfHBS1QB6/f+16mihBObaBC878= -modernc.org/cc/v3 v3.36.0/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= -modernc.org/cc/v3 v3.36.2/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= -modernc.org/cc/v3 v3.38.1 h1:Yu2IiiRpustRFUgMDZKwVn2RvyJzpfYSOw7zHeKtSi4= -modernc.org/cc/v3 v3.38.1/go.mod h1:vtL+3mdHx/wcj3iEGz84rQa8vEqR6XM84v5Lcvfph20= -modernc.org/ccgo/v3 v3.0.0-20220428102840-41399a37e894/go.mod h1:eI31LL8EwEBKPpNpA4bU1/i+sKOwOrQy8D87zWUcRZc= -modernc.org/ccgo/v3 v3.0.0-20220430103911-bc99d88307be/go.mod h1:bwdAnOoaIt8Ax9YdWGjxWsdkPcZyRPHqrOvJxaKAKGw= +modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw= +modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0= modernc.org/ccgo/v3 v3.9.2/go.mod h1:gnJpy6NIVqkETT+L5zPsQFj7L2kkhfPMzOghRNv/CFo= -modernc.org/ccgo/v3 v3.16.4/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ= -modernc.org/ccgo/v3 v3.16.6/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ= -modernc.org/ccgo/v3 v3.16.9 h1:AXquSwg7GuMk11pIdw7fmO1Y/ybgazVkMhsZWCV0mHM= -modernc.org/ccgo/v3 v3.16.9/go.mod h1:zNMzC9A9xeNUepy6KuZBbugn3c0Mc9TeiJO4lgvkJDo= +modernc.org/ccgo/v3 v3.16.13 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw= +modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY= modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk= -modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ= modernc.org/db v1.0.0/go.mod h1:kYD/cO29L/29RM0hXYl4i3+Q5VojL31kTUVpVJDw0s8= modernc.org/file v1.0.0/go.mod h1:uqEokAEn1u6e+J45e54dsEA/pw4o7zLrA2GwyntZzjw= modernc.org/fileutil v1.0.0/go.mod h1:JHsWpkrk/CnVV1H/eGlFf85BEpfkrp56ro8nojIq9Q8= @@ -1138,57 +1136,45 @@ modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk= modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM= modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM= modernc.org/internal v1.0.0/go.mod h1:VUD/+JAkhCpvkUitlEOnhpVxCgsBI90oTzSCRcqQVSM= -modernc.org/libc v0.0.0-20220428101251-2d5f3daf273b/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA= modernc.org/libc v1.7.13-0.20210308123627-12f642a52bb8/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w= modernc.org/libc v1.9.5/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w= -modernc.org/libc v1.16.0/go.mod h1:N4LD6DBE9cf+Dzf9buBlzVJndKr/iJHG97vGLHYnb5A= -modernc.org/libc v1.16.1/go.mod h1:JjJE0eu4yeK7tab2n4S1w8tlWd9MxXLRzheaRnAKymU= -modernc.org/libc v1.16.7/go.mod h1:hYIV5VZczAmGZAnG15Vdngn5HSF5cSkbvfz2B7GRuVU= -modernc.org/libc v1.17.0/go.mod h1:XsgLldpP4aWlPlsjqKRdHPqCxCjISdHfM/yeWC5GyW0= -modernc.org/libc v1.19.0 h1:bXyVhGQg6KIClTr8FMVIDPl7jtbcs7aS5WP7vLDaxPs= -modernc.org/libc v1.19.0/go.mod h1:ZRfIaEkgrYgZDl6pa4W39HgN5G/yDW+NRmNKZBDFrk0= +modernc.org/libc v1.22.3 h1:D/g6O5ftAfavceqlLOFwaZuA5KYafKwmr30A6iSqoyY= +modernc.org/libc v1.22.3/go.mod h1:MQrloYP209xa2zHome2a8HLiLm6k0UT8CoHpV74tOFw= modernc.org/lldb v1.0.0/go.mod h1:jcRvJGWfCGodDZz8BPwiKMJxGJngQ/5DrRapkQnLob8= modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k= modernc.org/mathutil v1.1.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= -modernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ= modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= modernc.org/memory v1.0.4/go.mod h1:nV2OApxradM3/OVbs2/0OsP6nPfakXpi50C7dcoHXlc= -modernc.org/memory v1.1.1/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw= -modernc.org/memory v1.2.0/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw= -modernc.org/memory v1.4.0 h1:crykUfNSnMAXaOJnnxcSzbUGMqkLWjklJKkBK2nwZwk= -modernc.org/memory v1.4.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= +modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds= +modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= modernc.org/ql v1.0.0/go.mod h1:xGVyrLIatPcO2C1JvI/Co8c0sr6y91HKFNy4pt9JXEY= modernc.org/sortutil v1.1.0/go.mod h1:ZyL98OQHJgH9IEfN71VsamvJgrtRX9Dj2gX+vH86L1k= modernc.org/sqlite v1.10.6/go.mod h1:Z9FEjUtZP4qFEg6/SiADg9XCER7aYy9a/j7Pg9P7CPs= -modernc.org/sqlite v1.17.3/go.mod h1:10hPVYar9C0kfXuTWGz8s0XtB8uAGymUy51ZzStYe3k= -modernc.org/sqlite v1.19.1 h1:8xmS5oLnZtAK//vnd4aTVj8VOeTAccEFOtUnIzfSw+4= -modernc.org/sqlite v1.19.1/go.mod h1:UfQ83woKMaPW/ZBruK0T7YaFCrI+IE0LeWVY6pmnVms= +modernc.org/sqlite v1.21.1 h1:GyDFqNnESLOhwwDRaHGdp2jKLDzpyT/rNLglX3ZkMSU= +modernc.org/sqlite v1.21.1/go.mod h1:XwQ0wZPIh1iKb5mkvCJ3szzbhk+tykC8ZWqTRTgYRwI= modernc.org/strutil v1.0.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs= modernc.org/strutil v1.1.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs= -modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw= modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY= modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw= modernc.org/tcl v1.5.2/go.mod h1:pmJYOLgpiys3oI4AeAafkcUfE+TKKilminxNyU/+Zlo= -modernc.org/tcl v1.13.1/go.mod h1:XOLfOwzhkljL4itZkK6T72ckMgvj0BDsnKNdZVUOecw= -modernc.org/tcl v1.14.0 h1:cO7oyRWEXweSJmjdbs1L86P52D9QmBy/CPFKmFvNYTU= +modernc.org/tcl v1.15.1 h1:mOQwiEK4p7HruMZcwKTZPw/aqtGM4aY00uzWhlKKYws= modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= modernc.org/token v1.0.1 h1:A3qvTqOwexpfZZeyI0FeGPDlSWX5pjZu9hF4lU+EKWg= modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= modernc.org/xc v1.0.0/go.mod h1:mRNCo0bvLjGhHO9WsyuKVU4q0ceiDDDoEeWDJHrNx8I= modernc.org/z v1.0.1-0.20210308123920-1f282aa71362/go.mod h1:8/SRk5C/HgiQWCgXdfpb+1RvhORdkz5sw72d3jjtyqA= modernc.org/z v1.0.1/go.mod h1:8/SRk5C/HgiQWCgXdfpb+1RvhORdkz5sw72d3jjtyqA= -modernc.org/z v1.5.1/go.mod h1:eWFB510QWW5Th9YGZT81s+LwvaAs3Q2yr4sP0rmLkv8= -modernc.org/z v1.6.0 h1:gLwAw6aS973K/k9EOJGlofauyMk4YOUiPDYzWnq/oXo= +modernc.org/z v1.7.0 h1:xkDw/KepgEjeizO2sNco+hqYkU12taxQFqPEmgm1GWE= modernc.org/zappy v1.0.0/go.mod h1:hHe+oGahLVII/aTTyWK/b53VDHMAGCBYYeZ9sn83HC4= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/pdf v0.1.1 h1:k1MczvYDUvJBe93bYd7wrZLLUEcLZAuF824/I4e5Xr4= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= -zombiezen.com/go/sqlite v0.10.1 h1:PSgVSHeIVOGKbX7ZIQNXGKn3wcqM6JBnT4yS1OLjWbM= -zombiezen.com/go/sqlite v0.10.1/go.mod h1:tOd9u3peffVYnXOedepSJmX92n/mbqf594wcJ+29jf8= +zombiezen.com/go/sqlite v0.13.0 h1:iEeyVqcm3fk5PCA8OQBhBxPnqrP4yYuVJBF+XZpSnOE= +zombiezen.com/go/sqlite v0.13.0/go.mod h1:Ht/5Rg3Ae2hoyh1I7gbWtWAl89CNocfqeb/aAMTkJr4= diff --git a/internal/geo/cache.go b/internal/geo/cache.go new file mode 100644 index 0000000..910c786 --- /dev/null +++ b/internal/geo/cache.go @@ -0,0 +1,96 @@ +package geo + +import ( + "fmt" + "photofield/internal/metrics" + "unsafe" + + "github.com/dgraph-io/ristretto" + "github.com/peterstace/simplefeatures/geom" + "github.com/smilyorg/tinygpkg/gpkg" +) + +type Cache struct { + cache *ristretto.Cache +} + +func NewCache() (*Cache, error) { + g := &Cache{} + c, err := ristretto.NewCache(&ristretto.Config{ + NumCounters: 100000, // number of keys to track frequency of, 10x max expected key count + MaxCost: 64_000_000, // maximum size/cost of cache + BufferItems: 64, // number of keys per Get buffer. + Metrics: true, + Cost: func(value interface{}) int64 { + return estimateGeometryMemorySize(value.(geom.Geometry)) + }, + }) + if err != nil { + return nil, fmt.Errorf("failed to create geometry cache: %w", err) + } + metrics.AddRistretto("geometry_cache", c) + g.cache = c + return g, nil +} + +func (g *Cache) Get(fid gpkg.FeatureId) (geom.Geometry, error) { + v, ok := g.cache.Get(int64(fid)) + if !ok { + return geom.Geometry{}, gpkg.ErrNotFound + } + return v.(geom.Geometry), nil +} + +func (g *Cache) Set(fid gpkg.FeatureId, geom geom.Geometry) error { + g.cache.Set(int64(fid), geom, 0) + return nil +} + +func estimateGeometryMemorySize(g geom.Geometry) int64 { + switch g.Type() { + case geom.TypeGeometryCollection: + m := int64(0) + c := g.MustAsGeometryCollection() + for i := 0; i < c.NumGeometries(); i++ { + m += estimateGeometryMemorySize(c.GeometryN(i)) + } + return m + case geom.TypePoint: + return int64(unsafe.Sizeof(geom.Point{})) + case geom.TypeLineString: + l := g.MustAsLineString() + c := l.Coordinates() + return int64(c.Length()*c.CoordinatesType().Dimension()*8) + int64(unsafe.Sizeof(l)) + case geom.TypePolygon: + p := g.MustAsPolygon() + m := int64(0) + m += estimateGeometryMemorySize(p.ExteriorRing().AsGeometry()) + for i := 0; i < p.NumInteriorRings(); i++ { + m += estimateGeometryMemorySize(p.InteriorRingN(i).AsGeometry()) + } + return m + int64(unsafe.Sizeof(p)) + case geom.TypeMultiPoint: + mp := g.MustAsMultiPoint() + m := int64(0) + for i := 0; i < mp.NumPoints(); i++ { + m += estimateGeometryMemorySize(mp.PointN(i).AsGeometry()) + } + return m + int64(unsafe.Sizeof(mp)) + case geom.TypeMultiLineString: + mls := g.MustAsMultiLineString() + m := int64(0) + for i := 0; i < mls.NumLineStrings(); i++ { + m += estimateGeometryMemorySize(mls.LineStringN(i).AsGeometry()) + } + return m + int64(unsafe.Sizeof(mls)) + case geom.TypeMultiPolygon: + mp := g.MustAsMultiPolygon() + m := int64(0) + for i := 0; i < mp.NumPolygons(); i++ { + m += estimateGeometryMemorySize(mp.PolygonN(i).AsGeometry()) + } + return m + int64(unsafe.Sizeof(mp)) + default: + panic("unsupported geometry type") + } +} diff --git a/internal/geo/geo.go b/internal/geo/geo.go new file mode 100644 index 0000000..4fac787 --- /dev/null +++ b/internal/geo/geo.go @@ -0,0 +1,170 @@ +package geo + +import ( + "context" + "embed" + "fmt" + "log" + "path/filepath" + "strings" + + "github.com/golang/geo/s2" + "github.com/smilyorg/tinygpkg/gpkg" + "modernc.org/sqlite/vfs" +) + +var ErrNotAvailable = fmt.Errorf("geopackage not available") + +type Config struct { + GeoPackage GeoPackageConfig `json:"geopackage"` + ReverseGeocode bool `json:"reverse_geocode"` +} + +type GeoPackageConfig struct { + Path string `json:"path"` + Table string `json:"table"` + NameCol string `json:"name_col"` +} + +type Geo struct { + config Config + uri string + fs *vfs.FS + gp *gpkg.GeoPackage +} + +// New creates a new Geo +// +// If reverse geocoding is enabled, it will attempt to open the geopackage +// using the `Path` in the config. If the path is empty, it will attempt to +// find the geopackage in the provided embed.FS. +// +// If reverse geocoding is disabled, it will return a Geo with no geopackage, +// leading to ErrNotAvailable being returned for all reverse geocoding requests. +// +// Call Close() on the returned Geo when you are done with it to free resources. +func New(config Config, fs embed.FS) (*Geo, error) { + g := &Geo{ + config: config, + } + if !config.ReverseGeocode { + return g, nil + } + if config.GeoPackage.Path == "" { + // If no path is provided, find the geopackage in the embed.FS + p, err := getGeoPackagePathFromFs(fs, ".") + if err != nil { + return nil, fmt.Errorf("error finding geopackage: %w", err) + } + if p == "" { + return nil, fmt.Errorf("path not set and embedded geopackage not found") + } + n, f, err := vfs.New(fs) + if err != nil { + return nil, fmt.Errorf("error creating geopackage vfs: %w", err) + } + g.fs = f + g.uri = "file:" + p + "?vfs=" + n + "&mode=ro" + } else { + // If a path is provided, use it + g.uri = config.GeoPackage.Path + } + + // Open the geopackage + gp, err := gpkg.Open( + g.uri, + g.config.GeoPackage.Table, + []string{g.config.GeoPackage.NameCol}, + ) + if err != nil { + g.Close() + return nil, fmt.Errorf("error opening geopackage: %w", err) + } + g.gp = gp + + // Set up the geometry cache, this prevents having to re-parse the geometry + // for every request + c, err := NewCache() + if err != nil { + g.Close() + return nil, fmt.Errorf("error creating geocache: %w", err) + } + g.gp.Cache = c + return g, nil +} + +func (g *Geo) Available() bool { + return g != nil && g.config.ReverseGeocode && g.gp != nil +} + +func (g *Geo) String() string { + if g == nil || !g.config.ReverseGeocode { + return "geo reverse geocoding disabled" + } + return "geo using " + g.uri +} + +// ReverseGeocode returns the name of the feature at the given location. +// +// If reverse geocoding is disabled, it will return ErrNotAvailable. +func (g *Geo) ReverseGeocode(ctx context.Context, l s2.LatLng) (string, error) { + if !g.Available() { + return "", ErrNotAvailable + } + cols, err := g.gp.ReverseGeocode(ctx, l) + if err != nil { + return "", fmt.Errorf("error reverse geocoding: %w", err) + } + return cols[0], nil +} + +func (g *Geo) Close() error { + if g == nil { + return nil + } + if g.gp != nil { + err := g.gp.Close() + if err != nil { + return fmt.Errorf("error closing geopackage: %w", err) + } + g.gp = nil + } + if g.fs != nil { + err := g.fs.Close() + if err != nil { + return fmt.Errorf("error closing geopackage vfs: %w", err) + } + g.fs = nil + } + return nil +} + +func getGeoPackagePathFromFs(fs embed.FS, dir string) (path string, _ error) { + entries, err := fs.ReadDir(dir) + if err != nil { + log.Fatalf("failed to read geopackage vfs: %s", err) + } + for _, entry := range entries { + n := entry.Name() + if entry.IsDir() { + p, err := getGeoPackagePathFromFs(fs, filepath.ToSlash(filepath.Join(dir, n))) + if err != nil { + return "", err + } + if p != "" { + if path != "" { + return "", fmt.Errorf("multiple geopackages found in %s", dir) + } + path = p + } + continue + } + if strings.HasSuffix(n, ".gpkg") { + if path != "" { + return "", fmt.Errorf("multiple geopackages found in %s", dir) + } + path = filepath.ToSlash(filepath.Join(dir, n)) + } + } + return path, nil +} diff --git a/internal/image/database.go b/internal/image/database.go index a708ae0..de6245d 100644 --- a/internal/image/database.go +++ b/internal/image/database.go @@ -762,7 +762,7 @@ func (source *Database) GetBatch(ids []ImageId) <-chan InfoListResult { unix := stmt.ColumnInt64(5) timezoneOffset := stmt.ColumnInt(6) - info.DateTime = time.Unix(unix, 0).In(time.FixedZone("tz_offset", timezoneOffset*60)) + info.DateTime = time.Unix(unix, 0).In(time.FixedZone("", timezoneOffset*60)) info.DateTimeNull = stmt.ColumnType(5) == sqlite.TypeNull info.LatLngNull = stmt.ColumnType(7) == sqlite.TypeNull || stmt.ColumnType(8) == sqlite.TypeNull @@ -1393,7 +1393,7 @@ func (source *Database) List(dirs []string, options ListOptions) <-chan InfoList unix := stmt.ColumnInt64(5) timezoneOffset := stmt.ColumnInt(6) - info.DateTime = time.Unix(unix, 0).In(time.FixedZone("tz_offset", timezoneOffset*60)) + info.DateTime = time.Unix(unix, 0).In(time.FixedZone("", timezoneOffset*60)) info.DateTimeNull = stmt.ColumnType(5) == sqlite.TypeNull info.LatLngNull = stmt.ColumnType(7) == sqlite.TypeNull || stmt.ColumnType(8) == sqlite.TypeNull diff --git a/internal/image/info.go b/internal/image/info.go index 97ef677..252f7a3 100644 --- a/internal/image/info.go +++ b/internal/image/info.go @@ -31,6 +31,10 @@ func IsNaNLatLng(latlng s2.LatLng) bool { return math.IsNaN(float64(latlng.Lat)) || math.IsNaN(float64(latlng.Lng)) } +func IsValidLatLng(latlng s2.LatLng) bool { + return !IsNaNLatLng(latlng) && latlng.Lat.Radians() != 0 && latlng.Lng.Radians() != 0 +} + func AngleToKm(a s1.Angle) float64 { return a.Radians() * earthRadiusKm } diff --git a/internal/image/source.go b/internal/image/source.go index 73dd114..bfd9cf0 100644 --- a/internal/image/source.go +++ b/internal/image/source.go @@ -12,6 +12,7 @@ import ( goio "io" "photofield/internal/clip" + "photofield/internal/geo" "photofield/internal/metrics" "photofield/internal/queue" "photofield/io" @@ -21,11 +22,8 @@ import ( "photofield/tag" "github.com/docker/go-units" - "github.com/golang/geo/s2" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" - - "github.com/sams96/rgeo" ) var ErrNotFound = errors.New("not found") @@ -110,14 +108,9 @@ type Caches struct { Image CacheConfig } -type Geo struct { - ReverseGeocode bool `json:"reverse_geocode"` -} - type Config struct { DataDir string AI clip.AI - Geo Geo TagConfig tag.Config `json:"-"` ExifToolCount int `json:"exif_tool_count"` @@ -152,7 +145,6 @@ type Source struct { decoder *Decoder database *Database - rg *rgeo.Rgeo imageInfoCache InfoCache pathCache PathCache @@ -165,24 +157,17 @@ type Source struct { thumbnailSink *sqlite.Source Clip clip.Clip + Geo *geo.Geo } -func NewSource(config Config, migrations embed.FS, migrationsThumbs embed.FS) *Source { +func NewSource(config Config, migrations embed.FS, migrationsThumbs embed.FS, geo *geo.Geo) *Source { source := Source{} source.Config = config source.decoder = NewDecoder(config.ExifToolCount) source.database = NewDatabase(filepath.Join(config.DataDir, "photofield.cache.db"), migrations) source.imageInfoCache = newInfoCache() source.pathCache = newPathCache() - - if config.Geo.ReverseGeocode { - log.Println("rgeo loading") - r, err := rgeo.New(rgeo.Provinces10, rgeo.Cities10) - if err != nil { - log.Fatalf("failed to initialize rgeo: %s", err) - } - source.rg = r - } + source.Geo = geo source.SourceLatencyHistogram = promauto.NewHistogramVec(prometheus.HistogramOpts{ Namespace: metrics.Namespace, @@ -290,29 +275,6 @@ func NewSource(config Config, migrations embed.FS, migrationsThumbs embed.FS) *S return &source } -func (source *Source) ReverseGeocode(l s2.LatLng) (string, error) { - if source.rg == nil { - return "", ErrUnavailable - } - location, err := source.rg.ReverseGeocode([]float64{l.Lng.Degrees(), l.Lat.Degrees()}) - if err != nil { - return "", err - } - loc := "" - if err == nil { - loc = location.City - if loc == "" { - loc = location.Province - } - if loc == "" { - loc = location.Country - } else if location.Country != "" { - loc = fmt.Sprintf("%s (%s)", loc, location.Country) - } - } - return loc, nil -} - func (source *Source) Vacuum() error { return source.database.vacuum() } diff --git a/internal/layout/timeline.go b/internal/layout/timeline.go index 9a54216..ccd640f 100644 --- a/internal/layout/timeline.go +++ b/internal/layout/timeline.go @@ -1,9 +1,11 @@ package layout import ( + "context" "log" "time" + "github.com/golang/geo/s2" "github.com/hako/durafmt" "github.com/tdewolff/canvas" @@ -79,6 +81,8 @@ func LayoutTimeline(infos <-chan image.SourcedInfo, layout Layout, scene *render event := TimelineEvent{} eventCount := 0 var lastPhotoTime time.Time + var lastLocationTime time.Time + var lastLatLng s2.LatLng rect := render.Rect{ X: sceneMargin, @@ -124,10 +128,22 @@ func LayoutTimeline(infos <-chan image.SourcedInfo, layout Layout, scene *render event.Section.infos = append(event.Section.infos, info) - if !image.IsNaNLatLng(info.LatLng) { - location, err := source.ReverseGeocode(info.LatLng) - if err == nil { - locations[location] = struct{}{} + if source.Geo.Available() { + lastLocationTimeElapsed := lastLocationTime.Sub(photoTime) + if lastLocationTimeElapsed < 0 { + lastLocationTimeElapsed = -lastLocationTimeElapsed + } + queryLocation := lastLocationTime.IsZero() || lastLocationTimeElapsed > 15*time.Minute + if queryLocation && image.IsValidLatLng(info.LatLng) { + lastLocationTime = photoTime + dist := image.AngleToKm(lastLatLng.Distance(info.LatLng)) + if dist > 1 { + location, err := source.Geo.ReverseGeocode(context.TODO(), info.LatLng) + if err == nil { + locations[location] = struct{}{} + } + lastLatLng = info.LatLng + } } } diff --git a/justfile b/justfile index e1c7283..06a569d 100644 --- a/justfile +++ b/justfile @@ -12,6 +12,16 @@ build-ui: build-local: goreleaser build --snapshot --single-target --clean +# Download geopackage to be embedded via -tags embedgeo +assets: + mkdir -p data/geo + gpkg_file="$(grep -e '//go:embed data/geo/' embed-geo.go | cut -d / -f 5)" && \ + gpkg_ver="$(grep -e '// tinygpkg-data release:' embed-geo.go | cut -d ' ' -f 4)" && \ + gpkg_dst="data/geo/$gpkg_file" && \ + echo "Downloading $gpkg_ver/$gpkg_file" && \ + wget -q -O "$gpkg_dst" https://github.com/SmilyOrg/tinygpkg-data/releases/download/$gpkg_ver/$gpkg_file && \ + echo "Downloaded to $gpkg_dst" + release-local: goreleaser release --snapshot --clean @@ -22,6 +32,10 @@ run-static *args: go build -tags embedstatic ./photofield {{args}} +run-geo *args: + go build -tags embedgeo + ./photofield {{args}} + bench collection: build ./photofield -bench -bench.collection {{collection}} -test.benchtime 1s -test.count 6 diff --git a/main.go b/main.go index ee898fd..9630f1d 100644 --- a/main.go +++ b/main.go @@ -37,11 +37,11 @@ import ( "github.com/go-chi/chi/v5/middleware" "github.com/go-chi/cors" chirender "github.com/go-chi/render" + "github.com/grafana/pyroscope-go" "github.com/hako/durafmt" "github.com/imdario/mergo" "github.com/joho/godotenv" "github.com/lpar/gzipped" - "github.com/pyroscope-io/client/pyroscope" "github.com/tdewolff/canvas" "github.com/tdewolff/canvas/rasterizer" @@ -55,6 +55,7 @@ import ( "photofield/internal/clip" "photofield/internal/codec" "photofield/internal/collection" + "photofield/internal/geo" "photofield/internal/image" "photofield/internal/layout" "photofield/internal/metrics" @@ -997,7 +998,7 @@ type AppConfig struct { Render render.Render `json:"render"` Media image.Config `json:"media"` AI clip.AI `json:"ai"` - Geo image.Geo `json:"geo"` + Geo geo.Config `json:"geo"` Tags tag.Config `json:"tags"` TileRequests TileRequestConfig `json:"tile_requests"` } @@ -1066,7 +1067,6 @@ func loadConfiguration(path string) AppConfig { } appConfig.Media.AI = appConfig.AI - appConfig.Media.Geo = appConfig.Geo appConfig.Tags.Enable = appConfig.Tags.Enable || appConfig.Tags.Enabled return appConfig @@ -1166,6 +1166,8 @@ func benchmarkSources(collection *collection.Collection, seed int64, sampleSize } func main() { + var err error + startupTime = time.Now() testing.Init() @@ -1246,7 +1248,16 @@ func main() { defaultSceneConfig.Render = appConfig.Render tileRequestConfig = appConfig.TileRequests - imageSource = image.NewSource(appConfig.Media, migrations, migrationsThumbs) + geo, err := geo.New( + appConfig.Geo, + GeoFs, + ) + if err != nil { + log.Printf("geo disabled: %v", err) + } else { + log.Printf("%v", geo.String()) + } + imageSource = image.NewSource(appConfig.Media, migrations, migrationsThumbs, geo) defer imageSource.Close() if *vacuumFlag { @@ -1261,7 +1272,7 @@ func main() { fontFamily := canvas.NewFontFamily("Main") // fontFamily.Use(canvas.CommonLigatures) - err := fontFamily.LoadFont(robotoRegular, canvas.FontRegular) + err = fontFamily.LoadFont(robotoRegular, canvas.FontRegular) if err != nil { panic(err) }