diff --git a/README.md b/README.md index 870b374..0bd0e7b 100644 --- a/README.md +++ b/README.md @@ -55,15 +55,13 @@ go build . If you prefer to run the binary directly you have the following options: ``` -Usage of ./go_tile: +Usage of go_tile: -data string Path to directory containing tiles (default "./data") -host string HTTP Listening host (default "0.0.0.0") -map string Name of map. This value is also used to determine the metatile subdirectory (default "ajt") - -osm_path string - Path to osm_path to use for direct rendering. (experimental) -port int HTTP Listening port (default 8080) -renderd-timeout duration @@ -138,24 +136,3 @@ Percentage of the requests served within a certain time (ms) This benchmark doesn't access the disk, as the tile has obviously been cached in memory. Anyways it should give you an indication of whether this is fast enough for your use-case. - -# Experimental renderer - -This repo also contains an experimental rudimentary renderer. Its goal is to be able to render some sort of map with little operational overhead. - -Experimental renderer (this will not be updated on every release, so ymmv) compared with osm.org: - -![black lines only](assets/5295.png) -![osm.org](assets/5295-compare.png) - -Images [OpenStreetMap](https://www.openstreetmap.org/) contributors, [CC-BY-SA](https://creativecommons.org/licenses/by-sa/2.0/) - -To use this, pass an oms.pbf file via `-osm_path`. - -Currently osm.pbf files have to be prepared like so -``` -osmium add-locations-to-ways ~/Downloads/hamburg-latest.osm.pbf -o prepared.osm.pbf -f -``` - -This creates a temporary file on startup, which might take some time. -Testing so far was only done for individual cities. diff --git a/assets/5295-compare.png b/assets/5295-compare.png deleted file mode 100644 index 23eff16..0000000 Binary files a/assets/5295-compare.png and /dev/null differ diff --git a/assets/5295.png b/assets/5295.png deleted file mode 100644 index d44e6c9..0000000 Binary files a/assets/5295.png and /dev/null differ diff --git a/go.mod b/go.mod index 32a97f2..67c35f3 100644 --- a/go.mod +++ b/go.mod @@ -1,20 +1,3 @@ module github.com/nielsole/go_tile go 1.20 - -require ( - git.sr.ht/~sbinet/gg v0.5.0 - github.com/paulmach/osm v0.7.1 -) - -require ( - github.com/campoy/embedmd v1.0.0 // indirect - github.com/datadog/czlib v0.0.0-20160811164712-4bc9a24e37f2 // indirect - github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect - github.com/paulmach/orb v0.1.3 // indirect - github.com/paulmach/protoscan v0.2.1 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - golang.org/x/image v0.7.0 // indirect - golang.org/x/text v0.9.0 // indirect - google.golang.org/protobuf v1.27.1 // indirect -) diff --git a/go.sum b/go.sum index a878925..e69de29 100644 --- a/go.sum +++ b/go.sum @@ -1,60 +0,0 @@ -git.sr.ht/~sbinet/gg v0.5.0 h1:6V43j30HM623V329xA9Ntq+WJrMjDxRjuAB1LFWF5m8= -git.sr.ht/~sbinet/gg v0.5.0/go.mod h1:G2C0eRESqlKhS7ErsNey6HHrqU1PwsnCQlekFi9Q2Oo= -github.com/campoy/embedmd v1.0.0 h1:V4kI2qTJJLf4J29RzI/MAt2c3Bl4dQSYPuflzwFH2hY= -github.com/campoy/embedmd v1.0.0/go.mod h1:oxyr9RCiSXg0M3VJ3ks0UGfp98BpSSGr0kpiX3MzVl8= -github.com/datadog/czlib v0.0.0-20160811164712-4bc9a24e37f2 h1:ISaMhBq2dagaoptFGUyywT5SzpysCbHofX3sCNw1djo= -github.com/datadog/czlib v0.0.0-20160811164712-4bc9a24e37f2/go.mod h1:2yDaWzisHKoQoxm+EU4YgKBaD7g1M0pxy7THWG44Lro= -github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= -github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/paulmach/orb v0.1.3 h1:Wa1nzU269Zv7V9paVEY1COWW8FCqv4PC/KJRbJSimpM= -github.com/paulmach/orb v0.1.3/go.mod h1:VFlX/8C+IQ1p6FTRRKzKoOPJnvEtA5G0Veuqwbu//Vk= -github.com/paulmach/osm v0.7.1 h1:dc84gLa4S/zCCqpBxb6jXTkN5dCI7VK7edt/tZTFG50= -github.com/paulmach/osm v0.7.1/go.mod h1:v0vZa0rKnCsO8ovx0Z+hR9BWVD+vO4ogLOXcV18/0yk= -github.com/paulmach/protoscan v0.2.1 h1:rM0FpcTjUMvPUNk2BhPJrreDKetq43ChnL+x1sRg8O8= -github.com/paulmach/protoscan v0.2.1/go.mod h1:SpcSwydNLrxUGSDvXvO0P7g7AuhJ7lcKfDlhJCDw2gY= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/image v0.7.0 h1:gzS29xtG1J5ybQlv0PuyfE3nmc6R4qB73m6LUUmvFuw= -golang.org/x/image v0.7.0/go.mod h1:nd/q4ef1AKKYl/4kft7g+6UyGbdiqWqTP1ZAbRoV7Rg= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/time v0.0.0-20190921001708-c4c64cad1fd0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= -google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= diff --git a/main.go b/main.go index 4bc9397..aebb405 100644 --- a/main.go +++ b/main.go @@ -7,7 +7,6 @@ import ( "flag" "fmt" "io" - "io/ioutil" "net" "net/http" "os" @@ -15,7 +14,6 @@ import ( "syscall" "time" - "github.com/nielsole/go_tile/renderer" "github.com/nielsole/go_tile/utils" ) @@ -174,7 +172,6 @@ func main() { data_dir := flag.String("data", "./data", "Path to directory containing tiles") static_dir := flag.String("static", "./static/", "Path to static file directory") renderd_socket := flag.String("socket", "", "Unix domain socket path or hostname:port for contacting renderd. Rendering disabled by default.") - osm_path := flag.String("osm_path", "", "Path to osm_path to use for direct rendering. (experimental)") renderd_timeout_duration := flag.Duration("renderd-timeout", (time.Duration(60) * time.Second), "Timeout duration after which renderd returns an error to the client (I.E. '30s' for thirty seconds). Set negative to disable") map_name := flag.String("map", "ajt", "Name of map. This value is also used to determine the metatile subdirectory") tls_cert_path := flag.String("tls_cert_path", "", "Path to TLS certificate") @@ -184,10 +181,6 @@ func main() { flag.Parse() - if len(*osm_path) > 0 && len(*renderd_socket) > 0 { - logFatalf("osm_path and renderd_socket are mutually exclusive") - } - // Renderd expects at most 64 bytes. // 64 - (5 * 4 bytes - 1 zero byte of null-terminated string) = 43 if len(*map_name) > 43 { @@ -212,63 +205,15 @@ func main() { logInfof("renderd backend is disabled") } - var requestHandler func(http.ResponseWriter, *http.Request) - - if len(*osm_path) > 0 { - // Create a temp file. - var err error - tempFile, err := ioutil.TempFile("", "example") - if err != nil { - fmt.Println("Cannot create temp file:", err) - os.Exit(1) - } - - data, err := renderer.LoadData(*osm_path, 15, tempFile) - if err != nil { - logFatalf("There was an error loading data: %v", err) - } - tempFileName := tempFile.Name() - tempFile.Close() - - // Memory-map the file - mmapData, mmapFile, err := renderer.Mmap(tempFileName) - if err != nil { - logFatalf("There was an error memory-mapping temp file: %v", err) - } - defer renderer.Munmap(mmapData) - defer mmapFile.Close() - requestHandler = func(w http.ResponseWriter, r *http.Request) { - if *verbose { - logDebugf("%s request received: %s", r.Method, r.RequestURI) - } - if r.Method != "GET" { - http.Error(w, "Only GET requests allowed", http.StatusMethodNotAllowed) - return - } - renderer.HandleRenderRequest(w, r, *renderd_timeout_duration, data, 15, mmapData) - //handleRequest(w, r, *data_dir, *map_name, *renderd_socket, *renderd_timeout_duration, *tile_expiration_duration) + requestHandler := func(w http.ResponseWriter, r *http.Request) { + if *verbose { + logDebugf("%s request received: %s", r.Method, r.RequestURI) } - defer func() { - // Cleanup the temp file. - if err := os.Remove(tempFile.Name()); err != nil { - fmt.Println("Failed to remove temp file:", err) - } else { - fmt.Println("Temp file removed.") - } - - }() - } else { - - requestHandler = func(w http.ResponseWriter, r *http.Request) { - if *verbose { - logDebugf("%s request received: %s", r.Method, r.RequestURI) - } - if r.Method != "GET" { - http.Error(w, "Only GET requests allowed", http.StatusMethodNotAllowed) - return - } - handleRequest(w, r, *data_dir, *map_name, *renderd_socket, *renderd_timeout_duration, *tile_expiration_duration) + if r.Method != "GET" { + http.Error(w, "Only GET requests allowed", http.StatusMethodNotAllowed) + return } + handleRequest(w, r, *data_dir, *map_name, *renderd_socket, *renderd_timeout_duration, *tile_expiration_duration) } // HTTP request multiplexer httpServeMux := http.NewServeMux() diff --git a/renderer/.gitignore b/renderer/.gitignore deleted file mode 100644 index da5f78c..0000000 --- a/renderer/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -cpu.out -*.svg -*.test diff --git a/renderer/experimental-renderer-windows.go b/renderer/experimental-renderer-windows.go deleted file mode 100644 index 172873c..0000000 --- a/renderer/experimental-renderer-windows.go +++ /dev/null @@ -1,27 +0,0 @@ -//go:build windows -// +build windows - -package renderer - -import ( - "errors" - "net/http" - "os" - "time" -) - -func LoadData(path string, maxZ uint32, tempFile *os.File) (*Data, error) { - return nil, errors.New("not implemented on Windows") -} - -func Mmap(path string) (*[]byte, *os.File, error) { - return nil, nil, errors.New("not implemented on Windows") -} - -func HandleRenderRequest(w http.ResponseWriter, r *http.Request, duration time.Duration, data *Data, maxTreeDepth uint32, mmapData *[]byte) { - return -} - -func Munmap(data *[]byte) error { - return errors.New("not implemented on Windows") -} diff --git a/renderer/experimental-renderer.go b/renderer/experimental-renderer.go deleted file mode 100644 index 01c8283..0000000 --- a/renderer/experimental-renderer.go +++ /dev/null @@ -1,239 +0,0 @@ -//go:build !windows -// +build !windows - -package renderer - -import ( - "context" - "io" - "net/http" - "os" - "runtime" - "syscall" - "time" - - "git.sr.ht/~sbinet/gg" - "github.com/nielsole/go_tile/utils" - "github.com/paulmach/osm" - "github.com/paulmach/osm/osmpbf" -) - -func pointToPixels(point Point, bbox BoundingBox, pixels uint32) Pixel { - x := (point.Lon - bbox.Min.Lon) / (bbox.Max.Lon - bbox.Min.Lon) * float64(pixels) - y := float64(pixels) - (point.Lat-bbox.Min.Lat)/(bbox.Max.Lat-bbox.Min.Lat)*float64(pixels) - return Pixel{x, y} -} - -// Returns true for types of Ways that should be displayed at zoom level 0-9. -// Includes major roads, railways, waterways, and important landmarks. -func importantLandmarkZ9(tags *osm.Tags) bool { - return isImportantWay(tags) //|| isRailWay(tags) || isWaterWay(tags) || isImportantLandmark(tags) -} - -func isImportantWay(tags *osm.Tags) bool { - value := tags.Find("highway") - return value == "motorway" || value == "trunk" || value == "primary" || value == "secondary" || value == "tertiary" || value == "motorway_link" || value == "trunk_link" || value == "primary_link" || value == "secondary_link" || value == "tertiary_link" -} - -func nodeToPoint(node *osm.WayNode) Point { - return Point{node.Lon, node.Lat} -} - -func LoadData(path string, maxZ uint32, tempFile *os.File) (*Data, error) { - file, err := os.Open(path) - if err != nil { - panic(err) - } - defer file.Close() - - // The third parameter is the number of parallel decoders to use. - scanner := osmpbf.New(context.Background(), file, runtime.GOMAXPROCS(-1)) - scanner.SkipNodes = true - scanner.SkipRelations = true - defer scanner.Close() - data := Data{ - Tiles: make(map[uint64]*[]MapObjectOffset), - Filepath: tempFile.Name(), - } - - // // Creating an empty list for every layer of zoom 0-20 - // for i := 0; i < 21; i++ { - // data.Levels = append(data.Levels, make([]uint64, 0)) - // } - var i uint64 = 0 - var maxPoints int = 0 - for scanner.Scan() { - switch v := scanner.Object().(type) { - case *osm.Way: - mapObject := MapObject{ - BoundingBox: getBoundingBoxFromWay(v), - Points: make([]Point, 0), - } - for _, node := range v.Nodes { - mapObject.Points = append(mapObject.Points, nodeToPoint(&node)) - } - if len(mapObject.Points) > maxPoints { - maxPoints = len(mapObject.Points) - } - position, err := tempFile.Seek(0, io.SeekCurrent) - if err != nil { - return nil, err - } - _, err = WriteMapObject(tempFile, mapObject) - if err != nil { - return nil, err - } - containingTiles := getTilesForBoundingBox(mapObject.BoundingBox, 0, maxZ) - for _, containingTile := range containingTiles { - if !importantLandmarkZ9(&v.Tags) && (containingTile.Z < 11) { - continue - } - index := containingTile.index() - if _, ok := data.Tiles[index]; ok { - *data.Tiles[index] = append(*data.Tiles[index], MapObjectOffset(position)) - } else { - data.Tiles[index] = &[]MapObjectOffset{MapObjectOffset(position)} - } - } - i += 1 - } - - if err := scanner.Err(); err != nil { - return nil, err - } - } - data.MaxPoints = maxPoints - return &data, nil -} - -func Mmap(path string) (*[]byte, *os.File, error) { - file, err := os.Open(path) - if err != nil { - return nil, nil, err - } - defer file.Close() - fi, err := file.Stat() - if err != nil { - return nil, nil, err - } - size := int64(fi.Size()) - // Get system page size - pageSize := os.Getpagesize() - - // Calculate the number of pages needed, rounding up - pages := (size + int64(pageSize) - 1) / int64(pageSize) - - // Calculate the size in bytes - sizeInBytes := pages * int64(pageSize) - - // Memory-map the file - mmapData, err := syscall.Mmap(int(file.Fd()), 0, int(sizeInBytes), syscall.PROT_READ, syscall.MAP_SHARED) - if err != nil { - return nil, nil, err - } - return &mmapData, file, nil -} - -func Munmap(data *[]byte) error { - return syscall.Munmap(*data) -} - -func HandleRenderRequest(w http.ResponseWriter, r *http.Request, duration time.Duration, data *Data, maxTreeDepth uint32, mmapData *[]byte) { - z, x, y, ext, err := utils.ParsePath(r.URL.Path) - if ext != "png" { - http.Error(w, "Only png is supported", http.StatusBadRequest) - } - if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - tile := Tile{ - X: x, - Y: y, - Z: z, - } - bbox := getBoundingBox(tile) - // fmt.Printf("Bounding box is from Lat %f, Lon %f to Lat %f, Lon %f\n", bbox.Min.Lat, bbox.Min.Lon, bbox.Max.Lat, bbox.Max.Lon) - - const S = 256 - dc := gg.NewContext(S, S) - - dc.SetRGB(1, 1, 1) - dc.Clear() - parentTile := tile - for tempZ := z; tempZ > maxTreeDepth; tempZ-- { - parentTile = parentTile.getParent() - } - wayIndices, ok := data.Tiles[parentTile.index()] - if !ok { - // Return 404 - w.WriteHeader(http.StatusNotFound) - return - } - way := MapObject{Points: make([]Point, 0, data.MaxPoints)} - for _, wayReference := range *wayIndices { - err := ReadMapObject(mmapData, int64(wayReference), &way) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - // way := data.MapObjects[wayReference] - visible := false - if !bbox.overlaps(way.BoundingBox) { - continue - } - //tags := way.Tags.Map() - for i, point := range way.Points { - // Print Lat and Lng of node - //fmt.Printf("Lat: %f, Lon: %f\n", node.Lat, node.Lon) - - if bbox.contains(point) { - visible = true - } - - // We know that all previous points were outside of bounds, so we can skip them - if i != 0 && visible { - dc.SetRGB(0, 0, 0) - // if highway, ok := tags["highway"]; ok { - // switch highway { - // case "motorway": - // dc.SetRGB(0.9, 0.6, 0.6) - // dc.SetLineWidth(6) - // case "trunk": - // dc.SetRGB(0.85, 0.55, 0.55) - // dc.SetLineWidth(4) - // case "primary": - // dc.SetRGB(0.8, 0.5, 0.5) - // dc.SetLineWidth(3) - // case "secondary": - // dc.SetRGB(0.75, 0.45, 0.45) - // dc.SetLineWidth(2) - // case "tertiary": - // dc.SetRGB(0.7, 0.4, 0.4) - // dc.SetLineWidth(1.5) - // case "unclassified": - // dc.SetRGB(0.65, 0.35, 0.35) - // dc.SetLineWidth(1.5) - // case "residential": - // dc.SetRGB(0.6, 0.3, 0.3) - // dc.SetLineWidth(1.5) - // } - // } else { - dc.SetLineWidth(1) - //} - - previousPoint := way.Points[i-1] - currentPixel := pointToPixels(point, bbox, S) - previousPixel := pointToPixels(previousPoint, bbox, S) - dc.DrawLine(previousPixel.X, previousPixel.Y, currentPixel.X, currentPixel.Y) - dc.Stroke() - } - } - } - - w.Header().Set("Content-Type", "image/png") - if err := dc.EncodePNG(w); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - } -} diff --git a/renderer/experimental-renderer_test.go b/renderer/experimental-renderer_test.go deleted file mode 100644 index 5c5fb8f..0000000 --- a/renderer/experimental-renderer_test.go +++ /dev/null @@ -1,182 +0,0 @@ -//go:build !windows -// +build !windows - -package renderer - -import ( - "bytes" - "fmt" - "io/ioutil" - "log" - "net/http/httptest" - "os" - "syscall" - "testing" - "time" -) - -// Test to check bounding box -func TestBoundingBox(t *testing.T) { - // Test bounding box - p := Point{10.068140300000001, 53.6577386} - tile := Tile{ - X: 138403, - Y: 84591, - Z: 18, - } - bbox := getBoundingBox(tile) - if !bbox.contains(p) { - t.Errorf("Point %v is not in bounding box %v", p, bbox) - } -} - -// How to debug -// go test -benchtime 15s -bench=BenchmarkServe -cpuprofile cpu.out -// go tool pprof cpu.out -// svg - -// 26440844f98323a1ea611acd3be00196a7b2a48d 2023-06-27 -// -// 752985932 ns/op -// -// 643b79ef31ade931afa5037381eeecd8a2a19d19 -// -// 54120852 ns/op -// -// b82ccbeef7bebe3647783ea9b3ed80638d5785cd -// -// 8390724 ns/op -// 5443820 ns/op -// -// Full tile -// 3484142532 ns/op -// cec1fd4962a9fd01956f827a7ca74d98560bb6a1 -// -// 293297990 ns/op -// 88056 ns/op -// -// c03af9bccb2499f3f0291c8e8aca22f141f06600 -// -// 77898 ns/op -// -// ef29875363c610d536be31e603921c71ef698468 -// -// 95696 ns/op -func BenchmarkServeEmptyTile(b *testing.B) { - b.StopTimer() - pathTile := "/tile/11/1086/664.png" - tempFile, err := ioutil.TempFile("", "example") - if err != nil { - fmt.Println("Cannot create temp file:", err) - os.Exit(1) - } - defer os.Remove(tempFile.Name()) - data, err := LoadData("/home/nokadmin/projects/go_tile/mock_data/test.osm.pbf", 15, tempFile) - if err != nil { - b.Error(err) - } - tempFileName := tempFile.Name() - tempFile.Close() - // Memory-map the file - mmapData, mmapFile, err := Mmap(tempFileName) - if err != nil { - log.Fatalf("There was an error memory-mapping temp file: %v", err) - } - defer syscall.Munmap(*mmapData) - defer mmapFile.Close() - b.StartTimer() - for i := 0; i < b.N; i++ { - req := httptest.NewRequest("GET", pathTile, bytes.NewReader([]byte{})) - resp := httptest.ResponseRecorder{} - HandleRenderRequest(&resp, req, time.Second, data, 15, mmapData) - } -} - -// b82ccbeef7bebe3647783ea9b3ed80638d5785cd -// -// 2579786011 ns/op -// 2532791281 ns/op -// -// cec1fd4962a9fd01956f827a7ca74d98560bb6a1 -// 18045510356 ns/op -// 13405872770 ns/op -// -// 4064247136 ns/op -// -// c03af9bccb2499f3f0291c8e8aca22f141f06600 -// -// 2456439762 ns/op -// 2536369085 ns/op -func BenchmarkServeFullTile(b *testing.B) { - b.StopTimer() - pathTile := "/tile/11/1081/661.png" - tempFile, err := ioutil.TempFile("", "example") - if err != nil { - fmt.Println("Cannot create temp file:", err) - os.Exit(1) - } - defer os.Remove(tempFile.Name()) - data, err := LoadData("/home/nokadmin/projects/go_tile/mock_data/test.osm.pbf", 15, tempFile) - if err != nil { - b.Error(err) - } - tempFileName := tempFile.Name() - tempFile.Close() - // Memory-map the file - mmapData, mmapFile, err := Mmap(tempFileName) - if err != nil { - log.Fatalf("There was an error memory-mapping temp file: %v", err) - } - defer syscall.Munmap(*mmapData) - defer mmapFile.Close() - b.StartTimer() - for i := 0; i < b.N; i++ { - req := httptest.NewRequest("GET", pathTile, bytes.NewReader([]byte{})) - resp := httptest.ResponseRecorder{} - HandleRenderRequest(&resp, req, time.Second, data, 15, mmapData) - } -} - -// b82ccbeef7bebe3647783ea9b3ed80638d5785cd -// -// 8440346178 ns/op -// 90810744 ns/op -// -// cec1fd4962a9fd01956f827a7ca74d98560bb6a1 -// -// 41576416793 ns/op -// 7996205074 ns/op -// -// c03af9bccb2499f3f0291c8e8aca22f141f06600 -// -// 4617123956 ns/op -// 101012328 ns/op -func BenchmarkServeFullTileZ3(b *testing.B) { - b.StopTimer() - pathTile := "/tile/3/4/2.png" - tempFile, err := ioutil.TempFile("", "example") - if err != nil { - fmt.Println("Cannot create temp file:", err) - os.Exit(1) - } - defer os.Remove(tempFile.Name()) - data, err := LoadData("/home/nokadmin/projects/go_tile/mock_data/test.osm.pbf", 15, tempFile) - if err != nil { - b.Error(err) - } - tempFileName := tempFile.Name() - tempFile.Close() - // Memory-map the file - mmapData, mmapFile, err := Mmap(tempFileName) - if err != nil { - log.Fatalf("There was an error memory-mapping temp file: %v", err) - } - defer syscall.Munmap(*mmapData) - defer mmapFile.Close() - b.StartTimer() - for i := 0; i < b.N; i++ { - req := httptest.NewRequest("GET", pathTile, bytes.NewReader([]byte{})) - resp := httptest.ResponseRecorder{} - HandleRenderRequest(&resp, req, time.Second, data, 15, mmapData) - } -} diff --git a/renderer/file.go b/renderer/file.go deleted file mode 100644 index 620ef08..0000000 --- a/renderer/file.go +++ /dev/null @@ -1,88 +0,0 @@ -package renderer - -import ( - "bytes" - "encoding/binary" - "os" -) - -func WriteMapObject(file *os.File, mo MapObject) (int64, error) { - // Buffer to store binary representation - buf := new(bytes.Buffer) - - // Write BoundingBox - err := binary.Write(buf, binary.LittleEndian, mo.BoundingBox.Min.Lat) - if err != nil { - return 0, err - } - err = binary.Write(buf, binary.LittleEndian, mo.BoundingBox.Min.Lon) - if err != nil { - return 0, err - } - err = binary.Write(buf, binary.LittleEndian, mo.BoundingBox.Max.Lat) - if err != nil { - return 0, err - } - err = binary.Write(buf, binary.LittleEndian, mo.BoundingBox.Max.Lon) - if err != nil { - return 0, err - } - - // Write length of Points slice - err = binary.Write(buf, binary.LittleEndian, int64(len(mo.Points))) - if err != nil { - return 0, err - } - - // Write Points - for _, p := range mo.Points { - err = binary.Write(buf, binary.LittleEndian, p.Lat) - if err != nil { - return 0, err - } - err = binary.Write(buf, binary.LittleEndian, p.Lon) - if err != nil { - return 0, err - } - } - - // Write to file - n, err := file.Write(buf.Bytes()) - return int64(n), err -} - -func ReadMapObject(mmapData *[]byte, offset int64, mo *MapObject) error { - // Go to the correct offset - - // Create a bytes reader for the buffer - reader := bytes.NewReader((*mmapData)[offset : offset+40]) - - // Read BoundingBox and length from the buffer - binary.Read(reader, binary.LittleEndian, &mo.BoundingBox.Min.Lat) - binary.Read(reader, binary.LittleEndian, &mo.BoundingBox.Min.Lon) - binary.Read(reader, binary.LittleEndian, &mo.BoundingBox.Max.Lat) - binary.Read(reader, binary.LittleEndian, &mo.BoundingBox.Max.Lon) - - var lenPoints int64 - binary.Read(reader, binary.LittleEndian, &lenPoints) - - // Create a bytes reader for the buffer - reader = bytes.NewReader((*mmapData)[offset+40 : offset+40+lenPoints*16]) - - // Ensure Points slice is big enough - if cap(mo.Points) < int(lenPoints) { - mo.Points = make([]Point, lenPoints) - } else { - mo.Points = mo.Points[:lenPoints] - } - - // Read Points from the buffer - for i := int64(0); i < lenPoints; i++ { - var p Point - binary.Read(reader, binary.LittleEndian, &p.Lat) - binary.Read(reader, binary.LittleEndian, &p.Lon) - mo.Points[i] = p - } - - return nil -} diff --git a/renderer/projector.go b/renderer/projector.go deleted file mode 100644 index a39540d..0000000 --- a/renderer/projector.go +++ /dev/null @@ -1,61 +0,0 @@ -package renderer - -import ( - "math" - - "github.com/paulmach/osm" -) - -func getBoundingBox(tile Tile) BoundingBox { - n := math.Pow(2.0, float64(tile.Z)) - lonMin := float64(tile.X)/n*360.0 - 180.0 - latMin := math.Atan(math.Sinh(math.Pi*(1-2*float64(tile.Y)/n))) * 180.0 / math.Pi - lonMax := float64(tile.X+1)/n*360.0 - 180.0 - latMax := math.Atan(math.Sinh(math.Pi*(1-2*float64(tile.Y+1)/n))) * 180.0 / math.Pi - pointMin := Point{math.Min(lonMin, lonMax), math.Min(latMin, latMax)} - pointMax := Point{math.Max(lonMin, lonMax), math.Max(latMin, latMax)} - return BoundingBox{pointMin, pointMax} -} - -func getBoundingBoxFromWay(way *osm.Way) BoundingBox { - var lonMin float64 = 200.0 - var latMin float64 = 200.0 - var lonMax float64 = -200.0 - var latMax float64 = -200.0 - for _, node := range way.Nodes { - lonMin = math.Min(lonMin, node.Lon) - latMin = math.Min(latMin, node.Lat) - lonMax = math.Max(lonMax, node.Lon) - latMax = math.Max(latMax, node.Lat) - } - pointMin := Point{lonMin, latMin} - pointMax := Point{lonMax, latMax} - return BoundingBox{pointMin, pointMax} - -} - -// Given a latitude, longitude and zoom level, return the tile coordinates -func deg2num(lat_deg, lon_deg float64, zoom uint32) (x, y uint32) { - lat_rad := math.Pi * lat_deg / 180.0 - n := math.Pow(2.0, float64(zoom)) - x = uint32(math.Floor((lon_deg + 180.0) / 360.0 * n)) - y = uint32(math.Floor((1.0 - math.Log(math.Tan(lat_rad)+(1/math.Cos(lat_rad)))/math.Pi) / 2.0 * n)) - return -} - -func getTilesForBoundingBox(bbox BoundingBox, minZ, maxZ uint32) []Tile { - var tiles []Tile - - for z := minZ; z <= maxZ; z++ { - minX, minY := deg2num(bbox.Max.Lat, bbox.Min.Lon, z) - maxX, maxY := deg2num(bbox.Min.Lat, bbox.Max.Lon, z) - - for x := minX; x <= maxX; x++ { - for y := minY; y <= maxY; y++ { - tiles = append(tiles, Tile{X: x, Y: y, Z: z}) - } - } - } - - return tiles -} diff --git a/renderer/projector_test.go b/renderer/projector_test.go deleted file mode 100644 index a2e4ebd..0000000 --- a/renderer/projector_test.go +++ /dev/null @@ -1,51 +0,0 @@ -package renderer - -import "testing" - -func TestNum2Deg(t *testing.T) { - // The center of Hamburg - tile := Tile{ - X: 138346, - Y: 84715, - Z: 18, - } - bbox := getBoundingBox(tile) - test_point := bbox.center() - x, y := deg2num(test_point.Lat, test_point.Lon, 18) - if x != tile.X { - t.Errorf("X should be %v, but is %v", tile.X, x) - } - if y != tile.Y { - t.Errorf("Y should be %v, but is %v", tile.Y, y) - } -} - -func TestGetTilesForBoundingBox(t *testing.T) { - bbox := BoundingBox{ - Min: Point{ - Lat: 53.557078, - Lon: 9.989095, - }, - Max: Point{ - Lat: 53.557078, - Lon: 9.989095, - }, - } - tiles := getTilesForBoundingBox(bbox, 18, 18) - if len(tiles) != 1 { - t.Errorf("Should be 1 tile, but is %v", len(tiles)) - } - if tiles[0].X != 138345 { - t.Errorf("X should be 138346, but is %v", tiles[0].X) - } - if tiles[0].Y != 84715 { - t.Errorf("Y should be 84715, but is %v", tiles[0].Y) - } - if tiles[0].Z != 18 { - t.Errorf("Z should be 18, but is %v", tiles[0].Z) - } - tiles = getTilesForBoundingBox(bbox, 1, 17) - if len(tiles) != 17 { - t.Errorf("Should be 17 tiles, but is %v", len(tiles)) - } -} diff --git a/renderer/types.go b/renderer/types.go deleted file mode 100644 index 4d17687..0000000 --- a/renderer/types.go +++ /dev/null @@ -1,87 +0,0 @@ -package renderer - -import ( - "math" -) - -type Tile struct { - X, Y, Z uint32 -} - -// when all possible tiles including all smaller zoom levels are stored in a quadtree, this function will be used to get the index of a tile -func (tile Tile) index() uint64 { - // Calculate the total number of tiles for all zoom levels from 0 to tile.Z-1 - total := uint64(0) - for z := uint32(0); z < tile.Z; z++ { - total += uint64(math.Pow(4, float64(z))) - } - - // Calculate the position of the tile within its zoom level - levelPos := tile.Y*uint32(math.Pow(2, float64(tile.Z))) + tile.X - - return total + uint64(levelPos) -} - -// Returns the OSM tile one zoom level above that contains the given point -func (tile Tile) getParent() Tile { - return Tile{tile.X / 2, tile.Y / 2, tile.Z - 1} -} - -type Point struct { - Lon, Lat float64 -} - -type BoundingBox struct { - Min, Max Point -} - -type Pixel struct { - X, Y float64 -} - -// member function checks if a point is inside a bounding box -func (bbox BoundingBox) contains(point Point) bool { - return point.Lat >= bbox.Min.Lat && point.Lat <= bbox.Max.Lat && point.Lon >= bbox.Min.Lon && point.Lon <= bbox.Max.Lon -} - -func (bbox BoundingBox) overlaps(other BoundingBox) bool { - return bbox.Min.Lat <= other.Max.Lat && bbox.Max.Lat >= other.Min.Lat && bbox.Min.Lon <= other.Max.Lon && bbox.Max.Lon >= other.Min.Lon -} - -func (bbox BoundingBox) center() Point { - return Point{(bbox.Min.Lon + bbox.Max.Lon) / 2, (bbox.Min.Lat + bbox.Max.Lat) / 2} -} - -// Architecture: -// We have tiles. Each tile has a bounding box. -// Each tile has a list of map objects. -// Each map object has a bounding box. -// Each map object has a list of points. -// Each point has a longitude and a latitude. -// Each tile may have 4 children and 1 parent. -// Each tile has x, y, and z coordinates. -// Map objects are stored at the tile that contains their bounding box. -// As Tiles are stored on disk, we cannot reference them through pointers but by their index position. -// There is a quadtree for every zoom level. -// The quadtree stops dividing when there are 64 or less map objects in a tile. -type TileData struct { - X, Y, Z uint32 - Children [4]uint64 -} - -type MapObjectOffset uint64 - -type Data struct { - // ChatGPT has the idea to use a map of tiles as a sparse array. Not sure what the performance hit of that is. - // Then I could skip the whole fancy part of continuous memory layout and just have a map[Tile][] map. - // TODO: replace Tile with index - Tiles map[uint64]*[]MapObjectOffset - Filepath string - MaxPoints int -} - -type MapObject struct { - //The bounding box of the map object - BoundingBox BoundingBox - Points []Point -} diff --git a/renderer/types_test.go b/renderer/types_test.go deleted file mode 100644 index 34328a0..0000000 --- a/renderer/types_test.go +++ /dev/null @@ -1,21 +0,0 @@ -package renderer - -import "testing" - -func TestTileGetParent(t *testing.T) { - tile := Tile{ - X: 276643, - Y: 169357, - Z: 19, - } - parent := tile.getParent() - if parent.X != 138321 { - t.Errorf("X should be 138321, but is %v", parent.X) - } - if parent.Y != 84678 { - t.Errorf("Y should be 84678, but is %v", parent.Y) - } - if parent.Z != 18 { - t.Errorf("Z should be 18, but is %v", parent.Z) - } -}