maps

package module
v2.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 6, 2025 License: BSD-3-Clause Imports: 10 Imported by: 2

README

go-http-maps

Go package providing opinionated HTTP middleware for web-based map tiles.

Version 2

/v2 of this package is not just a complete refactoring of the code but also a complete rethink about what its function is and how it works. There is basically nothing which is backwards compatible with previous versions.

Motivation

The original motivation for the go-http-maps package was to define a handful of top-level methods and net/http middleware handlers to manage the drudegry of setting up maps, with a variety of map providers (Leaflet, Protomaps, Nextzen/Tangramjs), It "worked" but, in the end, it was also not "easy".

Version 2 removes most of the functionality of the go-http-maps package and instead focuses on a handful of methods for providing a dynamic map "config" file (exposed as an HTTP endpoint) which can be from the browser.

This package no longer provides static asset handlers for Leaflet or Protomaps. It is left up to you to bundle (and serve) them from your own code. You could, if you wanted, define a custom http.Handler instance to load those files from the github.com/aaronland/go-http-maps/v2/static/www.FS embedded filesystem but that's still something you'll need to do on your own.

Documentation

godoc is still incomplete at this time.

Usage

The easiest usage example is the cmd/example tool:

package main

import (
	"flag"
	"fmt"
	"log"
	"log/slog"
	"net/http"

	"github.com/aaronland/go-http-maps/v2"
	"github.com/aaronland/go-http-maps/v2/static/www"
)

func main() {

	var verbose bool
	var host string
	var port int

	var initial_view string
	var map_provider string
	var map_tile_uri string
	var protomaps_theme string
	var leaflet_style string
	var leaflet_point_style string

	flag.StringVar(&host, "host", "localhost", "The host to listen for requests on")
	flag.IntVar(&port, "port", 8080, "The port number to listen for requests on")
	flag.BoolVar(&verbose, "verbose", false, "Enable verbose (debug) logging.")

	flag.StringVar(&map_provider, "map-provider", "leaflet", "Valid options are: leaflet, protomaps")
	flag.StringVar(&map_tile_uri, "map-tile-uri", maps.LEAFLET_OSM_TILE_URL, "A valid Leaflet tile layer URI. See documentation for special-case (interpolated tile) URIs.")
	flag.StringVar(&protomaps_theme, "protomaps-theme", "white", "A valid Protomaps theme label.")
	flag.StringVar(&leaflet_style, "leaflet_style", "", "A custom Leaflet style definition for geometries. This may either be a JSON-encoded string or a path on disk.")
	flag.StringVar(&leaflet_point_style, "leaflet_point_style", "", "A custom Leaflet style definition for points. This may either be a JSON-encoded string or a path on disk.")
	flag.StringVar(&initial_view, "initial-view", "", "A comma-separated string indicating the map's initial view. Valid options are: 'LON,LAT', 'LON,LAT,ZOOM' or 'MINX,MINY,MAXX,MAXY'.")

	flag.Parse()

	if verbose {
		slog.SetLogLoggerLevel(slog.LevelDebug)
		slog.Debug("Verbose logging enabled")
	}

	mux := http.NewServeMux()

	opts := &maps.AssignMapConfigHandlerOptions{
		MapProvider:       map_provider,
		MapTileURI:        map_tile_uri,
		InitialView:       initial_view,
		LeafletStyle:      leaflet_style,
		LeafletPointStyle: leaflet_point_style,
		ProtomapsTheme:    protomaps_theme,
	}

	maps.AssignMapConfigHandler(opts, mux, "/map.json")
	
	www_fs := http.FS(www.FS)
	www_handler := http.FileServer(www_fs)

	mux.Handle("/", www_handler)

	addr := fmt.Sprintf("%s:%d", host, port)
	slog.Info("Listening for requests", "address", addr)

	http.ListenAndServe(addr, mux)
}

Error handling omitted for the sake of brevity.

The "nut" of it being this part:

	opts := &maps.AssignMapConfigHandlerOptions{
		MapProvider:       map_provider,
		MapTileURI:        map_tile_uri,
		InitialView:       initial_view,
		LeafletStyle:      leaflet_style,
		LeafletPointStyle: leaflet_point_style,
		ProtomapsTheme:    protomaps_theme,
	}

	maps.AssignMapConfigHandler(opts, mux, "/map.json")

Which populates the maps.AssignMapConfigHandlerOptions with map-specific command line flags and passes those options (along with your http.ServeMux instance) to the AssignMapConfigHandler ethod. This method will validate all the options and create a new map config http.Handler assigning it to the http.ServeMux instance.

If you are using Protomaps as your map provider and the corresponding MapTileURI starts with file:// it will be assumed that you are trying to serve a local Protomaps database and a matching http.Handler will assign to the http.ServeMux instance.

And then in your JavaScript code you would write something like this:

window.addEventListener("load", function load(event){

    // Null Island    
    var map = L.map('map').setView([0.0, 0.0], 1);    

    fetch("/map.json")
        .then((rsp) => rsp.json())
        .then((cfg) => {

	    console.debug("Got map config", cfg);
	    
            switch (cfg.provider) {
                case "leaflet":

                    var tile_url = cfg.tile_url;

                    var tile_layer = L.tileLayer(tile_url, {
                        maxZoom: 19,
                    });

                    tile_layer.addTo(map);
                    break;

                case "protomaps":

                    var tile_url = cfg.tile_url;

                    var tile_layer = protomapsL.leafletLayer({
                        url: tile_url,
                        theme: cfg.protomaps.theme,
                    })

                    tile_layer.addTo(map);
                    break;

                default:
                    console.error("Uknown or unsupported map provider");
                    return;
	    }

	    if (cfg.initial_view) {

		var zm = map.getZoom();

		if (cfg.initial_zoom){
		    zm = cfg.initial_zoom;
		}

		map.setView([cfg.initial_view[1], cfg.initial_view[0]], zm);
		
	    } else if (cfg.initial_bounds){

		var bounds = [
		    [ cfg.initial_bounds[1], cfg.initial_bounds[0] ],
		    [ cfg.initial_bounds[3], cfg.initial_bounds[2] ],
		];

		map.fitBounds(bounds);
	    }
	    
	    console.debug("Finished map setup");
	    
        }).catch((err) => {
	    console.error("Failed to derive map config", err);
	    return;
	});    
    
});

Example

Leaflet

$> make example
go run cmd/example/main.go \
		-initial-view '-122.384292,37.621131,13'
		
2025/03/06 10:00:09 INFO Listening for requests address=localhost:8080
Protomaps

$> make example-protomaps
go run cmd/example/main.go \
		-initial-view '-122.384292,37.621131,13' \
		-map-provider protomaps \
		-map-tile-uri 'file:///usr/local/go-http-maps/fixtures/sfo.pmtiles'
		
2025/03/06 09:59:05 INFO Listening for requests address=localhost:8080

Documentation

Index

Constants

View Source
const LEAFLET_OSM_TILE_URL = "https://tile.openstreetmap.org/{z}/{x}/{y}.png"
View Source
const PROTOMAPS_API_TILE_URL = "https://api.protomaps.com/tiles/v3/{z}/{x}/{y}.mvt?key={key}"

Variables

This section is empty.

Functions

func AssignMapConfigHandler

func AssignMapConfigHandler(opts *AssignMapConfigHandlerOptions, mux *http.ServeMux, map_cfg_uri string) error

AssignMapConfigHandler derives a new `MapConfig` and corresponding `http.Handler` instance from 'opts' assigning to 'mux' at 'map_cfg_uri'.

func MapConfigHandler

func MapConfigHandler(cfg *MapConfig) http.Handler

MapConfigHandler returns a new `http.Handler` that will return a JSON-encoded version of 'cfg'.

func ProtomapsFileHandlerFromPath

func ProtomapsFileHandlerFromPath(path string, prefix string) (string, http.Handler, error)

ProtomapsFileHandlerFromPath will take a path and create a http.FileServer handler instance for the files in its root directory. The handler is returned with a relative URI for the filename in 'path' to be assigned to a net/http ServeMux instance.

Types

type AssignMapConfigHandlerOptions

type AssignMapConfigHandlerOptions struct {
	// A string label indicating the map provider to use. Valid options are: leaflet, protomaps.
	MapProvider string
	// A valid Leaflet tile layer URI.
	MapTileURI string
	// A comma-separated string indicating the map's initial view. Valid options are: 'LON,LAT', 'LON,LAT,ZOOM' or 'MINX,MINY,MAXX,MAXY'.
	InitialView string
	// A custom Leaflet style definition for geometries. This may either be a JSON-encoded string or a path on disk.
	LeafletStyle string
	// A custom Leaflet style definition for points. This may either be a JSON-encoded string or a path on disk.
	LeafletPointStyle string
	// A valid Protomaps theme label.
	ProtomapsTheme string
}

AssignMapConfigHandlerOptions defines configuration options for the AssignMapConfigHandler method.

type InitialBounds

type InitialBounds [4]float64

func (*InitialBounds) String

func (v *InitialBounds) String() string

type InitialView

type InitialView [2]float64

func (*InitialView) String

func (v *InitialView) String() string

type LeafletConfig

type LeafletConfig struct {
	Style      *LeafletStyle `json:"style,omitempty"`
	PointStyle *LeafletStyle `json:"point_style,omitempty"`
}

type LeafletStyle

type LeafletStyle struct {
	Color       string  `json:"color,omitempty"`
	FillColor   string  `json:"fillColor,omitempty"`
	Weight      float64 `json:"weight,omitempty"`
	Opacity     float64 `json:"opacity,omitempty"`
	Radius      float64 `json:"radius,omitempty"`
	FillOpacity float64 `json:"fillOpacity,omitempty"`
}

LeafletStyle is a struct containing details for decorating GeoJSON features and markers

func UnmarshalLeafletStyle

func UnmarshalLeafletStyle(raw string) (*LeafletStyle, error)

UnmarshalLeafletStyle derives a `LeafletStyle` instance from 'raw'. If 'raw' starts with "{" then it is treated as a JSON-encoded string, otherwise it is treated as a local path on disk.

func UnmarshalStyleFromReader

func UnmarshalStyleFromReader(r io.Reader) (*LeafletStyle, error)

UnmarshalStyleFromString derives a `LeafletStyle` instance from the body of 'r'.

func UnmarshalStyleFromString

func UnmarshalStyleFromString(raw string) (*LeafletStyle, error)

UnmarshalStyleFromString derives a `LeafletStyle` instance from 'raw'.

type MapConfig

type MapConfig struct {
	// A valid map provider label.
	Provider string `json:"provider"`
	// A valid Leaflet tile layer URI.
	TileURL string `json:"tile_url"`
	// Optional Protomaps configuration details
	Protomaps *ProtomapsConfig `json:"protomaps,omitempty"`
	// Optional Leaflet configuration details
	Leaflet *LeafletConfig `json:"leaflet,omitempty"`
	// The initial view (lon, lat) for the map (optional).
	InitialView *InitialView `json:"initial_view,omitempty"`
	// The initial zoom level for the map (optional).
	InitialZoom int `json:"initial_zoom,omitempty"`
	// The initial bounds (minx, miny, maxx, maxy) for the map (optional).
	InitialBounds *InitialBounds `json:"initial_bounds,omitempty"`
}

MapConfig defines common configuration details for maps.

type ProtomapsConfig

type ProtomapsConfig struct {
	// A valid Protomaps theme label
	Theme string `json:"theme"`
}

ProtomapsConfig defines configuration details for maps using Protomaps.

Directories

Path Synopsis
cmd
static
www

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL