mirror of
https://github.com/omniscale/magnacarto.git
synced 2025-02-23 16:24:09 +01:00
498 lines
13 KiB
Go
498 lines
13 KiB
Go
// Package mapnik renders beautiful maps with Mapnik.
|
|
package mapnik
|
|
|
|
// #include <stdlib.h>
|
|
// #include "mapnik_c_api.h"
|
|
import "C"
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"image"
|
|
"image/color"
|
|
"image/draw"
|
|
"io/ioutil"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"unsafe"
|
|
)
|
|
|
|
type LogLevel int
|
|
|
|
var (
|
|
None = LogLevel(C.MAPNIK_NONE)
|
|
Debug = LogLevel(C.MAPNIK_DEBUG)
|
|
Warn = LogLevel(C.MAPNIK_WARN)
|
|
Error = LogLevel(C.MAPNIK_ERROR)
|
|
)
|
|
|
|
var (
|
|
// You can overwrite defaults at compile time, eg:
|
|
// go build -ldflags "-X github.com/omniscale/go-mapnik/v2.fontPath $(mapnik-config -fonts)"
|
|
fontPath = "/usr/local/lib/mapnik/fonts"
|
|
pluginPath = "/usr/local/lib/mapnik/input"
|
|
)
|
|
|
|
func init() {
|
|
// Register default datasources path and fonts path like the Python bindings do.
|
|
if err := RegisterDatasources(pluginPath); err != nil {
|
|
fmt.Fprintf(os.Stderr, "MAPNIK: %s\n", err)
|
|
}
|
|
if err := RegisterFonts(fontPath); err != nil {
|
|
fmt.Fprintf(os.Stderr, "MAPNIK: %s\n", err)
|
|
}
|
|
}
|
|
|
|
// RegisterDatasources registers all input plugins found in the given path.
|
|
func RegisterDatasources(path string) error {
|
|
fileInfos, err := ioutil.ReadDir(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, file := range fileInfos {
|
|
cs := C.CString(filepath.Join(path, file.Name()))
|
|
defer C.free(unsafe.Pointer(cs))
|
|
// Register datasources one-by-one to avoid recursive_mutex assertion
|
|
// errors in register_datasources, triggered at least on MacOS 10.13
|
|
// with Mapnik 3.0.22
|
|
if C.mapnik_register_datasource(cs) == 0 {
|
|
e := C.GoString(C.mapnik_register_last_error())
|
|
if e != "" {
|
|
return errors.New("registering datasources: " + e)
|
|
}
|
|
return errors.New("error while registering datasources")
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// RegisterDatasources registers all fonts found in the given path.
|
|
func RegisterFonts(path string) error {
|
|
fileInfos, err := ioutil.ReadDir(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, file := range fileInfos {
|
|
fullPath := filepath.Join(path, file.Name())
|
|
if !isFontFile(fullPath) {
|
|
continue
|
|
}
|
|
cs := C.CString(fullPath)
|
|
defer C.free(unsafe.Pointer(cs))
|
|
// Register fonts one-by-one. See comment in RegisterDatasources.
|
|
if C.mapnik_register_font(cs) == 0 {
|
|
e := C.GoString(C.mapnik_register_last_error())
|
|
if e != "" {
|
|
return errors.New("registering fonts: " + e)
|
|
}
|
|
return errors.New("error while registering fonts")
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func isFontFile(path string) bool {
|
|
ext := strings.ToLower(filepath.Ext(path))
|
|
return (ext == ".ttf" ||
|
|
ext == ".otf" ||
|
|
ext == ".woff" ||
|
|
ext == ".ttc" ||
|
|
ext == ".pfa" ||
|
|
ext == ".pfb" ||
|
|
ext == ".dfont")
|
|
}
|
|
|
|
// LogSeverity sets the global log level for Mapnik. Requires a Mapnik build with logging enabled.
|
|
func LogSeverity(level LogLevel) {
|
|
C.mapnik_logging_set_severity(C.int(level))
|
|
}
|
|
|
|
type version struct {
|
|
Numeric int
|
|
Major int
|
|
Minor int
|
|
Patch int
|
|
String string
|
|
}
|
|
|
|
var Version version
|
|
|
|
func init() {
|
|
Version.Numeric = int(C.mapnik_version)
|
|
Version.Major = int(C.mapnik_version_major)
|
|
Version.Minor = int(C.mapnik_version_minor)
|
|
Version.Patch = int(C.mapnik_version_patch)
|
|
Version.String = fmt.Sprintf("%d.%d.%d", Version.Major, Version.Minor, Version.Patch)
|
|
}
|
|
|
|
// Map base type
|
|
type Map struct {
|
|
m *C.struct__mapnik_map_t
|
|
width int
|
|
height int
|
|
layerStatus []bool
|
|
}
|
|
|
|
// New initializes a new Map.
|
|
func New() *Map {
|
|
return &Map{
|
|
m: C.mapnik_map(C.uint(800), C.uint(600)),
|
|
width: 800,
|
|
height: 600,
|
|
}
|
|
}
|
|
|
|
// NewSized initializes a new Map with the given size.
|
|
func NewSized(width, height int) *Map {
|
|
return &Map{
|
|
m: C.mapnik_map(C.uint(width), C.uint(height)),
|
|
width: width,
|
|
height: height,
|
|
}
|
|
}
|
|
|
|
func (m *Map) lastError() error {
|
|
return errors.New("mapnik: " + C.GoString(C.mapnik_map_last_error(m.m)))
|
|
}
|
|
|
|
// Load reads in a Mapnik map XML.
|
|
//
|
|
// Note: Since Mapnik 3 all layers with status="off" are not loaded and cannot
|
|
// be activated by a custom LayerSelector. As a workaround, all layers with names
|
|
// starting with '__OFF__' are disabled on load and the '__OFF__' prefix is removed
|
|
// from the layer name.
|
|
func (m *Map) Load(stylesheet string) error {
|
|
cs := C.CString(stylesheet)
|
|
defer C.free(unsafe.Pointer(cs))
|
|
if C.mapnik_map_load(m.m, cs) != 0 {
|
|
return m.lastError()
|
|
}
|
|
|
|
C.mapnik_apply_layer_off_hack(m.m)
|
|
return nil
|
|
}
|
|
|
|
// Resize changes the map size in pixel.
|
|
// Sizes larger than 16k pixels are ignored by Mapnik. Use NewSized
|
|
// to initialize larger maps.
|
|
func (m *Map) Resize(width, height int) {
|
|
C.mapnik_map_resize(m.m, C.uint(width), C.uint(height))
|
|
m.width = width
|
|
m.height = height
|
|
}
|
|
|
|
// Free deallocates the map.
|
|
func (m *Map) Free() {
|
|
C.mapnik_map_free(m.m)
|
|
m.m = nil
|
|
}
|
|
|
|
// SRS returns the projection of the map.
|
|
func (m *Map) SRS() string {
|
|
return C.GoString(C.mapnik_map_get_srs(m.m))
|
|
}
|
|
|
|
// SetSRS sets the projection of the map as a proj4 string ('+init=epsg:4326', etc).
|
|
func (m *Map) SetSRS(srs string) {
|
|
cs := C.CString(srs)
|
|
defer C.free(unsafe.Pointer(cs))
|
|
C.mapnik_map_set_srs(m.m, cs)
|
|
}
|
|
|
|
// ScaleDenominator returns the current scale denominator. Call after Resize and ZoomAll/ZoomTo.
|
|
func (m *Map) ScaleDenominator() float64 {
|
|
return float64(C.mapnik_map_get_scale_denominator(m.m))
|
|
}
|
|
|
|
// ZoomAll zooms to the maximum extent.
|
|
func (m *Map) ZoomAll() error {
|
|
if C.mapnik_map_zoom_all(m.m) != 0 {
|
|
return m.lastError()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ZoomTo zooms to the given bounding box.
|
|
func (m *Map) ZoomTo(minx, miny, maxx, maxy float64) {
|
|
bbox := C.mapnik_bbox(C.double(minx), C.double(miny), C.double(maxx), C.double(maxy))
|
|
defer C.mapnik_bbox_free(bbox)
|
|
C.mapnik_map_zoom_to_box(m.m, bbox)
|
|
}
|
|
|
|
func (m *Map) BackgroundColor() color.NRGBA {
|
|
c := color.NRGBA{}
|
|
C.mapnik_map_background(m.m, (*C.uint8_t)(&c.R), (*C.uint8_t)(&c.G), (*C.uint8_t)(&c.B), (*C.uint8_t)(&c.A))
|
|
return c
|
|
}
|
|
|
|
func (m *Map) SetBackgroundColor(c color.NRGBA) {
|
|
C.mapnik_map_set_background(m.m, C.uint8_t(c.R), C.uint8_t(c.G), C.uint8_t(c.B), C.uint8_t(c.A))
|
|
}
|
|
|
|
func (m *Map) printLayerStatus() {
|
|
n := C.mapnik_map_layer_count(m.m)
|
|
for i := 0; i < int(n); i++ {
|
|
fmt.Println(
|
|
C.GoString(C.mapnik_map_layer_name(m.m, C.size_t(i))),
|
|
C.mapnik_map_layer_is_active(m.m, C.size_t(i)),
|
|
)
|
|
}
|
|
}
|
|
|
|
func (m *Map) storeLayerStatus() {
|
|
if len(m.layerStatus) > 0 {
|
|
return // allready stored
|
|
}
|
|
m.layerStatus = m.currentLayerStatus()
|
|
}
|
|
|
|
func (m *Map) currentLayerStatus() []bool {
|
|
n := C.mapnik_map_layer_count(m.m)
|
|
active := make([]bool, n)
|
|
for i := 0; i < int(n); i++ {
|
|
if C.mapnik_map_layer_is_active(m.m, C.size_t(i)) == 1 {
|
|
active[i] = true
|
|
}
|
|
}
|
|
return active
|
|
}
|
|
|
|
func (m *Map) resetLayerStatus() {
|
|
if len(m.layerStatus) == 0 {
|
|
return // not stored
|
|
}
|
|
n := C.mapnik_map_layer_count(m.m)
|
|
if int(n) > len(m.layerStatus) {
|
|
// should not happen
|
|
return
|
|
}
|
|
for i := 0; i < int(n); i++ {
|
|
if m.layerStatus[i] {
|
|
C.mapnik_map_layer_set_active(m.m, C.size_t(i), 1)
|
|
} else {
|
|
C.mapnik_map_layer_set_active(m.m, C.size_t(i), 0)
|
|
}
|
|
}
|
|
m.layerStatus = nil
|
|
}
|
|
|
|
// Status defines if a layer should be rendered or not.
|
|
type Status int
|
|
|
|
const (
|
|
// Exclude layer from rendering.
|
|
Exclude Status = -1
|
|
// Default keeps layer at the current rendering status.
|
|
Default Status = 0
|
|
// Include layer for rendering.
|
|
Include Status = 1
|
|
)
|
|
|
|
type LayerSelector interface {
|
|
Select(layername string) Status
|
|
}
|
|
|
|
type SelectorFunc func(string) Status
|
|
|
|
func (f SelectorFunc) Select(layername string) Status {
|
|
return f(layername)
|
|
}
|
|
|
|
// SelectLayers enables/disables single layers. LayerSelector or SelectorFunc gets called for each layer.
|
|
// Returns true if at least one layer was included (or set to default).
|
|
func (m *Map) SelectLayers(selector LayerSelector) bool {
|
|
m.storeLayerStatus()
|
|
selected := false
|
|
n := C.mapnik_map_layer_count(m.m)
|
|
for i := 0; i < int(n); i++ {
|
|
layerName := C.GoString(C.mapnik_map_layer_name(m.m, C.size_t(i)))
|
|
switch selector.Select(layerName) {
|
|
case Include:
|
|
selected = true
|
|
C.mapnik_map_layer_set_active(m.m, C.size_t(i), 1)
|
|
case Exclude:
|
|
C.mapnik_map_layer_set_active(m.m, C.size_t(i), 0)
|
|
case Default:
|
|
selected = true
|
|
}
|
|
}
|
|
return selected
|
|
}
|
|
|
|
// ResetLayer resets all layers to the initial status.
|
|
func (m *Map) ResetLayers() {
|
|
m.resetLayerStatus()
|
|
}
|
|
|
|
func (m *Map) SetMaxExtent(minx, miny, maxx, maxy float64) {
|
|
C.mapnik_map_set_maximum_extent(m.m, C.double(minx), C.double(miny), C.double(maxx), C.double(maxy))
|
|
}
|
|
|
|
func (m *Map) ResetMaxExtent() {
|
|
C.mapnik_map_reset_maximum_extent(m.m)
|
|
}
|
|
|
|
// RenderOpts defines rendering options.
|
|
type RenderOpts struct {
|
|
// Scale renders the map at a fixed scale denominator.
|
|
Scale float64
|
|
// ScaleFactor renders the map with larger fonts sizes, line width, etc. For printing or retina/hq iamges.
|
|
ScaleFactor float64
|
|
// Format for the rendered image ('jpeg80', 'png256', etc. see: https://github.com/mapnik/mapnik/wiki/Image-IO)
|
|
Format string
|
|
}
|
|
|
|
// Render returns the map as an encoded image.
|
|
func (m *Map) Render(opts RenderOpts) ([]byte, error) {
|
|
scaleFactor := opts.ScaleFactor
|
|
if scaleFactor == 0.0 {
|
|
scaleFactor = 1.0
|
|
}
|
|
i := C.mapnik_map_render_to_image(m.m, C.double(opts.Scale), C.double(scaleFactor))
|
|
if i == nil {
|
|
return nil, m.lastError()
|
|
}
|
|
defer C.mapnik_image_free(i)
|
|
if opts.Format == "raw" {
|
|
size := 0
|
|
raw := C.mapnik_image_to_raw(i, (*C.size_t)(unsafe.Pointer(&size)))
|
|
return C.GoBytes(unsafe.Pointer(raw), C.int(size)), nil
|
|
}
|
|
var format *C.char
|
|
if opts.Format != "" {
|
|
format = C.CString(opts.Format)
|
|
} else {
|
|
format = C.CString("png256")
|
|
}
|
|
b := C.mapnik_image_to_blob(i, format)
|
|
if b == nil {
|
|
return nil, errors.New("mapnik: " + C.GoString(C.mapnik_image_last_error(i)))
|
|
}
|
|
C.free(unsafe.Pointer(format))
|
|
defer C.mapnik_image_blob_free(b)
|
|
return C.GoBytes(unsafe.Pointer(b.ptr), C.int(b.len)), nil
|
|
}
|
|
|
|
// RenderImage returns the map as an unencoded image.Image.
|
|
func (m *Map) RenderImage(opts RenderOpts) (*image.NRGBA, error) {
|
|
scaleFactor := opts.ScaleFactor
|
|
if scaleFactor == 0.0 {
|
|
scaleFactor = 1.0
|
|
}
|
|
i := C.mapnik_map_render_to_image(m.m, C.double(opts.Scale), C.double(scaleFactor))
|
|
if i == nil {
|
|
return nil, m.lastError()
|
|
}
|
|
defer C.mapnik_image_free(i)
|
|
size := 0
|
|
raw := C.mapnik_image_to_raw(i, (*C.size_t)(unsafe.Pointer(&size)))
|
|
b := C.GoBytes(unsafe.Pointer(raw), C.int(size))
|
|
img := &image.NRGBA{
|
|
Pix: b,
|
|
Stride: int(m.width * 4),
|
|
Rect: image.Rect(0, 0, int(m.width), int(m.height)),
|
|
}
|
|
return img, nil
|
|
}
|
|
|
|
// RenderToFile writes the map as an encoded image to the file system.
|
|
func (m *Map) RenderToFile(opts RenderOpts, path string) error {
|
|
scaleFactor := opts.ScaleFactor
|
|
if scaleFactor == 0.0 {
|
|
scaleFactor = 1.0
|
|
}
|
|
cs := C.CString(path)
|
|
defer C.free(unsafe.Pointer(cs))
|
|
var format *C.char
|
|
if opts.Format != "" {
|
|
format = C.CString(opts.Format)
|
|
} else {
|
|
format = C.CString("png256")
|
|
}
|
|
defer C.free(unsafe.Pointer(format))
|
|
if C.mapnik_map_render_to_file(m.m, cs, C.double(opts.Scale), C.double(scaleFactor), format) != 0 {
|
|
return m.lastError()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// SetBufferSize sets the pixel buffer at the map image edges where Mapnik should not render any labels.
|
|
func (m *Map) SetBufferSize(s int) {
|
|
C.mapnik_map_set_buffer_size(m.m, C.int(s))
|
|
}
|
|
|
|
// Encode image.Image with Mapniks image encoder.
|
|
// This is optimized for *image.NRGBA or *image.RGBA.
|
|
func Encode(img image.Image, format string) ([]byte, error) {
|
|
tmp := toNRGBA(img)
|
|
i := C.mapnik_image_from_raw(
|
|
(*C.uint8_t)(unsafe.Pointer(&tmp.Pix[0])),
|
|
C.int(tmp.Bounds().Dx()),
|
|
C.int(tmp.Bounds().Dy()),
|
|
)
|
|
defer C.mapnik_image_free(i)
|
|
|
|
cformat := C.CString(format)
|
|
b := C.mapnik_image_to_blob(i, cformat)
|
|
if b == nil {
|
|
return nil, errors.New("mapnik: " + C.GoString(C.mapnik_image_last_error(i)))
|
|
}
|
|
C.free(unsafe.Pointer(cformat))
|
|
defer C.mapnik_image_blob_free(b)
|
|
return C.GoBytes(unsafe.Pointer(b.ptr), C.int(b.len)), nil
|
|
}
|
|
|
|
func toNRGBA(src image.Image) *image.NRGBA {
|
|
switch src := src.(type) {
|
|
case *image.NRGBA:
|
|
return src
|
|
case *image.RGBA:
|
|
result := image.NewNRGBA(src.Bounds())
|
|
drawRGBAOver(result, result.Bounds(), src, image.ZP)
|
|
return result
|
|
default:
|
|
result := image.NewNRGBA(src.Bounds())
|
|
draw.Draw(result, result.Bounds(), src, image.ZP, draw.Over)
|
|
return result
|
|
}
|
|
}
|
|
|
|
func drawRGBAOver(dst *image.NRGBA, r image.Rectangle, src *image.RGBA, sp image.Point) {
|
|
i0 := (r.Min.X - dst.Rect.Min.X) * 4
|
|
i1 := (r.Max.X - dst.Rect.Min.X) * 4
|
|
si0 := (sp.X - src.Rect.Min.X) * 4
|
|
yMax := r.Max.Y - dst.Rect.Min.Y
|
|
|
|
y := r.Min.Y - dst.Rect.Min.Y
|
|
sy := sp.Y - src.Rect.Min.Y
|
|
for ; y != yMax; y, sy = y+1, sy+1 {
|
|
dpix := dst.Pix[y*dst.Stride:]
|
|
spix := src.Pix[sy*src.Stride:]
|
|
|
|
for i, si := i0, si0; i < i1; i, si = i+4, si+4 {
|
|
sr := spix[si+0]
|
|
sg := spix[si+1]
|
|
sb := spix[si+2]
|
|
sa := spix[si+3]
|
|
|
|
// reverse pre-multiplication adapted from color.NRGBAModel
|
|
if sa == 0xff {
|
|
dpix[i+0] = sr
|
|
dpix[i+1] = sg
|
|
dpix[i+2] = sb
|
|
} else if sa == 0x00 {
|
|
dpix[i+0] = 0
|
|
dpix[i+1] = 0
|
|
dpix[i+2] = 0
|
|
} else {
|
|
dpix[i+0] = uint8(((uint32(sr) * 0xffff) / uint32(sa)) >> 8)
|
|
dpix[i+1] = uint8(((uint32(sg) * 0xffff) / uint32(sa)) >> 8)
|
|
dpix[i+2] = uint8(((uint32(sb) * 0xffff) / uint32(sa)) >> 8)
|
|
}
|
|
dpix[i+3] = sa
|
|
}
|
|
}
|
|
}
|