130 lines
3.8 KiB
Elm
130 lines
3.8 KiB
Elm
module TileMap exposing (tiles
|
|
, FineZoomLevel(..)
|
|
, ZoomLevel
|
|
, Coord
|
|
, TileMap(..)
|
|
, toCoord
|
|
, toZoom
|
|
, translate
|
|
, translatePixels
|
|
, incZoom
|
|
, bounds
|
|
, pixelsToCoord
|
|
, pixelFromCoord
|
|
, zoomStep)
|
|
|
|
import Html exposing (img, div)
|
|
import Html.Attributes as H exposing (src, style, width, height)
|
|
|
|
import Pos exposing (Pos)
|
|
|
|
type alias TileNumber = { x: Int, y: Int }
|
|
|
|
-- Coordinates in a Mercator projection
|
|
type alias Coord = { x: Float, y: Float }
|
|
|
|
-- zoom level
|
|
type alias ZoomLevel = Int
|
|
type FineZoomLevel = FineZoomLevel Int
|
|
|
|
|
|
type TileMap = TileMap Coord FineZoomLevel Int Int
|
|
|
|
zoomStep = 8
|
|
|
|
toZoom : FineZoomLevel -> ZoomLevel
|
|
toZoom (FineZoomLevel f) = f // zoomStep
|
|
|
|
|
|
incZoom : FineZoomLevel -> Int -> FineZoomLevel
|
|
incZoom (FineZoomLevel z) delta =
|
|
FineZoomLevel (clamp 0 (20 * zoomStep) (z + delta))
|
|
|
|
|
|
-- project lat/long to co-ordinates based on pseudocode at
|
|
-- https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames#Zoom_levels
|
|
|
|
sec x = 1 / (cos x)
|
|
|
|
toCoord : Pos -> Coord
|
|
toCoord pos =
|
|
let
|
|
lat_rad = pos.lat * pi / 180
|
|
x = (pos.lon + 180) / 360
|
|
y = (1 - (logBase e ((tan lat_rad) + (sec lat_rad))) / pi) / 2
|
|
in
|
|
Coord x y
|
|
|
|
pixelsToCoord z_ (x,y) =
|
|
let scale = 8 + toZoom z_
|
|
x_float = toFloat x / toFloat ( 2 ^ scale)
|
|
y_float = toFloat y / toFloat ( 2 ^ scale)
|
|
in Coord x_float y_float
|
|
|
|
reflect : Coord -> Coord
|
|
reflect c = Coord -c.x -c.y
|
|
|
|
-- used for Coords and for TileNumbers
|
|
translate base offset =
|
|
{ x = (base.x + offset.x), y = (base.y + offset.y) }
|
|
|
|
translatePixels : Coord -> FineZoomLevel -> (Int, Int) -> Coord
|
|
translatePixels old z_ (x, y) =
|
|
translate old (pixelsToCoord z_ (x, y))
|
|
|
|
tileCovering : Coord -> ZoomLevel -> TileNumber
|
|
tileCovering c z =
|
|
TileNumber (truncate (toFloat (2 ^ z) * c.x)) (truncate (toFloat (2 ^ z) * c.y))
|
|
|
|
pixelFromCoord : Coord -> FineZoomLevel -> (Int, Int)
|
|
pixelFromCoord c z_ =
|
|
let z = toZoom z_
|
|
{x,y} = tileCovering c (z + 8)
|
|
in (x,y)
|
|
|
|
boundingTiles : TileMap -> (TileNumber, TileNumber)
|
|
boundingTiles (TileMap centre z1 width height) =
|
|
-- find the tiles needed to cover the area (`width` x `height`)
|
|
-- about the point at `centre`
|
|
let z = toZoom z1
|
|
delta = pixelsToCoord z1 ((width // 2), (height // 2))
|
|
minCoord = translate centre (reflect delta)
|
|
maxCoord = translate centre delta
|
|
in ((tileCovering minCoord z),
|
|
(translate (tileCovering maxCoord z) (TileNumber 1 1)))
|
|
|
|
bounds : TileMap -> { left : Int, top : Int, width: Int, height: Int }
|
|
bounds tmap =
|
|
let (mintile, maxtile) = boundingTiles tmap
|
|
in {
|
|
left = mintile.x * 256,
|
|
top = mintile.y * 256,
|
|
width = (1 + maxtile.x - mintile.x) * 256,
|
|
height = (1 + maxtile.y - mintile.y) * 256
|
|
}
|
|
|
|
|
|
tileUrl : TileNumber -> ZoomLevel -> String
|
|
tileUrl {x,y} z =
|
|
String.concat ["https://a.tile.openstreetmap.org",
|
|
"/", String.fromInt z,
|
|
"/", String.fromInt x,
|
|
"/", String.fromInt y,
|
|
".png" ]
|
|
|
|
tileImg zoom tilenumber = img [ width 256,
|
|
height 256,
|
|
src (tileUrl tilenumber zoom) ] []
|
|
|
|
tiles tmap =
|
|
let (TileMap centre zoom width height) = tmap
|
|
(mintile, maxtile) = boundingTiles tmap
|
|
xs = List.range mintile.x maxtile.x
|
|
ys = List.range mintile.y maxtile.y
|
|
zoom_ = toZoom zoom
|
|
in
|
|
List.map
|
|
(\ y -> div []
|
|
(List.map (\ x -> tileImg zoom_ (TileNumber x y)) xs))
|
|
ys
|