TileMap.elm (3825B)
1 module TileMap exposing (tiles 2 , ZoomLevel(..) 3 , Coord 4 , TileMap(..) 5 , toCoord 6 , toZoom 7 , translate 8 , translatePixels 9 , incZoom 10 , bounds 11 , pixelsToCoord 12 , pixelFromCoord 13 , zoomStep) 14 15 import Html exposing (img, div) 16 import Html.Attributes as H exposing (src, style, width, height) 17 18 import Pos exposing (Pos) 19 20 type alias TileNumber = { x: Int, y: Int } 21 22 -- Coordinates in a Mercator projection 23 type alias Coord = { x: Float, y: Float } 24 25 -- zoom level 26 type alias TileZoomLevel = Int 27 type ZoomLevel = ZoomLevel Int 28 29 30 type TileMap = TileMap Coord ZoomLevel Int Int 31 32 zoomStep = 8 33 34 toZoom : ZoomLevel -> TileZoomLevel 35 toZoom (ZoomLevel f) = f // zoomStep 36 37 38 incZoom : ZoomLevel -> Int -> ZoomLevel 39 incZoom (ZoomLevel z) delta = 40 ZoomLevel (clamp 0 (20 * zoomStep) (z + delta)) 41 42 43 -- project lat/long to co-ordinates based on pseudocode at 44 -- https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames#Zoom_levels 45 46 sec x = 1 / (cos x) 47 48 toCoord : Pos -> Coord 49 toCoord pos = 50 let 51 lat_rad = pos.lat * pi / 180 52 x = (pos.lon + 180) / 360 53 y = (1 - (logBase e ((tan lat_rad) + (sec lat_rad))) / pi) / 2 54 in 55 Coord x y 56 57 pixelsToCoord z_ (x,y) = 58 let scale = 8 + toZoom z_ 59 x_float = toFloat x / toFloat ( 2 ^ scale) 60 y_float = toFloat y / toFloat ( 2 ^ scale) 61 in Coord x_float y_float 62 63 reflect : Coord -> Coord 64 reflect c = Coord -c.x -c.y 65 66 -- used for Coords and for TileNumbers 67 translate base offset = 68 { x = (base.x + offset.x), y = (base.y + offset.y) } 69 70 translatePixels : Coord -> ZoomLevel -> (Int, Int) -> Coord 71 translatePixels old z_ (x, y) = 72 translate old (pixelsToCoord z_ (x, y)) 73 74 tileCovering : Coord -> TileZoomLevel -> TileNumber 75 tileCovering c z = 76 TileNumber (truncate (toFloat (2 ^ z) * c.x)) (truncate (toFloat (2 ^ z) * c.y)) 77 78 pixelFromCoord : Coord -> ZoomLevel -> (Int, Int) 79 pixelFromCoord c z_ = 80 let z = toZoom z_ 81 {x,y} = tileCovering c (z + 8) 82 in (x,y) 83 84 boundingTiles : TileMap -> (TileNumber, TileNumber) 85 boundingTiles (TileMap centre z1 width height) = 86 -- find the tiles needed to cover the area (`width` x `height`) 87 -- about the point at `centre` 88 let z = toZoom z1 89 delta = pixelsToCoord z1 ((width // 2), (height // 2)) 90 minCoord = translate centre (reflect delta) 91 maxCoord = translate centre delta 92 in ((tileCovering minCoord z), 93 (translate (tileCovering maxCoord z) (TileNumber 1 1))) 94 95 bounds : TileMap -> { left : Int, top : Int, width: Int, height: Int } 96 bounds tmap = 97 let (mintile, maxtile) = boundingTiles tmap 98 in { 99 left = mintile.x * 256, 100 top = mintile.y * 256, 101 width = (1 + maxtile.x - mintile.x) * 256, 102 height = (1 + maxtile.y - mintile.y) * 256 103 } 104 105 106 tileUrl : TileNumber -> TileZoomLevel -> String 107 tileUrl {x,y} z = 108 String.concat ["https://a.tile.openstreetmap.org", 109 "/", String.fromInt z, 110 "/", String.fromInt x, 111 "/", String.fromInt y, 112 ".png" ] 113 114 tileImg zoom tilenumber = img [ width 256, 115 height 256, 116 src (tileUrl tilenumber zoom) ] [] 117 118 tiles tmap = 119 let (TileMap centre zoom width height) = tmap 120 (mintile, maxtile) = boundingTiles tmap 121 xs = List.range mintile.x maxtile.x 122 ys = List.range mintile.y maxtile.y 123 zoom_ = toZoom zoom 124 in 125 List.map 126 (\ y -> div [] 127 (List.map (\ x -> tileImg zoom_ (TileNumber x y)) xs)) 128 ys