TZF: a fast timezone finder for Go.
TZF is a fast timezone finder package designed for Go. It allows you to quickly
find the timezone for a given latitude and longitude, making it ideal for geo
queries and services such as weather forecast APIs. With optimized performance
and two different data options, TZF is a powerful tool for any Go developer's
toolkit.
[!NOTE]
Here are some language or server which built with tzf or it's other language
bindings:
Quick Start
To start using TZF in your Go project, you first need to install the package:
go get github.com/Alfex4936/tzf
Then, you can use the following code to locate:
package main
import (
"fmt"
"github.com/Alfex4936/tzf"
)
func main() {
finder, err := tzf.NewDefaultFinder()
if err != nil {
panic(err)
}
fmt.Println(finder.GetTimezoneName(116.6386, 40.0786))
}
If you require a query result that is 100% accurate, use the following to
locate:
package main
import (
"fmt"
"github.com/Alfex4936/tzf"
tzfrel "github.com/ringsaturn/tzf-rel"
"github.com/Alfex4936/tzf/pb"
"google.golang.org/protobuf/proto"
)
func main() {
input := &pb.Timezones{}
dataFile := tzfrel.FullData
if err := proto.Unmarshal(dataFile, input); err != nil {
panic(err)
}
finder, _ := tzf.NewFinderFromPB(input)
fmt.Println(finder.GetTimezoneName(116.6386, 40.0786))
}
Best Practice
It's expensive to init tzf's Finder/FuzzyFinder/DefaultFinder, please consider
reuse it or as a global var. Below is a global var example:
package main
import (
"fmt"
"github.com/Alfex4936/tzf"
)
var f tzf.F
func init() {
var err error
f, err = tzf.NewDefaultFinder()
if err != nil {
panic(err)
}
}
func main() {
fmt.Println(f.GetTimezoneName(116.3883, 39.9289))
fmt.Println(f.GetTimezoneName(-73.935242, 40.730610))
}
CLI Tool
In addition to using TZF as a library in your Go projects, you can also use the
tzf command-line interface (CLI) tool to quickly get the timezone name for a set
of coordinates. To use the CLI tool, you first need to install it using the
following command:
go install github.com/Alfex4936/tzf/cmd/tzf@latest
Once installed, you can use the tzf command followed by the latitude and
longitude values to get the timezone name:
tzf -lng 116.3883 -lat 39.9289
Data
You can download the original data from
https://github.com/evansiroky/timezone-boundary-builder.
The preprocessed protobuf data can be obtained from
https://github.com/Alfex4936/tzf-rel, which has Go's embedded
support.
These files are Protocol Buffers messages for more efficient binary
distribution, similar to Python wheels. You can view the
pb/tzinfo.proto file
or its
HTML format documentation for information about the internal format.
The data pipeline for tzf can be illustrated as follows:
graph TD
Raw[GeoJSON from evansiroky/timezone-boundary-builder]
Full[Full: Probuf based data]
Lite[Lite: smaller of Full data]
Compressed[Compressed: Lite compressed via Polyline]
Preindex[Tile based data]
Finder[Finder: Polygon Based Finder]
FuzzyFinder[FuzzyFinder: Tile based Finder]
DefaultFinder[DefaultFinder: combine FuzzyFinder and Compressed Finder]
Raw --> |cmd/geojson2tzpb|Full
Full --> |cmd/reducetzpb|Lite
Lite --> |cmd/compresstzpb|Compressed
Lite --> |cmd/preindextzpb|Preindex
Full --> |tzf.NewFinderFromPB|Finder
Lite --> |tzf.NewFinderFromPB|Finder
Compressed --> |tzf.NewFinderFromCompressed|Finder --> |tzf.NewDefaultFinder|DefaultFinder
Preindex --> |tzf.NewFuzzyFinderFromPB|FuzzyFinder --> |tzf.NewDefaultFinder|DefaultFinder
The complete dataset (~80MB) can be used anywhere, but requires
higher memory usage.
The lightweight dataset (~10MB) may not function optimally in some
border areas.
You can observe points with different outcomes on this page.
If a slightly longer initialization time is tolerable, the
compressed dataset (~5MB) derived from the lightweight dataset
will be more suitable for binary distribution.
The pre-indexed dataset (~1.78MB) consists of multiple tiles.
It is used within the DefaultFinder
, which is built on FuzzyFinder
, to
reduce execution times of the raycasting algorithm.
I have written an article about the history of tzf, its Rust port, and its Rust
port's Python binding; you can view it
here.
Performance
The tzf package is intended for high-performance geospatial query services, such
as weather forecasting APIs. Most queries can be returned within a very short
time, averaging around 2000 nanoseconds.
Here is what has been done to improve performance:
- Using pre-indexing to handle most queries takes approximately 1000
nanoseconds.
- Using an RTree to filter candidate polygons, instead of iterating through all
polygons, reduces the execution times of the Ray Casting algorithm.
- Using a finely-tuned Ray Casting algorithm package
https://github.com/tidwall/geojson to verify whether a polygon contains a
point.
That's all. There are no black magic tricks inside the tzf package.
The benchmark was conducted using version
https://github.com/Alfex4936/tzf/releases/tag/v0.10.0
goos: darwin
goarch: amd64
pkg: github.com/Alfex4936/tzf
cpu: Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz
BenchmarkDefaultFinder_GetTimezoneName_Random_WorldCities-16 441309 2778 ns/op 1000 ns/p50 10000 ns/p90 19000 ns/p99
BenchmarkFuzzyFinder_GetTimezoneName_Random_WorldCities-16 1000000 1077 ns/op 1000 ns/p50 2000 ns/p90 2000 ns/p99
BenchmarkGetTimezoneName-16 226834 5190 ns/op 5000 ns/p50 5000 ns/p90 22000 ns/p99
BenchmarkGetTimezoneNameAtEdge-16 211555 5606 ns/op 5000 ns/p50 6000 ns/p90 23000 ns/p99
BenchmarkGetTimezoneName_Random_WorldCities-16 163000 7279 ns/op 7000 ns/p50 10000 ns/p90 29000 ns/p99
BenchmarkFullFinder_GetTimezoneName-16 212896 5556 ns/op 5000 ns/p50 6000 ns/p90 22000 ns/p99
BenchmarkFullFinder_GetTimezoneNameAtEdge-16 195381 6262 ns/op 6000 ns/p50 7000 ns/p90 23000 ns/p99
BenchmarkFullFinder_GetTimezoneName_Random_WorldCities-16 116652 9354 ns/op 8000 ns/p50 15000 ns/p90 31000 ns/p99
PASS
ok github.com/Alfex4936/tzf 18.321s
Related Repos
Thanks