Export Transformer (#43)

Export Transformer
This commit is contained in:
Balázs Lécz 2020-10-17 08:48:34 +01:00 committed by GitHub
parent 3465b33495
commit 112a0c57b9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 50 additions and 24 deletions

View File

@ -84,7 +84,7 @@ func (p *Area) bounds() s2.Rect {
return r
}
func (p *Area) draw(gc *gg.Context, trans *transformer) {
func (p *Area) draw(gc *gg.Context, trans *Transformer) {
if len(p.Positions) <= 1 {
return
}
@ -94,7 +94,7 @@ func (p *Area) draw(gc *gg.Context, trans *transformer) {
gc.SetLineCap(gg.LineCapRound)
gc.SetLineJoin(gg.LineJoinRound)
for _, ll := range p.Positions {
gc.LineTo(trans.ll2p(ll))
gc.LineTo(trans.LatLngToXY(ll))
}
gc.ClosePath()
gc.SetColor(p.Fill)

View File

@ -106,15 +106,15 @@ func (m *Circle) bounds() s2.Rect {
return r
}
func (m *Circle) draw(gc *gg.Context, trans *transformer) {
func (m *Circle) draw(gc *gg.Context, trans *Transformer) {
if !CanDisplay(m.Position) {
log.Printf("Circle coordinates not displayable: %f/%f", m.Position.Lat.Degrees(), m.Position.Lng.Degrees())
return
}
ll := m.getLatLng(true)
x, y := trans.ll2p(m.Position)
x1, y1 := trans.ll2p(ll)
x, y := trans.LatLngToXY(m.Position)
x1, y1 := trans.LatLngToXY(ll)
radius := math.Sqrt(math.Pow(x1-x, 2) + math.Pow(y1-y, 2))
gc.ClearPath()
gc.SetLineWidth(m.Weight)

View File

@ -263,7 +263,8 @@ func (m *Context) determineZoomCenter() (int, s2.LatLng, error) {
return 0, s2.LatLngFromDegrees(0, 0), errors.New("cannot determine map extent: no center coordinates given, no bounding box given, no content (markers, paths, areas) given")
}
type transformer struct {
// Transformer implements coordinate transformation from latitude longitude to image pixel coordinates.
type Transformer struct {
zoom int
numTiles float64 // number of tiles per dimension at this zoom level
tileSize int // tile size in pixels from this provider
@ -275,8 +276,18 @@ type transformer struct {
pMinX, pMaxX int
}
func newTransformer(width int, height int, zoom int, llCenter s2.LatLng, tileSize int) *transformer {
t := new(transformer)
// Transformer returns an initialized Transformer instance.
func (m *Context) Transformer() (*Transformer, error) {
zoom, center, err := m.determineZoomCenter()
if err != nil {
return nil, err
}
return newTransformer(m.width, m.height, zoom, center, m.tileProvider.TileSize), nil
}
func newTransformer(width int, height int, zoom int, llCenter s2.LatLng, tileSize int) *Transformer {
t := new(Transformer)
t.zoom = zoom
t.numTiles = math.Exp2(float64(t.zoom))
@ -311,13 +322,14 @@ func newTransformer(width int, height int, zoom int, llCenter s2.LatLng, tileSiz
}
// ll2t returns fractional tile index for a lat/lng points
func (t *transformer) ll2t(ll s2.LatLng) (float64, float64) {
func (t *Transformer) ll2t(ll s2.LatLng) (float64, float64) {
x := t.numTiles * (ll.Lng.Degrees() + 180.0) / 360.0
y := t.numTiles * (1 - math.Log(math.Tan(ll.Lat.Radians())+(1.0/math.Cos(ll.Lat.Radians())))/math.Pi) / 2.0
return x, y
}
func (t *transformer) ll2p(ll s2.LatLng) (float64, float64) {
// LatLngToXY transforms a latitude longitude pair into image x, y coordinates.
func (t *Transformer) LatLngToXY(ll s2.LatLng) (float64, float64) {
x, y := t.ll2t(ll)
x = float64(t.pCenterX) + (x-t.tCenterX)*float64(t.tileSize)
y = float64(t.pCenterY) + (y-t.tCenterY)*float64(t.tileSize)
@ -335,8 +347,8 @@ func (t *transformer) ll2p(ll s2.LatLng) (float64, float64) {
return x, y
}
// Rect returns an s2.Rect bounding box around the set of tiles described by transformer
func (t *transformer) Rect() (bbox s2.Rect) {
// Rect returns an s2.Rect bounding box around the set of tiles described by Transformer.
func (t *Transformer) Rect() (bbox s2.Rect) {
// transform from https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames#Go
invNumTiles := 1.0 / t.numTiles
// Get latitude bounds
@ -418,15 +430,15 @@ func (m *Context) Render() (image.Image, error) {
return croppedImg, nil
}
// RenderWithBounds actually renders the map image including all map objects (markers, paths, areas).
// RenderWithTransformer actually renders the map image including all map objects (markers, paths, areas).
// The returned image covers requested area as well as any tiles necessary to cover that area, which may
// be larger than the request.
//
// Specific bounding box of returned image is provided to support image registration with other data
func (m *Context) RenderWithBounds() (image.Image, s2.Rect, error) {
// A Transformer is returned to support image registration with other data.
func (m *Context) RenderWithTransformer() (image.Image, *Transformer, error) {
zoom, center, err := m.determineZoomCenter()
if err != nil {
return nil, s2.Rect{}, err
return nil, nil, err
}
tileSize := m.tileProvider.TileSize
@ -445,7 +457,7 @@ func (m *Context) RenderWithBounds() (image.Image, s2.Rect, error) {
for _, layer := range layers {
if err := m.renderLayer(gc, zoom, trans, tileSize, layer); err != nil {
return nil, s2.Rect{}, err
return nil, nil, err
}
}
@ -465,7 +477,7 @@ func (m *Context) RenderWithBounds() (image.Image, s2.Rect, error) {
// draw attribution
if m.tileProvider.Attribution == "" {
return img, trans.Rect(), nil
return img, trans, nil
}
_, textHeight := gc.MeasureString(m.tileProvider.Attribution)
boxHeight := textHeight + 4.0
@ -475,10 +487,24 @@ func (m *Context) RenderWithBounds() (image.Image, s2.Rect, error) {
gc.SetRGBA(1.0, 1.0, 1.0, 0.75)
gc.DrawString(m.tileProvider.Attribution, 4.0, float64(m.height)-4.0)
return img, trans, nil
}
// RenderWithBounds actually renders the map image including all map objects (markers, paths, areas).
// The returned image covers requested area as well as any tiles necessary to cover that area, which may
// be larger than the request.
//
// Specific bounding box of returned image is provided to support image registration with other data
func (m *Context) RenderWithBounds() (image.Image, s2.Rect, error) {
img, trans, err := m.RenderWithTransformer()
if err != nil {
return nil, s2.Rect{}, err
}
return img, trans.Rect(), nil
}
func (m *Context) renderLayer(gc *gg.Context, zoom int, trans *transformer, tileSize int, provider *TileProvider) error {
func (m *Context) renderLayer(gc *gg.Context, zoom int, trans *Transformer, tileSize int, provider *TileProvider) error {
t := NewTileFetcher(provider, m.cache)
if m.userAgent != "" {
t.SetUserAgent(m.userAgent)

View File

@ -14,7 +14,7 @@ import (
type MapObject interface {
bounds() s2.Rect
extraMarginPixels() float64
draw(dc *gg.Context, trans *transformer)
draw(dc *gg.Context, trans *Transformer)
}
// CanDisplay checks if pos is generally displayable (i.e. its latitude is in [-85,85])

View File

@ -122,7 +122,7 @@ func (m *Marker) bounds() s2.Rect {
return r
}
func (m *Marker) draw(gc *gg.Context, trans *transformer) {
func (m *Marker) draw(gc *gg.Context, trans *Transformer) {
if !CanDisplay(m.Position) {
log.Printf("Marker coordinates not displayable: %f/%f", m.Position.Lat.Degrees(), m.Position.Lng.Degrees())
return
@ -133,7 +133,7 @@ func (m *Marker) draw(gc *gg.Context, trans *transformer) {
gc.SetLineWidth(1.0)
radius := 0.5 * m.Size
x, y := trans.ll2p(m.Position)
x, y := trans.LatLngToXY(m.Position)
gc.DrawArc(x, y-m.Size, radius, (90.0+60.0)*math.Pi/180.0, (360.0+90.0-60.0)*math.Pi/180.0)
gc.LineTo(x, y)
gc.ClosePath()

View File

@ -96,7 +96,7 @@ func (p *Path) bounds() s2.Rect {
return r
}
func (p *Path) draw(gc *gg.Context, trans *transformer) {
func (p *Path) draw(gc *gg.Context, trans *Transformer) {
if len(p.Positions) <= 1 {
return
}
@ -106,7 +106,7 @@ func (p *Path) draw(gc *gg.Context, trans *transformer) {
gc.SetLineCap(gg.LineCapRound)
gc.SetLineJoin(gg.LineJoinRound)
for _, ll := range p.Positions {
gc.LineTo(trans.ll2p(ll))
gc.LineTo(trans.LatLngToXY(ll))
}
gc.SetColor(p.Color)
gc.Stroke()