├── .gitignore ├── Makefile ├── README.md ├── queries ├── rail-commuter.sparql ├── rail.sparql ├── subway-lightrail.sparql └── tram.sparql ├── tiles ├── Leaflet.VectorGrid.js ├── index.html ├── leaflet.css └── leaflet.js └── web ├── Leaflet.VectorGrid.js ├── index.html ├── leaflet.css └── leaflet.js /.gitignore: -------------------------------------------------------------------------------- 1 | *.json 2 | .* 3 | components 4 | tiles 5 | stats 6 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | OCTI=octi --write-stats 2 | LOOM=loom --write-stats 3 | TOPO=topo --write-stats --random-colors --turn-restr-full-turn-pen 1000 4 | TRANSITMAP=transitmap --print-stats 5 | 6 | TRAMSTATS_GEO = $(patsubst %,stats/tram/%/geo.stats.json, 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0) 7 | TRAMSTATS_OCTI = $(patsubst %,stats/tram/%/octi.stats.json, 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0) 8 | TRAMSTATS_OCTIGEO = $(patsubst %,stats/tram/%/octi-geo.stats.json, 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0) 9 | TRAMSTATS_ORTHORAD = $(patsubst %,stats/tram/%/orthorad.stats.json, 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0) 10 | 11 | SUBWAY_LIGHTRAILSTATS_GEO = $(patsubst %,stats/subway-lightrail/%/geo.stats.json, 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0) 12 | SUBWAY_LIGHTRAILSTATS_OCTI = $(patsubst %,stats/subway-lightrail/%/octi.stats.json, 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0) 13 | SUBWAY_LIGHTRAILSTATS_OCTIGEO = $(patsubst %,stats/subway-lightrail/%/octi-geo.stats.json, 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0) 14 | SUBWAY_LIGHTRAILSTATS_ORTHORAD = $(patsubst %,stats/subway-lightrail/%/orthorad.stats.json, 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0) 15 | 16 | RAIL_COMMUTERSTATS_GEO = $(patsubst %,stats/rail-commuter/%/geo.stats.json, 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0) 17 | RAIL_COMMUTERSTATS_OCTI = $(patsubst %,stats/rail-commuter/%/octi.stats.json, 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0) 18 | RAIL_COMMUTERSTATS_OCTIGEO = $(patsubst %,stats/rail-commuter/%/octi-geo.stats.json, 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0) 19 | RAIL_COMMUTERSTATS_ORTHORAD = $(patsubst %,stats/rail-commuter/%/orthorad.stats.json, 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0) 20 | 21 | RAILSTATS_GEO = $(patsubst %,stats/rail/%/geo.stats.json, 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0) 22 | RAILSTATS_OCTI = $(patsubst %,stats/rail/%/octi.stats.json, 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0) 23 | RAILSTATS_OCTIGEO = $(patsubst %,stats/rail/%/octi-geo.stats.json, 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0) 24 | RAILSTATS_ORTHORAD = $(patsubst %,stats/rail/%/orthorad.stats.json, 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0) 25 | 26 | all: tram subway-lightrail rail-commuter rail 27 | 28 | tram: $(TRAMSTATS_GEO) $(TRAMSTATS_OCTI) $(TRAMSTATS_OCTIGEO) $(TRAMSTATS_ORTHORAD) 29 | subway-lightrail: $(SUBWAY_LIGHTRAILSTATS_GEO) $(SUBWAY_LIGHTRAILSTATS_OCTI) $(SUBWAY_LIGHTRAILSTATS_OCTIGEO) $(SUBWAY_LIGHTRAILSTATS_ORTHORAD) 30 | rail-commuter: $(RAIL_COMMUTERSTATS_GEO) $(RAIL_COMMUTERSTATS_OCTI) $(RAIL_COMMUTERSTATS_OCTIGEO) $(RAIL_COMMUTERSTATS_ORTHORAD) 31 | rail: $(RAILSTATS_GEO) $(RAILSTATS_OCTI) $(RAILSTATS_OCTIGEO) $(RAILSTATS_ORTHORAD) 32 | 33 | 34 | # keep all intermediate files 35 | .SECONDARY: 36 | 37 | stats/%/query.stats.json: %.json 38 | @: 39 | 40 | %.json: queries/%.sparql 41 | mkdir -p stats/$* 42 | echo "{\"query-time\":" > stats/$*/query.stats.json 43 | @# get qid 44 | /usr/bin/time --format="%e" bash -c 'curl -s -G --data-urlencode "backend=https://qlever.cs.uni-freiburg.de/api/osm-planet" --data-urlencode "query@$<" "https://qlever.cs.uni-freiburg.de/mapui-petri/query" > response.json' 2>> stats/$*/query.stats.json 45 | 46 | @# download GeoJSON export 47 | echo ",\"transfer-time\": " >> stats/$*/query.stats.json 48 | /usr/bin/time --format="%e" bash -c 'curl -s -G --data "id=`jq .qid response.json | tr -d '\''\"'\''`" "https://qlever.cs.uni-freiburg.de/mapui-petri/export" > $@' 2>> stats/$*/query.stats.json 49 | rm response.json 50 | echo "}" >> stats/$*/query.stats.json 51 | 52 | %.inputstats.json: %.json 53 | echo "{\"num_nds\" : `grep Point $< | wc -l`, \"num_edgs\" : `grep LineString $< | wc -l` }" > $@ 54 | 55 | # TOPO 56 | # ============ 57 | 58 | rail.15.topo.json: rail.json 59 | mkdir -p components/rail/15 60 | $(TOPO) --max-comp-dist 1000 --sample-dist 10 --write-components --write-components-path components/rail/15 -d 100 --infer-restr-max-dist 400 --turn-restr-full-turn-angle 90 < $< > tmp.json 61 | $(TOPO) --max-comp-dist 1000 --sample-dist 10 --write-components --smooth 60 --write-components-path components/rail/15 -d 200 --infer-restr-max-dist 400 --turn-restr-full-turn-angle 90 < tmp.json > $@ 62 | 63 | rail.%.topo.json: rail.15.topo.json 64 | ln -s $< $@ 65 | ln -s 15 components/rail/$* 66 | 67 | rail-commuter.15.topo.json: rail-commuter.json 68 | mkdir -p components/rail-commuter/15 69 | $(TOPO) --write-components --write-components-path components/rail-commuter/15 -d 100 --infer-restr-max-dist 400 --turn-restr-full-turn-angle 90 < $< > tmp.json 70 | $(TOPO) --write-components --write-components-path components/rail-commuter/15 --smooth 60 -d 200 --infer-restr-max-dist 400 --turn-restr-full-turn-angle 90 < tmp.json > $@ 71 | 72 | rail-commuter.%.topo.json: rail-commuter.15.topo.json 73 | ln -s $< $@ 74 | ln -s 15 components/rail-commuter/$* 75 | 76 | %.20.topo.json: %.15.topo.json 77 | ln -s $< $@ 78 | ln -s 15 components/$*/20 79 | 80 | %.19.topo.json: %.15.topo.json 81 | ln -s $< $@ 82 | ln -s 15 components/$*/19 83 | 84 | %.18.topo.json: %.15.topo.json 85 | ln -s $< $@ 86 | ln -s 15 components/$*/18 87 | 88 | %.17.topo.json: %.15.topo.json 89 | ln -s $< $@ 90 | ln -s 15 components/$*/17 91 | 92 | %.16.topo.json: %.15.topo.json 93 | ln -s $< $@ 94 | ln -s 15 components/$*/16 95 | 96 | %.15.topo.json: %.json 97 | mkdir -p components/$*/15 98 | $(TOPO) --write-components --write-components-path components/$*/15 --smooth 2 -d 100 --turn-restr-full-turn-angle 45 < $< > $@ 99 | 100 | %.14.topo.json: %.15.topo.json 101 | mkdir -p components/$*/14 102 | $(TOPO) --write-components --write-components-path components/$*/14 --smooth 2 -d 200 --turn-restr-full-turn-angle 45 < $< > $@ 103 | 104 | .SECONDEXPANSION: 105 | %.topo.json: $$(firstword $$(subst ., , $$*)).14.topo.json | dummy% 106 | ln -s $< $@ 107 | ln -s 14 components/$(firstword $(subst ., , $*))/$(lastword $(subst ., , $*)) 108 | 109 | # requried to make to catch-all rules above less attractive, see 110 | # https://stackoverflow.com/questions/18716736/changing-the-priority-of-rules-in-make/18726681#18726681 111 | dummy%: 112 | @: 113 | 114 | ######### 115 | 116 | 117 | %.20.loom.json: %.15.loom.json 118 | ln -s $< $@ 119 | 120 | %.19.loom.json: %.15.loom.json 121 | ln -s $< $@ 122 | 123 | %.18.loom.json: %.15.loom.json 124 | ln -s $< $@ 125 | 126 | %.17.loom.json: %.15.loom.json 127 | ln -s $< $@ 128 | 129 | %.16.loom.json: %.15.loom.json 130 | ln -s $< $@ 131 | 132 | %.15.loom.json: %.15.topo.json 133 | $(LOOM) -m anneal < $< > $@ 134 | 135 | %.14.loom.json: %.14.topo.json 136 | $(LOOM) -m anneal < $< > $@ 137 | 138 | .SECONDEXPANSION: 139 | %.loom.json: $$(firstword $$(subst ., , $$*)).14.loom.json | dummy% 140 | ln -s $< $@ 141 | 142 | ######### 143 | 144 | %.geo.json: %.loom.json 145 | ln -s $< $@ 146 | 147 | 148 | %.15.octi.json: %.15.topo.json 149 | $(OCTI) --retry-on-error --skip-on-error -g 75% < $< > $@ 150 | 151 | %.15.octi-geo.json: %.15.topo.json 152 | $(OCTI) --retry-on-error --skip-on-error --nd-move-pen 5 --geo-pen 10 --skip-on-error -g 50% < $< > $@ 153 | 154 | %.15.orthorad.json: %.15.topo.json 155 | $(OCTI) --retry-on-error --skip-on-error -b orthoradial -g 75% < $< > $@ 156 | 157 | 158 | .SECONDEXPANSION: 159 | %.octi.json: $$(firstword $$(subst ., , $$*)).15.octi.json | dummy% 160 | ln -s $< $@ 161 | 162 | .SECONDEXPANSION: 163 | %.octi-geo.json: $$(firstword $$(subst ., , $$*)).15.octi-geo.json | dummy% 164 | ln -s $< $@ 165 | 166 | .SECONDEXPANSION: 167 | %.orthorad.json: $$(firstword $$(subst ., , $$*)).15.orthorad.json | dummy% 168 | ln -s $< $@ 169 | 170 | %.15.octi.loom.json: %.15.octi.json 171 | $(LOOM) -m anneal < $< > $@ 172 | 173 | %.15.octi-geo.loom.json: %.15.octi-geo.json 174 | $(LOOM) -m anneal < $< > $@ 175 | 176 | %.15.orthorad.loom.json: %.15.orthorad.json 177 | $(LOOM) -m anneal < $< > $@ 178 | 179 | .SECONDEXPANSION: 180 | %.octi.loom.json: $$(firstword $$(subst ., , $$*)).15.octi.loom.json | dummy% 181 | ln -s $< $@ 182 | 183 | .SECONDEXPANSION: 184 | %.octi-geo.loom.json: $$(firstword $$(subst ., , $$*)).15.octi-geo.loom.json | dummy% 185 | ln -s $< $@ 186 | 187 | .SECONDEXPANSION: 188 | %.orthorad.loom.json: $$(firstword $$(subst ., , $$*)).15.orthorad.loom.json | dummy% 189 | ln -s $< $@ 190 | 191 | .SECONDEXPANSION: 192 | tiles/%/geo/20: %.20.geo.json 193 | mkdir -p $@ 194 | $(TRANSITMAP) --render-engine mvt --line-width=4 --line-spacing=3 -z=$(lastword $(subst /, , $@)) --mvt-path tiles/$*/geo < $< > $@/stats.json 195 | 196 | .SECONDEXPANSION: 197 | tiles/%/geo/19: %.19.geo.json 198 | mkdir -p $@ 199 | $(TRANSITMAP) --render-engine mvt --line-width=4 --line-spacing=3 -z=$(lastword $(subst /, , $@)) --mvt-path tiles/$*/geo < $< > $@/stats.json 200 | 201 | .SECONDEXPANSION: 202 | tiles/%/geo/18: %.15.geo.json 203 | mkdir -p $@ 204 | $(TRANSITMAP) --render-engine mvt --line-width=4 --line-spacing=3 -z=$(lastword $(subst /, , $@)) --mvt-path tiles/$*/geo < $< > $@/stats.json 205 | 206 | .SECONDEXPANSION: 207 | tiles/%/geo/17: %.15.geo.json 208 | mkdir -p $@ 209 | $(TRANSITMAP) --render-engine mvt --line-width=4 --line-spacing=3 -z=$(lastword $(subst /, , $@)) --mvt-path tiles/$*/geo < $< > $@/stats.json 210 | 211 | .SECONDEXPANSION: 212 | tiles/%/geo/16: %.15.geo.json 213 | mkdir -p $@ 214 | $(TRANSITMAP) --render-engine mvt --line-width=4 --line-spacing=3 -z=$(lastword $(subst /, , $@)) --mvt-path tiles/$*/geo < $< > $@/stats.json 215 | 216 | .SECONDEXPANSION: 217 | tiles/%/geo/15: %.15.geo.json 218 | mkdir -p $@ 219 | $(TRANSITMAP) --render-engine mvt --line-width=4 --line-spacing=3 -z=$(lastword $(subst /, , $@)) --mvt-path tiles/$*/geo < $< > $@/stats.json 220 | 221 | .SECONDEXPANSION: 222 | tiles/%/geo/14: %.14.geo.json 223 | mkdir -p $@ 224 | $(TRANSITMAP) --render-engine mvt --line-width=3 --line-spacing=2 -z=$(lastword $(subst /, , $@)) --mvt-path tiles/$*/geo < $< > $@/stats.json 225 | 226 | .SECONDEXPANSION: 227 | tiles/%/geo/13: %.14.geo.json 228 | mkdir -p $@ 229 | $(TRANSITMAP) --render-engine mvt --line-width=3 --line-spacing=1 -z=$(lastword $(subst /, , $@)) --mvt-path tiles/$*/geo < $< > $@/stats.json 230 | 231 | .SECONDEXPANSION: 232 | tiles/%/geo/12: %.14.geo.json 233 | mkdir -p $@ 234 | $(TRANSITMAP) --render-engine mvt --line-width=2 --line-spacing=1 g -z=$(lastword $(subst /, , $@)) --mvt-path tiles/$*/geo < $< > $@/stats.json 235 | 236 | .SECONDEXPANSION: 237 | tiles/%/geo/11: %.14.geo.json 238 | mkdir -p $@ 239 | $(TRANSITMAP) --render-engine mvt --line-width=1 --line-spacing=1 g -z=$(lastword $(subst /, , $@)) --mvt-path tiles/$*/geo < $< > $@/stats.json 240 | 241 | .SECONDEXPANSION: 242 | tiles/%/geo/10: %.14.geo.json 243 | mkdir -p $@ 244 | $(TRANSITMAP) --render-engine mvt --line-width=1 --line-spacing=0 g -z=$(lastword $(subst /, , $@)) --mvt-path tiles/$*/geo < $< > $@/stats.json 245 | 246 | .SECONDEXPANSION: 247 | tiles/%/geo/9: %.14.geo.json 248 | mkdir -p $@ 249 | $(TRANSITMAP) --render-engine mvt --line-width=1 --line-spacing=0 g -z=$(lastword $(subst /, , $@)) --mvt-path tiles/$*/geo < $< > $@/stats.json 250 | 251 | .SECONDEXPANSION: 252 | tiles/%/geo/8: %.14.geo.json 253 | mkdir -p $@ 254 | $(TRANSITMAP) --render-engine mvt --line-width=1 --line-spacing=0 g -z=$(lastword $(subst /, , $@)) --mvt-path tiles/$*/geo < $< > $@/stats.json 255 | 256 | .SECONDEXPANSION: 257 | tiles/%/geo/7: %.14.geo.json 258 | mkdir -p $@ 259 | $(TRANSITMAP) --render-engine mvt --line-width=1 --line-spacing=0 g -z=$(lastword $(subst /, , $@)) --mvt-path tiles/$*/geo < $< > $@/stats.json 260 | 261 | .SECONDEXPANSION: 262 | tiles/%/geo/6: %.14.geo.json 263 | mkdir -p $@ 264 | $(TRANSITMAP) --render-engine mvt --line-width=1 --line-spacing=0 g -z=$(lastword $(subst /, , $@)) --mvt-path tiles/$*/geo < $< > $@/stats.json 265 | 266 | .SECONDEXPANSION: 267 | tiles/%/geo/5: %.14.geo.json 268 | mkdir -p $@ 269 | $(TRANSITMAP) --render-engine mvt --line-width=1 --line-spacing=0 g -z=$(lastword $(subst /, , $@)) --mvt-path tiles/$*/geo < $< > $@/stats.json 270 | 271 | .SECONDEXPANSION: 272 | tiles/%/geo/4: %.14.geo.json 273 | mkdir -p $@ 274 | $(TRANSITMAP) --render-engine mvt --line-width=1 --line-spacing=0 g -z=$(lastword $(subst /, , $@)) --mvt-path tiles/$*/geo < $< > $@/stats.json 275 | 276 | .SECONDEXPANSION: 277 | tiles/%/geo/3: %.14.geo.json 278 | mkdir -p $@ 279 | $(TRANSITMAP) --render-engine mvt --line-width=1 --line-spacing=0 g -z=$(lastword $(subst /, , $@)) --mvt-path tiles/$*/geo < $< > $@/stats.json 280 | 281 | .SECONDEXPANSION: 282 | tiles/%/geo/2: %.14.geo.json 283 | mkdir -p $@ 284 | $(TRANSITMAP) --render-engine mvt --line-width=1 --line-spacing=0 g -z=$(lastword $(subst /, , $@)) --mvt-path tiles/$*/geo < $< > $@/stats.json 285 | 286 | .SECONDEXPANSION: 287 | tiles/%/geo/1: %.14.geo.json 288 | mkdir -p $@ 289 | $(TRANSITMAP) --render-engine mvt --line-width=1 --line-spacing=0 g -z=$(lastword $(subst /, , $@)) --mvt-path tiles/$*/geo < $< > $@/stats.json 290 | 291 | .SECONDEXPANSION: 292 | tiles/%/geo/0: %.14.geo.json 293 | mkdir -p $@ 294 | $(TRANSITMAP) --render-engine mvt --line-width=1 --line-spacing=0 g -z=$(lastword $(subst /, , $@)) --mvt-path tiles/$*/geo < $< > $@/stats.json 295 | ########## 296 | 297 | .SECONDEXPANSION: 298 | tiles/%/octi/20: %.14.octi.loom.json 299 | mkdir -p $@ 300 | $(TRANSITMAP) --tight-stations --render-engine mvt --line-width=5 --line-spacing=2 -z=$(lastword $(subst /, , $@)) --mvt-path tiles/$*/octi < $< > $@/stats.json 301 | 302 | 303 | .SECONDEXPANSION: 304 | tiles/%/octi/19: %.14.octi.loom.json 305 | mkdir -p $@ 306 | $(TRANSITMAP) --tight-stations --render-engine mvt --line-width=5 --line-spacing=2 -z=$(lastword $(subst /, , $@)) --mvt-path tiles/$*/octi < $< > $@/stats.json 307 | 308 | 309 | .SECONDEXPANSION: 310 | tiles/%/octi/18: %.14.octi.loom.json 311 | mkdir -p $@ 312 | $(TRANSITMAP) --tight-stations --render-engine mvt --line-width=5 --line-spacing=2 -z=$(lastword $(subst /, , $@)) --mvt-path tiles/$*/octi < $< > $@/stats.json 313 | 314 | .SECONDEXPANSION: 315 | tiles/%/octi/17: %.14.octi.loom.json 316 | mkdir -p $@ 317 | $(TRANSITMAP) --tight-stations --render-engine mvt --line-width=5 --line-spacing=2 -z=$(lastword $(subst /, , $@)) --mvt-path tiles/$*/octi < $< > $@/stats.json 318 | 319 | .SECONDEXPANSION: 320 | tiles/%/octi/16: %.14.octi.loom.json 321 | mkdir -p $@ 322 | $(TRANSITMAP) --tight-stations --render-engine mvt --line-width=5 --line-spacing=2 -z=$(lastword $(subst /, , $@)) --mvt-path tiles/$*/octi < $< > $@/stats.json 323 | 324 | .SECONDEXPANSION: 325 | tiles/%/octi/15: %.14.octi.loom.json 326 | mkdir -p $@ 327 | $(TRANSITMAP) --tight-stations --render-engine mvt --line-width=4 --line-spacing=2 -z=$(lastword $(subst /, , $@)) --mvt-path tiles/$*/octi < $< > $@/stats.json 328 | 329 | .SECONDEXPANSION: 330 | tiles/%/octi/14: %.14.octi.loom.json 331 | mkdir -p $@ 332 | $(TRANSITMAP) --tight-stations --render-engine mvt --line-width=3 --line-spacing=2 -z=$(lastword $(subst /, , $@)) --mvt-path tiles/$*/octi < $< > $@/stats.json 333 | 334 | .SECONDEXPANSION: 335 | tiles/%/octi/13: %.14.octi.loom.json 336 | mkdir -p $@ 337 | $(TRANSITMAP) --tight-stations --render-engine mvt --line-width=3 --line-spacing=1 -z=$(lastword $(subst /, , $@)) --mvt-path tiles/$*/octi < $< > $@/stats.json 338 | 339 | .SECONDEXPANSION: 340 | tiles/%/octi/12: %.14.octi.loom.json 341 | mkdir -p $@ 342 | $(TRANSITMAP) --tight-stations --render-engine mvt --line-width=3 --line-spacing=0 g -z=$(lastword $(subst /, , $@)) --mvt-path tiles/$*/octi < $< > $@/stats.json 343 | 344 | .SECONDEXPANSION: 345 | tiles/%/octi/11: %.14.octi.loom.json 346 | mkdir -p $@ 347 | $(TRANSITMAP) --tight-stations --render-engine mvt --line-width=2 --line-spacing=0 g -z=$(lastword $(subst /, , $@)) --mvt-path tiles/$*/octi < $< > $@/stats.json 348 | 349 | .SECONDEXPANSION: 350 | tiles/%/octi/10: %.14.octi.loom.json 351 | mkdir -p $@ 352 | $(TRANSITMAP) --tight-stations --render-engine mvt --line-width=1 --line-spacing=0 g -z=$(lastword $(subst /, , $@)) --mvt-path tiles/$*/octi < $< > $@/stats.json 353 | 354 | .SECONDEXPANSION: 355 | tiles/%/octi/9: %.14.octi.loom.json 356 | mkdir -p $@ 357 | $(TRANSITMAP) --tight-stations --render-engine mvt --line-width=1 --line-spacing=0 g -z=$(lastword $(subst /, , $@)) --mvt-path tiles/$*/octi < $< > $@/stats.json 358 | 359 | .SECONDEXPANSION: 360 | tiles/%/octi/8: %.14.octi.loom.json 361 | mkdir -p $@ 362 | $(TRANSITMAP) --tight-stations --render-engine mvt --line-width=1 --line-spacing=0 g -z=$(lastword $(subst /, , $@)) --mvt-path tiles/$*/octi < $< > $@/stats.json 363 | 364 | .SECONDEXPANSION: 365 | tiles/%/octi/7: %.14.octi.loom.json 366 | mkdir -p $@ 367 | $(TRANSITMAP) --tight-stations --render-engine mvt --line-width=1 --line-spacing=0 g -z=$(lastword $(subst /, , $@)) --mvt-path tiles/$*/octi < $< > $@/stats.json 368 | 369 | .SECONDEXPANSION: 370 | tiles/%/octi/6: %.14.octi.loom.json 371 | mkdir -p $@ 372 | $(TRANSITMAP) --tight-stations --render-engine mvt --line-width=1 --line-spacing=0 g -z=$(lastword $(subst /, , $@)) --mvt-path tiles/$*/octi < $< > $@/stats.json 373 | 374 | .SECONDEXPANSION: 375 | tiles/%/octi/5: %.14.octi.loom.json 376 | mkdir -p $@ 377 | $(TRANSITMAP) --tight-stations --render-engine mvt --line-width=1 --line-spacing=0 g -z=$(lastword $(subst /, , $@)) --mvt-path tiles/$*/octi < $< > $@/stats.json 378 | 379 | .SECONDEXPANSION: 380 | tiles/%/octi/4: %.14.octi.loom.json 381 | mkdir -p $@ 382 | $(TRANSITMAP) --tight-stations --render-engine mvt --line-width=1 --line-spacing=0 g -z=$(lastword $(subst /, , $@)) --mvt-path tiles/$*/octi < $< > $@/stats.json 383 | 384 | .SECONDEXPANSION: 385 | tiles/%/octi/3: %.14.octi.loom.json 386 | mkdir -p $@ 387 | $(TRANSITMAP) --tight-stations --render-engine mvt --line-width=1 --line-spacing=0 g -z=$(lastword $(subst /, , $@)) --mvt-path tiles/$*/octi < $< > $@/stats.json 388 | 389 | .SECONDEXPANSION: 390 | tiles/%/octi/2: %.14.octi.loom.json 391 | mkdir -p $@ 392 | $(TRANSITMAP) --tight-stations --render-engine mvt --line-width=1 --line-spacing=0 g -z=$(lastword $(subst /, , $@)) --mvt-path tiles/$*/octi < $< > $@/stats.json 393 | 394 | .SECONDEXPANSION: 395 | tiles/%/octi/1: %.14.octi.loom.json 396 | mkdir -p $@ 397 | $(TRANSITMAP) --tight-stations --render-engine mvt --line-width=1 --line-spacing=0 g -z=$(lastword $(subst /, , $@)) --mvt-path tiles/$*/octi < $< > $@/stats.json 398 | 399 | .SECONDEXPANSION: 400 | tiles/%/octi/0: %.14.octi.loom.json 401 | mkdir -p $@ 402 | $(TRANSITMAP) --tight-stations --render-engine mvt --line-width=1 --line-spacing=0 g -z=$(lastword $(subst /, , $@)) --mvt-path tiles/$*/octi < $< > $@/stats.json 403 | ########## 404 | 405 | .SECONDEXPANSION: 406 | tiles/%/octi-geo/20: %.14.octi-geo.loom.json 407 | mkdir -p $@ 408 | $(TRANSITMAP) --tight-stations --render-engine mvt --line-width=5 --line-spacing=2 -z=$(lastword $(subst /, , $@)) --mvt-path tiles/$*/octi-geo < $< > $@/stats.json 409 | 410 | 411 | .SECONDEXPANSION: 412 | tiles/%/octi-geo/19: %.14.octi-geo.loom.json 413 | mkdir -p $@ 414 | $(TRANSITMAP) --tight-stations --render-engine mvt --line-width=5 --line-spacing=2 -z=$(lastword $(subst /, , $@)) --mvt-path tiles/$*/octi-geo < $< > $@/stats.json 415 | 416 | 417 | .SECONDEXPANSION: 418 | tiles/%/octi-geo/18: %.14.octi-geo.loom.json 419 | mkdir -p $@ 420 | $(TRANSITMAP) --tight-stations --render-engine mvt --line-width=5 --line-spacing=2 -z=$(lastword $(subst /, , $@)) --mvt-path tiles/$*/octi-geo < $< > $@/stats.json 421 | 422 | .SECONDEXPANSION: 423 | tiles/%/octi-geo/17: %.14.octi-geo.loom.json 424 | mkdir -p $@ 425 | $(TRANSITMAP) --tight-stations --render-engine mvt --line-width=5 --line-spacing=2 -z=$(lastword $(subst /, , $@)) --mvt-path tiles/$*/octi-geo < $< > $@/stats.json 426 | 427 | .SECONDEXPANSION: 428 | tiles/%/octi-geo/16: %.14.octi-geo.loom.json 429 | mkdir -p $@ 430 | $(TRANSITMAP) --tight-stations --render-engine mvt --line-width=5 --line-spacing=2 -z=$(lastword $(subst /, , $@)) --mvt-path tiles/$*/octi-geo < $< > $@/stats.json 431 | 432 | .SECONDEXPANSION: 433 | tiles/%/octi-geo/15: %.14.octi-geo.loom.json 434 | mkdir -p $@ 435 | $(TRANSITMAP) --tight-stations --render-engine mvt --line-width=4 --line-spacing=2 -z=$(lastword $(subst /, , $@)) --mvt-path tiles/$*/octi-geo < $< > $@/stats.json 436 | 437 | .SECONDEXPANSION: 438 | tiles/%/octi-geo/14: %.14.octi-geo.loom.json 439 | mkdir -p $@ 440 | $(TRANSITMAP) --tight-stations --render-engine mvt --line-width=3 --line-spacing=2 -z=$(lastword $(subst /, , $@)) --mvt-path tiles/$*/octi-geo < $< > $@/stats.json 441 | 442 | .SECONDEXPANSION: 443 | tiles/%/octi-geo/13: %.14.octi-geo.loom.json 444 | mkdir -p $@ 445 | $(TRANSITMAP) --tight-stations --render-engine mvt --line-width=3 --line-spacing=1 -z=$(lastword $(subst /, , $@)) --mvt-path tiles/$*/octi-geo < $< > $@/stats.json 446 | 447 | .SECONDEXPANSION: 448 | tiles/%/octi-geo/12: %.14.octi-geo.loom.json 449 | mkdir -p $@ 450 | $(TRANSITMAP) --tight-stations --render-engine mvt --line-width=3 --line-spacing=0 g -z=$(lastword $(subst /, , $@)) --mvt-path tiles/$*/octi-geo < $< > $@/stats.json 451 | 452 | .SECONDEXPANSION: 453 | tiles/%/octi-geo/11: %.14.octi-geo.loom.json 454 | mkdir -p $@ 455 | $(TRANSITMAP) --tight-stations --render-engine mvt --line-width=2 --line-spacing=0 g -z=$(lastword $(subst /, , $@)) --mvt-path tiles/$*/octi-geo < $< > $@/stats.json 456 | 457 | .SECONDEXPANSION: 458 | tiles/%/octi-geo/10: %.14.octi-geo.loom.json 459 | mkdir -p $@ 460 | $(TRANSITMAP) --tight-stations --render-engine mvt --line-width=1 --line-spacing=0 g -z=$(lastword $(subst /, , $@)) --mvt-path tiles/$*/octi-geo < $< > $@/stats.json 461 | 462 | .SECONDEXPANSION: 463 | tiles/%/octi-geo/9: %.14.octi-geo.loom.json 464 | mkdir -p $@ 465 | $(TRANSITMAP) --tight-stations --render-engine mvt --line-width=1 --line-spacing=0 g -z=$(lastword $(subst /, , $@)) --mvt-path tiles/$*/octi-geo < $< > $@/stats.json 466 | 467 | .SECONDEXPANSION: 468 | tiles/%/octi-geo/8: %.14.octi-geo.loom.json 469 | mkdir -p $@ 470 | $(TRANSITMAP) --tight-stations --render-engine mvt --line-width=1 --line-spacing=0 g -z=$(lastword $(subst /, , $@)) --mvt-path tiles/$*/octi-geo < $< > $@/stats.json 471 | 472 | .SECONDEXPANSION: 473 | tiles/%/octi-geo/7: %.14.octi-geo.loom.json 474 | mkdir -p $@ 475 | $(TRANSITMAP) --tight-stations --render-engine mvt --line-width=1 --line-spacing=0 g -z=$(lastword $(subst /, , $@)) --mvt-path tiles/$*/octi-geo < $< > $@/stats.json 476 | 477 | .SECONDEXPANSION: 478 | tiles/%/octi-geo/6: %.14.octi-geo.loom.json 479 | mkdir -p $@ 480 | $(TRANSITMAP) --tight-stations --render-engine mvt --line-width=1 --line-spacing=0 g -z=$(lastword $(subst /, , $@)) --mvt-path tiles/$*/octi-geo < $< > $@/stats.json 481 | 482 | .SECONDEXPANSION: 483 | tiles/%/octi-geo/5: %.14.octi-geo.loom.json 484 | mkdir -p $@ 485 | $(TRANSITMAP) --tight-stations --render-engine mvt --line-width=1 --line-spacing=0 g -z=$(lastword $(subst /, , $@)) --mvt-path tiles/$*/octi-geo < $< > $@/stats.json 486 | 487 | .SECONDEXPANSION: 488 | tiles/%/octi-geo/4: %.14.octi-geo.loom.json 489 | mkdir -p $@ 490 | $(TRANSITMAP) --tight-stations --render-engine mvt --line-width=1 --line-spacing=0 g -z=$(lastword $(subst /, , $@)) --mvt-path tiles/$*/octi-geo < $< > $@/stats.json 491 | 492 | .SECONDEXPANSION: 493 | tiles/%/octi-geo/3: %.14.octi-geo.loom.json 494 | mkdir -p $@ 495 | $(TRANSITMAP) --tight-stations --render-engine mvt --line-width=1 --line-spacing=0 g -z=$(lastword $(subst /, , $@)) --mvt-path tiles/$*/octi-geo < $< > $@/stats.json 496 | 497 | .SECONDEXPANSION: 498 | tiles/%/octi-geo/2: %.14.octi-geo.loom.json 499 | mkdir -p $@ 500 | $(TRANSITMAP) --tight-stations --render-engine mvt --line-width=1 --line-spacing=0 g -z=$(lastword $(subst /, , $@)) --mvt-path tiles/$*/octi-geo < $< > $@/stats.json 501 | 502 | .SECONDEXPANSION: 503 | tiles/%/octi-geo/1: %.14.octi-geo.loom.json 504 | mkdir -p $@ 505 | $(TRANSITMAP) --tight-stations --render-engine mvt --line-width=1 --line-spacing=0 g -z=$(lastword $(subst /, , $@)) --mvt-path tiles/$*/octi-geo < $< > $@/stats.json 506 | 507 | .SECONDEXPANSION: 508 | tiles/%/octi-geo/0: %.14.octi-geo.loom.json 509 | mkdir -p $@ 510 | $(TRANSITMAP) --tight-stations --render-engine mvt --line-width=1 --line-spacing=0 g -z=$(lastword $(subst /, , $@)) --mvt-path tiles/$*/octi-geo < $< > $@/stats.json 511 | 512 | ########## 513 | 514 | .SECONDEXPANSION: 515 | tiles/%/orthorad/20: %.14.orthorad.loom.json 516 | mkdir -p $@ 517 | $(TRANSITMAP) --tight-stations --render-engine mvt --line-width=5 --line-spacing=2 -z=$(lastword $(subst /, , $@)) --mvt-path tiles/$*/orthorad < $< > $@/stats.json 518 | 519 | 520 | .SECONDEXPANSION: 521 | tiles/%/orthorad/19: %.14.orthorad.loom.json 522 | mkdir -p $@ 523 | $(TRANSITMAP) --tight-stations --render-engine mvt --line-width=5 --line-spacing=2 -z=$(lastword $(subst /, , $@)) --mvt-path tiles/$*/orthorad < $< > $@/stats.json 524 | 525 | 526 | .SECONDEXPANSION: 527 | tiles/%/orthorad/18: %.14.orthorad.loom.json 528 | mkdir -p $@ 529 | $(TRANSITMAP) --tight-stations --render-engine mvt --line-width=5 --line-spacing=2 -z=$(lastword $(subst /, , $@)) --mvt-path tiles/$*/orthorad < $< > $@/stats.json 530 | 531 | .SECONDEXPANSION: 532 | tiles/%/orthorad/17: %.14.orthorad.loom.json 533 | mkdir -p $@ 534 | $(TRANSITMAP) --tight-stations --render-engine mvt --line-width=5 --line-spacing=2 -z=$(lastword $(subst /, , $@)) --mvt-path tiles/$*/orthorad < $< > $@/stats.json 535 | 536 | .SECONDEXPANSION: 537 | tiles/%/orthorad/16: %.14.orthorad.loom.json 538 | mkdir -p $@ 539 | $(TRANSITMAP) --tight-stations --render-engine mvt --line-width=5 --line-spacing=2 -z=$(lastword $(subst /, , $@)) --mvt-path tiles/$*/orthorad < $< > $@/stats.json 540 | 541 | .SECONDEXPANSION: 542 | tiles/%/orthorad/15: %.14.orthorad.loom.json 543 | mkdir -p $@ 544 | $(TRANSITMAP) --tight-stations --render-engine mvt --line-width=4 --line-spacing=2 -z=$(lastword $(subst /, , $@)) --mvt-path tiles/$*/orthorad < $< > $@/stats.json 545 | 546 | .SECONDEXPANSION: 547 | tiles/%/orthorad/14: %.14.orthorad.loom.json 548 | mkdir -p $@ 549 | $(TRANSITMAP) --tight-stations --render-engine mvt --line-width=4 --line-spacing=1 -z=$(lastword $(subst /, , $@)) --mvt-path tiles/$*/orthorad < $< > $@/stats.json 550 | 551 | .SECONDEXPANSION: 552 | tiles/%/orthorad/13: %.14.orthorad.loom.json 553 | mkdir -p $@ 554 | $(TRANSITMAP) --tight-stations --render-engine mvt --line-width=3 --line-spacing=1 -z=$(lastword $(subst /, , $@)) --mvt-path tiles/$*/orthorad < $< > $@/stats.json 555 | 556 | .SECONDEXPANSION: 557 | tiles/%/orthorad/12: %.14.orthorad.loom.json 558 | mkdir -p $@ 559 | $(TRANSITMAP) --tight-stations --render-engine mvt --line-width=3 --line-spacing=0 g -z=$(lastword $(subst /, , $@)) --mvt-path tiles/$*/orthorad < $< > $@/stats.json 560 | 561 | .SECONDEXPANSION: 562 | tiles/%/orthorad/11: %.14.orthorad.loom.json 563 | mkdir -p $@ 564 | $(TRANSITMAP) --tight-stations --render-engine mvt --line-width=2 --line-spacing=0 g -z=$(lastword $(subst /, , $@)) --mvt-path tiles/$*/orthorad < $< > $@/stats.json 565 | 566 | .SECONDEXPANSION: 567 | tiles/%/orthorad/10: %.14.orthorad.loom.json 568 | mkdir -p $@ 569 | $(TRANSITMAP) --tight-stations --render-engine mvt --line-width=1 --line-spacing=0 g -z=$(lastword $(subst /, , $@)) --mvt-path tiles/$*/orthorad < $< > $@/stats.json 570 | 571 | .SECONDEXPANSION: 572 | tiles/%/orthorad/9: %.14.orthorad.loom.json 573 | mkdir -p $@ 574 | $(TRANSITMAP) --tight-stations --render-engine mvt --line-width=1 --line-spacing=0 g -z=$(lastword $(subst /, , $@)) --mvt-path tiles/$*/orthorad < $< > $@/stats.json 575 | 576 | .SECONDEXPANSION: 577 | tiles/%/orthorad/8: %.14.orthorad.loom.json 578 | mkdir -p $@ 579 | $(TRANSITMAP) --tight-stations --render-engine mvt --line-width=1 --line-spacing=0 g -z=$(lastword $(subst /, , $@)) --mvt-path tiles/$*/orthorad < $< > $@/stats.json 580 | 581 | .SECONDEXPANSION: 582 | tiles/%/orthorad/7: %.14.orthorad.loom.json 583 | mkdir -p $@ 584 | $(TRANSITMAP) --tight-stations --render-engine mvt --line-width=1 --line-spacing=0 g -z=$(lastword $(subst /, , $@)) --mvt-path tiles/$*/orthorad < $< > $@/stats.json 585 | 586 | .SECONDEXPANSION: 587 | tiles/%/orthorad/6: %.14.orthorad.loom.json 588 | mkdir -p $@ 589 | $(TRANSITMAP) --tight-stations --render-engine mvt --line-width=1 --line-spacing=0 g -z=$(lastword $(subst /, , $@)) --mvt-path tiles/$*/orthorad < $< > $@/stats.json 590 | 591 | .SECONDEXPANSION: 592 | tiles/%/orthorad/5: %.14.orthorad.loom.json 593 | mkdir -p $@ 594 | $(TRANSITMAP) --tight-stations --render-engine mvt --line-width=1 --line-spacing=0 g -z=$(lastword $(subst /, , $@)) --mvt-path tiles/$*/orthorad < $< > $@/stats.json 595 | 596 | .SECONDEXPANSION: 597 | tiles/%/orthorad/4: %.14.orthorad.loom.json 598 | mkdir -p $@ 599 | $(TRANSITMAP) --tight-stations --render-engine mvt --line-width=1 --line-spacing=0 g -z=$(lastword $(subst /, , $@)) --mvt-path tiles/$*/orthorad < $< > $@/stats.json 600 | 601 | .SECONDEXPANSION: 602 | tiles/%/orthorad/3: %.14.orthorad.loom.json 603 | mkdir -p $@ 604 | $(TRANSITMAP) --tight-stations --render-engine mvt --line-width=1 --line-spacing=0 g -z=$(lastword $(subst /, , $@)) --mvt-path tiles/$*/orthorad < $< > $@/stats.json 605 | 606 | .SECONDEXPANSION: 607 | tiles/%/orthorad/2: %.14.orthorad.loom.json 608 | mkdir -p $@ 609 | $(TRANSITMAP) --tight-stations --render-engine mvt --line-width=1 --line-spacing=0 g -z=$(lastword $(subst /, , $@)) --mvt-path tiles/$*/orthorad < $< > $@/stats.json 610 | 611 | .SECONDEXPANSION: 612 | tiles/%/orthorad/1: %.14.orthorad.loom.json 613 | mkdir -p $@ 614 | $(TRANSITMAP) --tight-stations --render-engine mvt --line-width=1 --line-spacing=0 g -z=$(lastword $(subst /, , $@)) --mvt-path tiles/$*/orthorad < $< > $@/stats.json 615 | 616 | .SECONDEXPANSION: 617 | tiles/%/orthorad/0: %.14.orthorad.loom.json 618 | mkdir -p $@ 619 | $(TRANSITMAP) --tight-stations --render-engine mvt --line-width=1 --line-spacing=0 g -z=$(lastword $(subst /, , $@)) --mvt-path tiles/$*/orthorad < $< > $@/stats.json 620 | 621 | 622 | # STATS 623 | # ------------------------------- 624 | .SECONDEXPANSION: 625 | stats/%/octi.stats.json: $$(firstword $$(subst /, , $$*)).$$(lastword $$(subst /, , $$*)).topo.json $$(firstword $$(subst /, , $$*)).$$(lastword $$(subst /, , $$*)).octi.loom.json $$(firstword $$(subst /, , $$*)).$$(lastword $$(subst /, , $$*)).octi.json $$(firstword $$(subst /, , $$*)).inputstats.json stats/$$(firstword $$(subst /, , $$*))/query.stats.json tiles/$$(firstword $$(subst /, , $$*))/octi/$$(lastword $$(subst /, , $$*)) 626 | mkdir -p stats/$* 627 | jq '{"topo": .[0].properties.statistics, "loom": .[1].properties.statistics, "octi": .[2].properties.statistics, "render":.[5], "input":.[3], "query":.[4]}' -s $^/stats.json > $@ 628 | 629 | .SECONDEXPANSION: 630 | stats/%/octi-geo.stats.json: $$(firstword $$(subst /, , $$*)).$$(lastword $$(subst /, , $$*)).topo.json $$(firstword $$(subst /, , $$*)).$$(lastword $$(subst /, , $$*)).octi-geo.loom.json $$(firstword $$(subst /, , $$*)).$$(lastword $$(subst /, , $$*)).octi-geo.json $$(firstword $$(subst /, , $$*)).inputstats.json stats/$$(firstword $$(subst /, , $$*))/query.stats.json tiles/$$(firstword $$(subst /, , $$*))/octi-geo/$$(lastword $$(subst /, , $$*)) 631 | mkdir -p stats/$* 632 | jq '{"topo": .[0].properties.statistics, "loom": .[1].properties.statistics, "octi": .[2].properties.statistics, "render":.[5], "input":.[3], "query":.[4]}' -s $^/stats.json > $@ 633 | 634 | .SECONDEXPANSION: 635 | stats/%/orthorad.stats.json: $$(firstword $$(subst /, , $$*)).$$(lastword $$(subst /, , $$*)).topo.json $$(firstword $$(subst /, , $$*)).$$(lastword $$(subst /, , $$*)).orthorad.loom.json $$(firstword $$(subst /, , $$*)).$$(lastword $$(subst /, , $$*)).orthorad.json $$(firstword $$(subst /, , $$*)).inputstats.json stats/$$(firstword $$(subst /, , $$*))/query.stats.json tiles/$$(firstword $$(subst /, , $$*))/orthorad/$$(lastword $$(subst /, , $$*)) 636 | mkdir -p stats/$* 637 | jq '{"topo": .[0].properties.statistics, "loom": .[1].properties.statistics, "octi": .[2].properties.statistics, "render":.[5], "input":.[3], "query":.[4]}' -s $^/stats.json > $@ 638 | 639 | .SECONDEXPANSION: 640 | stats/%/geo.stats.json: $$(firstword $$(subst /, , $$*)).$$(lastword $$(subst /, , $$*)).topo.json $$(firstword $$(subst /, , $$*)).$$(lastword $$(subst /, , $$*)).loom.json $$(firstword $$(subst /, , $$*)).$$(lastword $$(subst /, , $$*)).geo.json $$(firstword $$(subst /, , $$*)).inputstats.json stats/$$(firstword $$(subst /, , $$*))/query.stats.json tiles/$$(firstword $$(subst /, , $$*))/geo/$$(lastword $$(subst /, , $$*)) 641 | mkdir -p stats/$* 642 | jq '{"topo": .[0].properties.statistics, "loom": .[1].properties.statistics, "octi": {}, "render":.[5], "input":.[3], "query":.[4]}' -s $^/stats.json > $@ 643 | 644 | clean: 645 | rm -rf tiles/rail tiles/subway-lightrail tiles/tram tiles/rail-commuter 646 | rm -rf stats/* 647 | rm -rf *.json 648 | rm -rf components/* 649 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Global transit maps 2 | =================== 3 | 4 | This is the setup of the experimental evaluation of our paper "Large-Scale Generation of Transit Maps from OpenStreetMap Data". 5 | 6 | The Makefile provided here, together with the queries in folder `queries`, generates global transit map vector tiles from OpenStreetMap (OSM) data. 7 | 8 | It requires an installation of [LOOM](https://github.com/ad-freiburg/loom). 9 | 10 | The tiles can be inspected using the web application in `web`. 11 | 12 | An example installation is available at [https://loom.cs.uni-freiburg.de/global](https://loom.cs.uni-freiburg.de/global). 13 | 14 | All tiles are available for free at [https://loom.cs.uni-freiburg.de/tiles](https://loom.cs.uni-freiburg.de/tiles). 15 | 16 | Currently, 4 networks are supported: `tram`, `subway-lightrail`, `rail-commuter`, and `rail`. 17 | 18 | To render the tiles for each network, use 19 | 20 | make 21 | 22 | For example, to render all tram tiles, type 23 | 24 | make tram 25 | -------------------------------------------------------------------------------- /queries/rail-commuter.sparql: -------------------------------------------------------------------------------- 1 | PREFIX ogc: 2 | PREFIX osmrel: 3 | PREFIX geo: 4 | PREFIX osm: 5 | PREFIX rdf: 6 | PREFIX osmkey: 7 | PREFIX osm2rdf: 8 | SELECT ?color ?id ?label ?station_label ?station_id ?geom WHERE { 9 | { 10 | {?rel osmkey:route "railway"} UNION {?rel osmkey:route "train" } . 11 | ?rel osmrel:member ?mem . 12 | ?rel osmkey:service "commuter" . 13 | OPTIONAL {?rel osmkey:colour ?color} . 14 | OPTIONAL {?rel osmkey:colour ?id} . 15 | ?rel osmkey:ref ?label . 16 | ?mem osm:id ?osm_id . 17 | ?osm_id geo:hasGeometry ?geom . 18 | { ?osm_id osmkey:railway "rail" } UNION { ?osm_id osmkey:railway "light_rail" }. 19 | } UNION { 20 | ?osm_id geo:hasGeometry ?geom . 21 | { ?osm_id osmkey:public_transport "stop_position" } UNION { ?osm_id osmkey:public_transport "station" } . 22 | ?osm_id osmkey:name ?station_label . 23 | { ?osm_id osmkey:train "yes" } UNION { ?osm_id osmkey:railway "stop" } UNION { ?osm_id osmkey:railway "station" } UNION { ?osm_id osmkey:light_rail "yes" }. 24 | } UNION { 25 | {?rel osmkey:route "railway"} UNION {?rel osmkey:route "train" } . 26 | ?rel osmrel:member ?mem . 27 | ?rel osmkey:service "commuter" . 28 | ?mem osm:id ?osm_id . 29 | ?osm_id geo:hasGeometry ?geom . 30 | { ?osm_id osmkey:public_transport "stop_position" } UNION { ?osm_id osmkey:public_transport "station" } . 31 | ?osm_id osmkey:name ?station_label . 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /queries/rail.sparql: -------------------------------------------------------------------------------- 1 | PREFIX ogc: 2 | PREFIX osmrel: 3 | PREFIX geo: 4 | PREFIX osm: 5 | PREFIX rdf: 6 | PREFIX osmkey: 7 | PREFIX osm2rdf: 8 | SELECT ?color ?id ?label ?station_label ?station_id ?geom WHERE { 9 | { 10 | {?rel osmkey:route "railway"} UNION {?rel osmkey:route "train" } . 11 | ?rel osmrel:member ?mem . 12 | {?rel osmkey:service "night"} UNION {?rel osmkey:service "national"} UNION {?rel osmkey:service "long_distance"} UNION {?rel osmkey:service "high_speed" } UNION {?rel osmkey:highspeed "yes" } . 13 | OPTIONAL {?rel osmkey:colour ?color} . 14 | OPTIONAL {?rel osmkey:colour ?id} . 15 | OPTIONAL {?rel osmkey:ref ?label} . 16 | ?mem osm:id ?osm_id . 17 | ?osm_id geo:hasGeometry ?geom . 18 | { ?osm_id osmkey:railway "rail" } UNION { ?osm_id osmkey:railway "light_rail" }. 19 | } UNION { 20 | {?rel osmkey:route "railway"} UNION {?rel osmkey:route "train" } . 21 | ?rel osmrel:member ?mem . 22 | {?rel osmkey:service "night"} UNION {?rel osmkey:service "national"} UNION {?rel osmkey:service "long_distance"} UNION {?rel osmkey:service "high_speed" } UNION {?rel osmkey:highspeed "yes" } . 23 | ?mem osm:id ?osm_id . 24 | ?osm_id geo:hasGeometry ?geom . 25 | { ?osm_id osmkey:public_transport "stop_position" } UNION { ?osm_id osmkey:public_transport "station" } UNION { ?osm_id osmkey:railway "station" } . 26 | ?osm_id osmkey:name ?station_label . 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /queries/subway-lightrail.sparql: -------------------------------------------------------------------------------- 1 | PREFIX ogc: 2 | PREFIX osmrel: 3 | PREFIX geo: 4 | PREFIX osm: 5 | PREFIX rdf: 6 | PREFIX osmkey: 7 | PREFIX osm2rdf: 8 | SELECT ?color ?id ?label ?station_label ?station_id ?geom WHERE { 9 | { 10 | { ?rel osmkey:route "light_rail" } UNION { ?rel osmkey:route "subway" } . 11 | ?rel osmrel:member ?mem . 12 | OPTIONAL {?rel osmkey:colour ?color} . 13 | OPTIONAL {?rel osmkey:colour ?id} . 14 | ?rel osmkey:ref ?label . 15 | ?mem osm:id ?osm_id . 16 | ?osm_id geo:hasGeometry ?geom . 17 | { ?osm_id osmkey:railway "tram" } UNION { ?osm_id osmkey:railway "subway" } UNION { ?osm_id osmkey:railway "light_rail" }. 18 | } UNION { 19 | ?osm_id geo:hasGeometry ?geom . 20 | ?osm_id osmkey:public_transport "stop_position" . 21 | ?osm_id osmkey:name ?station_label . 22 | { ?osm_id osmkey:subway "yes" } UNION { ?osm_id osmkey:light_rail "yes" }. 23 | } UNION { 24 | { ?rel osmkey:route "light_rail" } UNION { ?rel osmkey:route "subway" } . 25 | ?rel osmrel:member ?mem . 26 | ?mem osm:id ?osm_id . 27 | ?osm_id geo:hasGeometry ?geom . 28 | ?osm_id osmkey:public_transport "stop_position" . 29 | ?osm_id osmkey:name ?station_label . 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /queries/tram.sparql: -------------------------------------------------------------------------------- 1 | PREFIX ogc: 2 | PREFIX osmrel: 3 | PREFIX geo: 4 | PREFIX osm: 5 | PREFIX rdf: 6 | PREFIX osmkey: 7 | PREFIX osm2rdf: 8 | SELECT ?color ?id ?label ?station_label ?station_id ?geom WHERE { 9 | { 10 | ?rel osmkey:route "tram" . 11 | ?rel osmrel:member ?mem . 12 | OPTIONAL {?rel osmkey:colour ?color} . 13 | OPTIONAL {?rel osmkey:colour ?id} . 14 | ?rel osmkey:ref ?label . 15 | ?mem osm:id ?osm_id . 16 | ?osm_id geo:hasGeometry ?geom . 17 | { ?osm_id osmkey:railway "tram" } UNION { ?osm_id osmkey:railway "subway" } UNION { ?osm_id osmkey:railway "light_rail" }. 18 | } UNION { 19 | ?osm_id geo:hasGeometry ?geom . 20 | ?osm_id osmkey:public_transport "stop_position" . 21 | ?osm_id osmkey:name ?station_label . 22 | { ?osm_id osmkey:tram "yes" } UNION { ?osm_id osmkey:subway "yes" } UNION { ?osm_id osmkey:light_rail "yes" }. 23 | } UNION { 24 | ?rel osmkey:route "tram" . 25 | ?rel osmrel:member ?mem . 26 | ?mem osm:id ?osm_id . 27 | ?osm_id geo:hasGeometry ?geom . 28 | ?osm_id osmkey:public_transport "stop_position" . 29 | ?osm_id osmkey:name ?station_label . 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /tiles/Leaflet.VectorGrid.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function __$strToBlobUri(str, mime, isBinary) {try {return window.URL.createObjectURL(new Blob([Uint8Array.from(str.split('').map(function(c) {return c.charCodeAt(0)}))], {type: mime}));} catch (e) {return "data:" + mime + (isBinary ? ";base64," : ",") + str;}} 4 | L.SVG.Tile = L.SVG.extend({ 5 | 6 | initialize: function (tileCoord, tileSize, options) { 7 | L.SVG.prototype.initialize.call(this, options); 8 | this._tileCoord = tileCoord; 9 | this._size = tileSize; 10 | 11 | this._initContainer(); 12 | this._container.setAttribute('width', this._size.x); 13 | this._container.setAttribute('height', this._size.y); 14 | this._container.setAttribute('viewBox', [0, 0, this._size.x, this._size.y].join(' ')); 15 | 16 | this._layers = {}; 17 | }, 18 | 19 | getCoord: function() { 20 | return this._tileCoord; 21 | }, 22 | 23 | getContainer: function() { 24 | return this._container; 25 | }, 26 | 27 | onAdd: L.Util.falseFn, 28 | 29 | addTo: function(map) { 30 | this._map = map; 31 | if (this.options.interactive) { 32 | for (var i in this._layers) { 33 | var layer = this._layers[i]; 34 | // By default, Leaflet tiles do not have pointer events. 35 | layer._path.style.pointerEvents = 'auto'; 36 | this._map._targets[L.stamp(layer._path)] = layer; 37 | } 38 | } 39 | }, 40 | 41 | removeFrom: function (map) { 42 | if (this.options.interactive) { 43 | for (var i in this._layers) { 44 | var layer = this._layers[i]; 45 | delete this._map._targets[L.stamp(layer._path)]; 46 | } 47 | } 48 | delete this._map; 49 | }, 50 | 51 | _initContainer: function() { 52 | L.SVG.prototype._initContainer.call(this); 53 | var rect = L.SVG.create('rect'); 54 | }, 55 | 56 | /// TODO: Modify _initPath to include an extra parameter, a group name 57 | /// to order symbolizers by z-index 58 | 59 | _addPath: function (layer) { 60 | this._rootGroup.appendChild(layer._path); 61 | this._layers[L.stamp(layer)] = layer; 62 | }, 63 | 64 | _updateIcon: function (layer) { 65 | var path = layer._path = L.SVG.create('image'), 66 | icon = layer.options.icon, 67 | options = icon.options, 68 | size = L.point(options.iconSize), 69 | anchor = options.iconAnchor || 70 | size && size.divideBy(2, true), 71 | p = layer._point.subtract(anchor); 72 | path.setAttribute('x', p.x); 73 | path.setAttribute('y', p.y); 74 | path.setAttribute('width', size.x + 'px'); 75 | path.setAttribute('height', size.y + 'px'); 76 | path.setAttribute('href', options.iconUrl); 77 | } 78 | }); 79 | 80 | 81 | L.svg.tile = function(tileCoord, tileSize, opts){ 82 | return new L.SVG.Tile(tileCoord, tileSize, opts); 83 | }; 84 | 85 | // 🍂class Symbolizer 86 | // 🍂inherits Class 87 | // The abstract Symbolizer class is mostly equivalent in concept to a `L.Path` - it's an interface for 88 | // polylines, polygons and circles. But instead of representing leaflet Layers, 89 | // it represents things that have to be drawn inside a vector tile. 90 | 91 | // A vector tile *data layer* might have zero, one, or more *symbolizer definitions* 92 | // A vector tile *feature* might have zero, one, or more *symbolizers*. 93 | // The actual symbolizers applied will depend on filters and the symbolizer functions. 94 | 95 | var Symbolizer = L.Class.extend({ 96 | // 🍂method initialize(feature: GeoJSON, pxPerExtent: Number) 97 | // Initializes a new Line Symbolizer given a GeoJSON feature and the 98 | // pixel-to-coordinate-units ratio. Internal use only. 99 | 100 | // 🍂method render(renderer, style) 101 | // Renders this symbolizer in the given tiled renderer, with the given 102 | // `L.Path` options. Internal use only. 103 | render: function(renderer, style) { 104 | this._renderer = renderer; 105 | this.options = style; 106 | renderer._initPath(this); 107 | renderer._updateStyle(this); 108 | }, 109 | 110 | // 🍂method render(renderer, style) 111 | // Updates the `L.Path` options used to style this symbolizer, and re-renders it. 112 | // Internal use only. 113 | updateStyle: function(renderer, style) { 114 | this.options = style; 115 | renderer._updateStyle(this); 116 | }, 117 | 118 | _getPixelBounds: function() { 119 | var parts = this._parts; 120 | var bounds = L.bounds([]); 121 | for (var i = 0; i < parts.length; i++) { 122 | var part = parts[i]; 123 | for (var j = 0; j < part.length; j++) { 124 | bounds.extend(part[j]); 125 | } 126 | } 127 | 128 | var w = this._clickTolerance(), 129 | p = new L.Point(w, w); 130 | 131 | bounds.min._subtract(p); 132 | bounds.max._add(p); 133 | 134 | return bounds; 135 | }, 136 | _clickTolerance: L.Path.prototype._clickTolerance, 137 | }); 138 | 139 | // Contains mixins which are common to the Line Symbolizer and the Fill Symbolizer. 140 | 141 | var PolyBase = { 142 | _makeFeatureParts: function(feat, pxPerExtent) { 143 | var rings = feat.geometry; 144 | var coord; 145 | 146 | this._parts = []; 147 | for (var i = 0; i < rings.length; i++) { 148 | var ring = rings[i]; 149 | var part = []; 150 | for (var j = 0; j < ring.length; j++) { 151 | coord = ring[j]; 152 | // Protobuf vector tiles return {x: , y:} 153 | // Geojson-vt returns [,] 154 | part.push(L.point(coord).scaleBy(pxPerExtent)); 155 | } 156 | this._parts.push(part); 157 | } 158 | }, 159 | 160 | makeInteractive: function() { 161 | this._pxBounds = this._getPixelBounds(); 162 | } 163 | }; 164 | 165 | // 🍂class PointSymbolizer 166 | // 🍂inherits CircleMarker 167 | // A symbolizer for points. 168 | 169 | var PointSymbolizer = L.CircleMarker.extend({ 170 | includes: Symbolizer.prototype, 171 | 172 | statics: { 173 | iconCache: {} 174 | }, 175 | 176 | initialize: function(feature, pxPerExtent) { 177 | this.properties = feature.properties; 178 | this._makeFeatureParts(feature, pxPerExtent); 179 | }, 180 | 181 | render: function(renderer, style) { 182 | Symbolizer.prototype.render.call(this, renderer, style); 183 | this._radius = style.radius || L.CircleMarker.prototype.options.radius; 184 | this._updatePath(); 185 | }, 186 | 187 | _makeFeatureParts: function(feat, pxPerExtent) { 188 | var coord = feat.geometry[0]; 189 | if (typeof coord[0] === 'object' && 'x' in coord[0]) { 190 | // Protobuf vector tiles return [{x: , y:}] 191 | this._point = L.point(coord[0]).scaleBy(pxPerExtent); 192 | this._empty = L.Util.falseFn; 193 | } else { 194 | // Geojson-vt returns [,] 195 | this._point = L.point(coord).scaleBy(pxPerExtent); 196 | this._empty = L.Util.falseFn; 197 | } 198 | }, 199 | 200 | makeInteractive: function() { 201 | this._updateBounds(); 202 | }, 203 | 204 | updateStyle: function(renderer, style) { 205 | this._radius = style.radius || this._radius; 206 | this._updateBounds(); 207 | return Symbolizer.prototype.updateStyle.call(this, renderer, style); 208 | }, 209 | 210 | _updateBounds: function() { 211 | var icon = this.options.icon; 212 | if (icon) { 213 | var size = L.point(icon.options.iconSize), 214 | anchor = icon.options.iconAnchor || 215 | size && size.divideBy(2, true), 216 | p = this._point.subtract(anchor); 217 | this._pxBounds = new L.Bounds(p, p.add(icon.options.iconSize)); 218 | } else { 219 | L.CircleMarker.prototype._updateBounds.call(this); 220 | } 221 | }, 222 | 223 | _updatePath: function() { 224 | if (this.options.icon) { 225 | this._renderer._updateIcon(this); 226 | } else { 227 | L.CircleMarker.prototype._updatePath.call(this); 228 | } 229 | }, 230 | 231 | _getImage: function () { 232 | if (this.options.icon) { 233 | var url = this.options.icon.options.iconUrl, 234 | img = PointSymbolizer.iconCache[url]; 235 | if (!img) { 236 | var icon = this.options.icon; 237 | img = PointSymbolizer.iconCache[url] = icon.createIcon(); 238 | } 239 | return img; 240 | } else { 241 | return null; 242 | } 243 | 244 | }, 245 | 246 | _containsPoint: function(p) { 247 | var icon = this.options.icon; 248 | if (icon) { 249 | return this._pxBounds.contains(p); 250 | } else { 251 | return L.CircleMarker.prototype._containsPoint.call(this, p); 252 | } 253 | } 254 | }); 255 | 256 | // 🍂class LineSymbolizer 257 | // 🍂inherits Polyline 258 | // A symbolizer for lines. Can be applied to line and polygon features. 259 | 260 | var LineSymbolizer = L.Polyline.extend({ 261 | includes: [Symbolizer.prototype, PolyBase], 262 | 263 | initialize: function(feature, pxPerExtent) { 264 | this.properties = feature.properties; 265 | this._makeFeatureParts(feature, pxPerExtent); 266 | }, 267 | 268 | render: function(renderer, style) { 269 | style.fill = false; 270 | Symbolizer.prototype.render.call(this, renderer, style); 271 | this._updatePath(); 272 | }, 273 | 274 | updateStyle: function(renderer, style) { 275 | style.fill = false; 276 | Symbolizer.prototype.updateStyle.call(this, renderer, style); 277 | }, 278 | }); 279 | 280 | // 🍂class FillSymbolizer 281 | // 🍂inherits Polyline 282 | // A symbolizer for filled areas. Applies only to polygon features. 283 | 284 | var FillSymbolizer = L.Polygon.extend({ 285 | includes: [Symbolizer.prototype, PolyBase], 286 | 287 | initialize: function(feature, pxPerExtent) { 288 | this.properties = feature.properties; 289 | this._makeFeatureParts(feature, pxPerExtent); 290 | }, 291 | 292 | render: function(renderer, style) { 293 | Symbolizer.prototype.render.call(this, renderer, style); 294 | this._updatePath(); 295 | } 296 | }); 297 | 298 | /* 🍂class VectorGrid 299 | * 🍂inherits GridLayer 300 | * 301 | * A `VectorGrid` is a generic, abstract class for displaying tiled vector data. 302 | * it provides facilities for symbolizing and rendering the data in the vector 303 | * tiles, but lacks the functionality to fetch the vector tiles from wherever 304 | * they are. 305 | * 306 | * Extends Leaflet's `L.GridLayer`. 307 | */ 308 | 309 | L.VectorGrid = L.GridLayer.extend({ 310 | 311 | options: { 312 | // 🍂option rendererFactory = L.svg.tile 313 | // A factory method which will be used to instantiate the per-tile renderers. 314 | rendererFactory: L.svg.tile, 315 | 316 | // 🍂option vectorTileLayerStyles: Object = {} 317 | // A data structure holding initial symbolizer definitions for the vector features. 318 | vectorTileLayerStyles: {}, 319 | 320 | // 🍂option interactive: Boolean = false 321 | // Whether this `VectorGrid` fires `Interactive Layer` events. 322 | interactive: false, 323 | 324 | // 🍂option getFeatureId: Function = undefined 325 | // A function that, given a vector feature, returns an unique identifier for it, e.g. 326 | // `function(feat) { return feat.properties.uniqueIdField; }`. 327 | // Must be defined for `setFeatureStyle` to work. 328 | }, 329 | 330 | initialize: function(options) { 331 | L.setOptions(this, options); 332 | L.GridLayer.prototype.initialize.apply(this, arguments); 333 | if (this.options.getFeatureId) { 334 | this._vectorTiles = {}; 335 | this._overriddenStyles = {}; 336 | this.on('tileunload', function(e) { 337 | var key = this._tileCoordsToKey(e.coords), 338 | tile = this._vectorTiles[key]; 339 | 340 | if (tile && this._map) { 341 | tile.removeFrom(this._map); 342 | } 343 | delete this._vectorTiles[key]; 344 | }, this); 345 | } 346 | this._dataLayerNames = {}; 347 | }, 348 | 349 | createTile: function(coords, done) { 350 | var storeFeatures = this.options.getFeatureId; 351 | 352 | var tileSize = this.getTileSize(); 353 | var renderer = this.options.rendererFactory(coords, tileSize, this.options); 354 | 355 | var vectorTilePromise = this._getVectorTilePromise(coords); 356 | 357 | if (storeFeatures) { 358 | this._vectorTiles[this._tileCoordsToKey(coords)] = renderer; 359 | renderer._features = {}; 360 | } 361 | 362 | vectorTilePromise.then( function renderTile(vectorTile) { 363 | for (var layerName in vectorTile.layers) { 364 | this._dataLayerNames[layerName] = true; 365 | var layer = vectorTile.layers[layerName]; 366 | 367 | var pxPerExtent = this.getTileSize().divideBy(layer.extent); 368 | 369 | var layerStyle = this.options.vectorTileLayerStyles[ layerName ] || 370 | L.Path.prototype.options; 371 | 372 | for (var i = 0; i < layer.features.length; i++) { 373 | var feat = layer.features[i]; 374 | var id; 375 | 376 | var styleOptions = layerStyle; 377 | if (storeFeatures) { 378 | id = this.options.getFeatureId(feat); 379 | var styleOverride = this._overriddenStyles[id]; 380 | if (styleOverride) { 381 | if (styleOverride[layerName]) { 382 | styleOptions = styleOverride[layerName]; 383 | } else { 384 | styleOptions = styleOverride; 385 | } 386 | } 387 | } 388 | 389 | if (styleOptions instanceof Function) { 390 | styleOptions = styleOptions(feat.properties, coords.z); 391 | } 392 | 393 | if (!(styleOptions instanceof Array)) { 394 | styleOptions = [styleOptions]; 395 | } 396 | 397 | if (!styleOptions.length) { 398 | continue; 399 | } 400 | 401 | var featureLayer = this._createLayer(feat, pxPerExtent); 402 | 403 | for (var j = 0; j < styleOptions.length; j++) { 404 | var style = L.extend({}, L.Path.prototype.options, styleOptions[j]); 405 | featureLayer.render(renderer, style); 406 | renderer._addPath(featureLayer); 407 | } 408 | 409 | if (this.options.interactive) { 410 | featureLayer.makeInteractive(); 411 | } 412 | 413 | if (storeFeatures) { 414 | renderer._features[id] = { 415 | layerName: layerName, 416 | feature: featureLayer 417 | }; 418 | } 419 | } 420 | 421 | } 422 | if (this._map != null) { 423 | renderer.addTo(this._map); 424 | } 425 | L.Util.requestAnimFrame(done.bind(coords, null, null)); 426 | }.bind(this)); 427 | 428 | return renderer.getContainer(); 429 | }, 430 | 431 | // 🍂method setFeatureStyle(id: Number, layerStyle: L.Path Options): this 432 | // Given the unique ID for a vector features (as per the `getFeatureId` option), 433 | // re-symbolizes that feature across all tiles it appears in. 434 | setFeatureStyle: function(id, layerStyle) { 435 | this._overriddenStyles[id] = layerStyle; 436 | 437 | for (var tileKey in this._vectorTiles) { 438 | var tile = this._vectorTiles[tileKey]; 439 | var features = tile._features; 440 | var data = features[id]; 441 | if (data) { 442 | var feat = data.feature; 443 | 444 | var styleOptions = layerStyle; 445 | if (layerStyle[data.layerName]) { 446 | styleOptions = layerStyle[data.layerName]; 447 | } 448 | 449 | this._updateStyles(feat, tile, styleOptions); 450 | } 451 | } 452 | return this; 453 | }, 454 | 455 | // 🍂method setFeatureStyle(id: Number): this 456 | // Reverts the effects of a previous `setFeatureStyle` call. 457 | resetFeatureStyle: function(id) { 458 | delete this._overriddenStyles[id]; 459 | 460 | for (var tileKey in this._vectorTiles) { 461 | var tile = this._vectorTiles[tileKey]; 462 | var features = tile._features; 463 | var data = features[id]; 464 | if (data) { 465 | var feat = data.feature; 466 | var styleOptions = this.options.vectorTileLayerStyles[ data.layerName ] || 467 | L.Path.prototype.options; 468 | this._updateStyles(feat, tile, styleOptions); 469 | } 470 | } 471 | return this; 472 | }, 473 | 474 | // 🍂method getDataLayerNames(): Array 475 | // Returns an array of strings, with all the known names of data layers in 476 | // the vector tiles displayed. Useful for introspection. 477 | getDataLayerNames: function() { 478 | return Object.keys(this._dataLayerNames); 479 | }, 480 | 481 | _updateStyles: function(feat, renderer, styleOptions) { 482 | styleOptions = (styleOptions instanceof Function) ? 483 | styleOptions(feat.properties, renderer.getCoord().z) : 484 | styleOptions; 485 | 486 | if (!(styleOptions instanceof Array)) { 487 | styleOptions = [styleOptions]; 488 | } 489 | 490 | for (var j = 0; j < styleOptions.length; j++) { 491 | var style = L.extend({}, L.Path.prototype.options, styleOptions[j]); 492 | feat.updateStyle(renderer, style); 493 | } 494 | }, 495 | 496 | _createLayer: function(feat, pxPerExtent, layerStyle) { 497 | var layer; 498 | switch (feat.type) { 499 | case 1: 500 | layer = new PointSymbolizer(feat, pxPerExtent); 501 | break; 502 | case 2: 503 | layer = new LineSymbolizer(feat, pxPerExtent); 504 | break; 505 | case 3: 506 | layer = new FillSymbolizer(feat, pxPerExtent); 507 | break; 508 | } 509 | 510 | if (this.options.interactive) { 511 | layer.addEventParent(this); 512 | } 513 | 514 | return layer; 515 | }, 516 | }); 517 | 518 | /* 519 | * 🍂section Extension methods 520 | * 521 | * Classes inheriting from `VectorGrid` **must** define the `_getVectorTilePromise` private method. 522 | * 523 | * 🍂method getVectorTilePromise(coords: Object): Promise 524 | * Given a `coords` object in the form of `{x: Number, y: Number, z: Number}`, 525 | * this function must return a `Promise` for a vector tile. 526 | * 527 | */ 528 | L.vectorGrid = function (options) { 529 | return new L.VectorGrid(options); 530 | }; 531 | 532 | var read = function (buffer, offset, isLE, mLen, nBytes) { 533 | var e, m; 534 | var eLen = nBytes * 8 - mLen - 1; 535 | var eMax = (1 << eLen) - 1; 536 | var eBias = eMax >> 1; 537 | var nBits = -7; 538 | var i = isLE ? (nBytes - 1) : 0; 539 | var d = isLE ? -1 : 1; 540 | var s = buffer[offset + i]; 541 | 542 | i += d; 543 | 544 | e = s & ((1 << (-nBits)) - 1); 545 | s >>= (-nBits); 546 | nBits += eLen; 547 | for (; nBits > 0; e = e * 256 + buffer[offset + i], i += d, nBits -= 8) {} 548 | 549 | m = e & ((1 << (-nBits)) - 1); 550 | e >>= (-nBits); 551 | nBits += mLen; 552 | for (; nBits > 0; m = m * 256 + buffer[offset + i], i += d, nBits -= 8) {} 553 | 554 | if (e === 0) { 555 | e = 1 - eBias; 556 | } else if (e === eMax) { 557 | return m ? NaN : ((s ? -1 : 1) * Infinity) 558 | } else { 559 | m = m + Math.pow(2, mLen); 560 | e = e - eBias; 561 | } 562 | return (s ? -1 : 1) * m * Math.pow(2, e - mLen) 563 | }; 564 | 565 | var write = function (buffer, value, offset, isLE, mLen, nBytes) { 566 | var e, m, c; 567 | var eLen = nBytes * 8 - mLen - 1; 568 | var eMax = (1 << eLen) - 1; 569 | var eBias = eMax >> 1; 570 | var rt = (mLen === 23 ? Math.pow(2, -24) - Math.pow(2, -77) : 0); 571 | var i = isLE ? 0 : (nBytes - 1); 572 | var d = isLE ? 1 : -1; 573 | var s = value < 0 || (value === 0 && 1 / value < 0) ? 1 : 0; 574 | 575 | value = Math.abs(value); 576 | 577 | if (isNaN(value) || value === Infinity) { 578 | m = isNaN(value) ? 1 : 0; 579 | e = eMax; 580 | } else { 581 | e = Math.floor(Math.log(value) / Math.LN2); 582 | if (value * (c = Math.pow(2, -e)) < 1) { 583 | e--; 584 | c *= 2; 585 | } 586 | if (e + eBias >= 1) { 587 | value += rt / c; 588 | } else { 589 | value += rt * Math.pow(2, 1 - eBias); 590 | } 591 | if (value * c >= 2) { 592 | e++; 593 | c /= 2; 594 | } 595 | 596 | if (e + eBias >= eMax) { 597 | m = 0; 598 | e = eMax; 599 | } else if (e + eBias >= 1) { 600 | m = (value * c - 1) * Math.pow(2, mLen); 601 | e = e + eBias; 602 | } else { 603 | m = value * Math.pow(2, eBias - 1) * Math.pow(2, mLen); 604 | e = 0; 605 | } 606 | } 607 | 608 | for (; mLen >= 8; buffer[offset + i] = m & 0xff, i += d, m /= 256, mLen -= 8) {} 609 | 610 | e = (e << mLen) | m; 611 | eLen += mLen; 612 | for (; eLen > 0; buffer[offset + i] = e & 0xff, i += d, e /= 256, eLen -= 8) {} 613 | 614 | buffer[offset + i - d] |= s * 128; 615 | }; 616 | 617 | var index$1 = { 618 | read: read, 619 | write: write 620 | }; 621 | 622 | var index = Pbf; 623 | 624 | var ieee754 = index$1; 625 | 626 | function Pbf(buf) { 627 | this.buf = ArrayBuffer.isView && ArrayBuffer.isView(buf) ? buf : new Uint8Array(buf || 0); 628 | this.pos = 0; 629 | this.type = 0; 630 | this.length = this.buf.length; 631 | } 632 | 633 | Pbf.Varint = 0; // varint: int32, int64, uint32, uint64, sint32, sint64, bool, enum 634 | Pbf.Fixed64 = 1; // 64-bit: double, fixed64, sfixed64 635 | Pbf.Bytes = 2; // length-delimited: string, bytes, embedded messages, packed repeated fields 636 | Pbf.Fixed32 = 5; // 32-bit: float, fixed32, sfixed32 637 | 638 | var SHIFT_LEFT_32 = (1 << 16) * (1 << 16); 639 | var SHIFT_RIGHT_32 = 1 / SHIFT_LEFT_32; 640 | 641 | Pbf.prototype = { 642 | 643 | destroy: function() { 644 | this.buf = null; 645 | }, 646 | 647 | // === READING ================================================================= 648 | 649 | readFields: function(readField, result, end) { 650 | var this$1 = this; 651 | 652 | end = end || this.length; 653 | 654 | while (this.pos < end) { 655 | var val = this$1.readVarint(), 656 | tag = val >> 3, 657 | startPos = this$1.pos; 658 | 659 | this$1.type = val & 0x7; 660 | readField(tag, result, this$1); 661 | 662 | if (this$1.pos === startPos) { this$1.skip(val); } 663 | } 664 | return result; 665 | }, 666 | 667 | readMessage: function(readField, result) { 668 | return this.readFields(readField, result, this.readVarint() + this.pos); 669 | }, 670 | 671 | readFixed32: function() { 672 | var val = readUInt32(this.buf, this.pos); 673 | this.pos += 4; 674 | return val; 675 | }, 676 | 677 | readSFixed32: function() { 678 | var val = readInt32(this.buf, this.pos); 679 | this.pos += 4; 680 | return val; 681 | }, 682 | 683 | // 64-bit int handling is based on github.com/dpw/node-buffer-more-ints (MIT-licensed) 684 | 685 | readFixed64: function() { 686 | var val = readUInt32(this.buf, this.pos) + readUInt32(this.buf, this.pos + 4) * SHIFT_LEFT_32; 687 | this.pos += 8; 688 | return val; 689 | }, 690 | 691 | readSFixed64: function() { 692 | var val = readUInt32(this.buf, this.pos) + readInt32(this.buf, this.pos + 4) * SHIFT_LEFT_32; 693 | this.pos += 8; 694 | return val; 695 | }, 696 | 697 | readFloat: function() { 698 | var val = ieee754.read(this.buf, this.pos, true, 23, 4); 699 | this.pos += 4; 700 | return val; 701 | }, 702 | 703 | readDouble: function() { 704 | var val = ieee754.read(this.buf, this.pos, true, 52, 8); 705 | this.pos += 8; 706 | return val; 707 | }, 708 | 709 | readVarint: function(isSigned) { 710 | var buf = this.buf, 711 | val, b; 712 | 713 | b = buf[this.pos++]; val = b & 0x7f; if (b < 0x80) { return val; } 714 | b = buf[this.pos++]; val |= (b & 0x7f) << 7; if (b < 0x80) { return val; } 715 | b = buf[this.pos++]; val |= (b & 0x7f) << 14; if (b < 0x80) { return val; } 716 | b = buf[this.pos++]; val |= (b & 0x7f) << 21; if (b < 0x80) { return val; } 717 | b = buf[this.pos]; val |= (b & 0x0f) << 28; 718 | 719 | return readVarintRemainder(val, isSigned, this); 720 | }, 721 | 722 | readVarint64: function() { // for compatibility with v2.0.1 723 | return this.readVarint(true); 724 | }, 725 | 726 | readSVarint: function() { 727 | var num = this.readVarint(); 728 | return num % 2 === 1 ? (num + 1) / -2 : num / 2; // zigzag encoding 729 | }, 730 | 731 | readBoolean: function() { 732 | return Boolean(this.readVarint()); 733 | }, 734 | 735 | readString: function() { 736 | var end = this.readVarint() + this.pos, 737 | str = readUtf8(this.buf, this.pos, end); 738 | this.pos = end; 739 | return str; 740 | }, 741 | 742 | readBytes: function() { 743 | var end = this.readVarint() + this.pos, 744 | buffer = this.buf.subarray(this.pos, end); 745 | this.pos = end; 746 | return buffer; 747 | }, 748 | 749 | // verbose for performance reasons; doesn't affect gzipped size 750 | 751 | readPackedVarint: function(arr, isSigned) { 752 | var this$1 = this; 753 | 754 | var end = readPackedEnd(this); 755 | arr = arr || []; 756 | while (this.pos < end) { arr.push(this$1.readVarint(isSigned)); } 757 | return arr; 758 | }, 759 | readPackedSVarint: function(arr) { 760 | var this$1 = this; 761 | 762 | var end = readPackedEnd(this); 763 | arr = arr || []; 764 | while (this.pos < end) { arr.push(this$1.readSVarint()); } 765 | return arr; 766 | }, 767 | readPackedBoolean: function(arr) { 768 | var this$1 = this; 769 | 770 | var end = readPackedEnd(this); 771 | arr = arr || []; 772 | while (this.pos < end) { arr.push(this$1.readBoolean()); } 773 | return arr; 774 | }, 775 | readPackedFloat: function(arr) { 776 | var this$1 = this; 777 | 778 | var end = readPackedEnd(this); 779 | arr = arr || []; 780 | while (this.pos < end) { arr.push(this$1.readFloat()); } 781 | return arr; 782 | }, 783 | readPackedDouble: function(arr) { 784 | var this$1 = this; 785 | 786 | var end = readPackedEnd(this); 787 | arr = arr || []; 788 | while (this.pos < end) { arr.push(this$1.readDouble()); } 789 | return arr; 790 | }, 791 | readPackedFixed32: function(arr) { 792 | var this$1 = this; 793 | 794 | var end = readPackedEnd(this); 795 | arr = arr || []; 796 | while (this.pos < end) { arr.push(this$1.readFixed32()); } 797 | return arr; 798 | }, 799 | readPackedSFixed32: function(arr) { 800 | var this$1 = this; 801 | 802 | var end = readPackedEnd(this); 803 | arr = arr || []; 804 | while (this.pos < end) { arr.push(this$1.readSFixed32()); } 805 | return arr; 806 | }, 807 | readPackedFixed64: function(arr) { 808 | var this$1 = this; 809 | 810 | var end = readPackedEnd(this); 811 | arr = arr || []; 812 | while (this.pos < end) { arr.push(this$1.readFixed64()); } 813 | return arr; 814 | }, 815 | readPackedSFixed64: function(arr) { 816 | var this$1 = this; 817 | 818 | var end = readPackedEnd(this); 819 | arr = arr || []; 820 | while (this.pos < end) { arr.push(this$1.readSFixed64()); } 821 | return arr; 822 | }, 823 | 824 | skip: function(val) { 825 | var type = val & 0x7; 826 | if (type === Pbf.Varint) { while (this.buf[this.pos++] > 0x7f) {} } 827 | else if (type === Pbf.Bytes) { this.pos = this.readVarint() + this.pos; } 828 | else if (type === Pbf.Fixed32) { this.pos += 4; } 829 | else if (type === Pbf.Fixed64) { this.pos += 8; } 830 | else { throw new Error('Unimplemented type: ' + type); } 831 | }, 832 | 833 | // === WRITING ================================================================= 834 | 835 | writeTag: function(tag, type) { 836 | this.writeVarint((tag << 3) | type); 837 | }, 838 | 839 | realloc: function(min) { 840 | var length = this.length || 16; 841 | 842 | while (length < this.pos + min) { length *= 2; } 843 | 844 | if (length !== this.length) { 845 | var buf = new Uint8Array(length); 846 | buf.set(this.buf); 847 | this.buf = buf; 848 | this.length = length; 849 | } 850 | }, 851 | 852 | finish: function() { 853 | this.length = this.pos; 854 | this.pos = 0; 855 | return this.buf.subarray(0, this.length); 856 | }, 857 | 858 | writeFixed32: function(val) { 859 | this.realloc(4); 860 | writeInt32(this.buf, val, this.pos); 861 | this.pos += 4; 862 | }, 863 | 864 | writeSFixed32: function(val) { 865 | this.realloc(4); 866 | writeInt32(this.buf, val, this.pos); 867 | this.pos += 4; 868 | }, 869 | 870 | writeFixed64: function(val) { 871 | this.realloc(8); 872 | writeInt32(this.buf, val & -1, this.pos); 873 | writeInt32(this.buf, Math.floor(val * SHIFT_RIGHT_32), this.pos + 4); 874 | this.pos += 8; 875 | }, 876 | 877 | writeSFixed64: function(val) { 878 | this.realloc(8); 879 | writeInt32(this.buf, val & -1, this.pos); 880 | writeInt32(this.buf, Math.floor(val * SHIFT_RIGHT_32), this.pos + 4); 881 | this.pos += 8; 882 | }, 883 | 884 | writeVarint: function(val) { 885 | val = +val || 0; 886 | 887 | if (val > 0xfffffff || val < 0) { 888 | writeBigVarint(val, this); 889 | return; 890 | } 891 | 892 | this.realloc(4); 893 | 894 | this.buf[this.pos++] = val & 0x7f | (val > 0x7f ? 0x80 : 0); if (val <= 0x7f) { return; } 895 | this.buf[this.pos++] = ((val >>>= 7) & 0x7f) | (val > 0x7f ? 0x80 : 0); if (val <= 0x7f) { return; } 896 | this.buf[this.pos++] = ((val >>>= 7) & 0x7f) | (val > 0x7f ? 0x80 : 0); if (val <= 0x7f) { return; } 897 | this.buf[this.pos++] = (val >>> 7) & 0x7f; 898 | }, 899 | 900 | writeSVarint: function(val) { 901 | this.writeVarint(val < 0 ? -val * 2 - 1 : val * 2); 902 | }, 903 | 904 | writeBoolean: function(val) { 905 | this.writeVarint(Boolean(val)); 906 | }, 907 | 908 | writeString: function(str) { 909 | str = String(str); 910 | this.realloc(str.length * 4); 911 | 912 | this.pos++; // reserve 1 byte for short string length 913 | 914 | var startPos = this.pos; 915 | // write the string directly to the buffer and see how much was written 916 | this.pos = writeUtf8(this.buf, str, this.pos); 917 | var len = this.pos - startPos; 918 | 919 | if (len >= 0x80) { makeRoomForExtraLength(startPos, len, this); } 920 | 921 | // finally, write the message length in the reserved place and restore the position 922 | this.pos = startPos - 1; 923 | this.writeVarint(len); 924 | this.pos += len; 925 | }, 926 | 927 | writeFloat: function(val) { 928 | this.realloc(4); 929 | ieee754.write(this.buf, val, this.pos, true, 23, 4); 930 | this.pos += 4; 931 | }, 932 | 933 | writeDouble: function(val) { 934 | this.realloc(8); 935 | ieee754.write(this.buf, val, this.pos, true, 52, 8); 936 | this.pos += 8; 937 | }, 938 | 939 | writeBytes: function(buffer) { 940 | var this$1 = this; 941 | 942 | var len = buffer.length; 943 | this.writeVarint(len); 944 | this.realloc(len); 945 | for (var i = 0; i < len; i++) { this$1.buf[this$1.pos++] = buffer[i]; } 946 | }, 947 | 948 | writeRawMessage: function(fn, obj) { 949 | this.pos++; // reserve 1 byte for short message length 950 | 951 | // write the message directly to the buffer and see how much was written 952 | var startPos = this.pos; 953 | fn(obj, this); 954 | var len = this.pos - startPos; 955 | 956 | if (len >= 0x80) { makeRoomForExtraLength(startPos, len, this); } 957 | 958 | // finally, write the message length in the reserved place and restore the position 959 | this.pos = startPos - 1; 960 | this.writeVarint(len); 961 | this.pos += len; 962 | }, 963 | 964 | writeMessage: function(tag, fn, obj) { 965 | this.writeTag(tag, Pbf.Bytes); 966 | this.writeRawMessage(fn, obj); 967 | }, 968 | 969 | writePackedVarint: function(tag, arr) { this.writeMessage(tag, writePackedVarint, arr); }, 970 | writePackedSVarint: function(tag, arr) { this.writeMessage(tag, writePackedSVarint, arr); }, 971 | writePackedBoolean: function(tag, arr) { this.writeMessage(tag, writePackedBoolean, arr); }, 972 | writePackedFloat: function(tag, arr) { this.writeMessage(tag, writePackedFloat, arr); }, 973 | writePackedDouble: function(tag, arr) { this.writeMessage(tag, writePackedDouble, arr); }, 974 | writePackedFixed32: function(tag, arr) { this.writeMessage(tag, writePackedFixed32, arr); }, 975 | writePackedSFixed32: function(tag, arr) { this.writeMessage(tag, writePackedSFixed32, arr); }, 976 | writePackedFixed64: function(tag, arr) { this.writeMessage(tag, writePackedFixed64, arr); }, 977 | writePackedSFixed64: function(tag, arr) { this.writeMessage(tag, writePackedSFixed64, arr); }, 978 | 979 | writeBytesField: function(tag, buffer) { 980 | this.writeTag(tag, Pbf.Bytes); 981 | this.writeBytes(buffer); 982 | }, 983 | writeFixed32Field: function(tag, val) { 984 | this.writeTag(tag, Pbf.Fixed32); 985 | this.writeFixed32(val); 986 | }, 987 | writeSFixed32Field: function(tag, val) { 988 | this.writeTag(tag, Pbf.Fixed32); 989 | this.writeSFixed32(val); 990 | }, 991 | writeFixed64Field: function(tag, val) { 992 | this.writeTag(tag, Pbf.Fixed64); 993 | this.writeFixed64(val); 994 | }, 995 | writeSFixed64Field: function(tag, val) { 996 | this.writeTag(tag, Pbf.Fixed64); 997 | this.writeSFixed64(val); 998 | }, 999 | writeVarintField: function(tag, val) { 1000 | this.writeTag(tag, Pbf.Varint); 1001 | this.writeVarint(val); 1002 | }, 1003 | writeSVarintField: function(tag, val) { 1004 | this.writeTag(tag, Pbf.Varint); 1005 | this.writeSVarint(val); 1006 | }, 1007 | writeStringField: function(tag, str) { 1008 | this.writeTag(tag, Pbf.Bytes); 1009 | this.writeString(str); 1010 | }, 1011 | writeFloatField: function(tag, val) { 1012 | this.writeTag(tag, Pbf.Fixed32); 1013 | this.writeFloat(val); 1014 | }, 1015 | writeDoubleField: function(tag, val) { 1016 | this.writeTag(tag, Pbf.Fixed64); 1017 | this.writeDouble(val); 1018 | }, 1019 | writeBooleanField: function(tag, val) { 1020 | this.writeVarintField(tag, Boolean(val)); 1021 | } 1022 | }; 1023 | 1024 | function readVarintRemainder(l, s, p) { 1025 | var buf = p.buf, 1026 | h, b; 1027 | 1028 | b = buf[p.pos++]; h = (b & 0x70) >> 4; if (b < 0x80) { return toNum(l, h, s); } 1029 | b = buf[p.pos++]; h |= (b & 0x7f) << 3; if (b < 0x80) { return toNum(l, h, s); } 1030 | b = buf[p.pos++]; h |= (b & 0x7f) << 10; if (b < 0x80) { return toNum(l, h, s); } 1031 | b = buf[p.pos++]; h |= (b & 0x7f) << 17; if (b < 0x80) { return toNum(l, h, s); } 1032 | b = buf[p.pos++]; h |= (b & 0x7f) << 24; if (b < 0x80) { return toNum(l, h, s); } 1033 | b = buf[p.pos++]; h |= (b & 0x01) << 31; if (b < 0x80) { return toNum(l, h, s); } 1034 | 1035 | throw new Error('Expected varint not more than 10 bytes'); 1036 | } 1037 | 1038 | function readPackedEnd(pbf) { 1039 | return pbf.type === Pbf.Bytes ? 1040 | pbf.readVarint() + pbf.pos : pbf.pos + 1; 1041 | } 1042 | 1043 | function toNum(low, high, isSigned) { 1044 | if (isSigned) { 1045 | return high * 0x100000000 + (low >>> 0); 1046 | } 1047 | 1048 | return ((high >>> 0) * 0x100000000) + (low >>> 0); 1049 | } 1050 | 1051 | function writeBigVarint(val, pbf) { 1052 | var low, high; 1053 | 1054 | if (val >= 0) { 1055 | low = (val % 0x100000000) | 0; 1056 | high = (val / 0x100000000) | 0; 1057 | } else { 1058 | low = ~(-val % 0x100000000); 1059 | high = ~(-val / 0x100000000); 1060 | 1061 | if (low ^ 0xffffffff) { 1062 | low = (low + 1) | 0; 1063 | } else { 1064 | low = 0; 1065 | high = (high + 1) | 0; 1066 | } 1067 | } 1068 | 1069 | if (val >= 0x10000000000000000 || val < -0x10000000000000000) { 1070 | throw new Error('Given varint doesn\'t fit into 10 bytes'); 1071 | } 1072 | 1073 | pbf.realloc(10); 1074 | 1075 | writeBigVarintLow(low, high, pbf); 1076 | writeBigVarintHigh(high, pbf); 1077 | } 1078 | 1079 | function writeBigVarintLow(low, high, pbf) { 1080 | pbf.buf[pbf.pos++] = low & 0x7f | 0x80; low >>>= 7; 1081 | pbf.buf[pbf.pos++] = low & 0x7f | 0x80; low >>>= 7; 1082 | pbf.buf[pbf.pos++] = low & 0x7f | 0x80; low >>>= 7; 1083 | pbf.buf[pbf.pos++] = low & 0x7f | 0x80; low >>>= 7; 1084 | pbf.buf[pbf.pos] = low & 0x7f; 1085 | } 1086 | 1087 | function writeBigVarintHigh(high, pbf) { 1088 | var lsb = (high & 0x07) << 4; 1089 | 1090 | pbf.buf[pbf.pos++] |= lsb | ((high >>>= 3) ? 0x80 : 0); if (!high) { return; } 1091 | pbf.buf[pbf.pos++] = high & 0x7f | ((high >>>= 7) ? 0x80 : 0); if (!high) { return; } 1092 | pbf.buf[pbf.pos++] = high & 0x7f | ((high >>>= 7) ? 0x80 : 0); if (!high) { return; } 1093 | pbf.buf[pbf.pos++] = high & 0x7f | ((high >>>= 7) ? 0x80 : 0); if (!high) { return; } 1094 | pbf.buf[pbf.pos++] = high & 0x7f | ((high >>>= 7) ? 0x80 : 0); if (!high) { return; } 1095 | pbf.buf[pbf.pos++] = high & 0x7f; 1096 | } 1097 | 1098 | function makeRoomForExtraLength(startPos, len, pbf) { 1099 | var extraLen = 1100 | len <= 0x3fff ? 1 : 1101 | len <= 0x1fffff ? 2 : 1102 | len <= 0xfffffff ? 3 : Math.ceil(Math.log(len) / (Math.LN2 * 7)); 1103 | 1104 | // if 1 byte isn't enough for encoding message length, shift the data to the right 1105 | pbf.realloc(extraLen); 1106 | for (var i = pbf.pos - 1; i >= startPos; i--) { pbf.buf[i + extraLen] = pbf.buf[i]; } 1107 | } 1108 | 1109 | function writePackedVarint(arr, pbf) { for (var i = 0; i < arr.length; i++) { pbf.writeVarint(arr[i]); } } 1110 | function writePackedSVarint(arr, pbf) { for (var i = 0; i < arr.length; i++) { pbf.writeSVarint(arr[i]); } } 1111 | function writePackedFloat(arr, pbf) { for (var i = 0; i < arr.length; i++) { pbf.writeFloat(arr[i]); } } 1112 | function writePackedDouble(arr, pbf) { for (var i = 0; i < arr.length; i++) { pbf.writeDouble(arr[i]); } } 1113 | function writePackedBoolean(arr, pbf) { for (var i = 0; i < arr.length; i++) { pbf.writeBoolean(arr[i]); } } 1114 | function writePackedFixed32(arr, pbf) { for (var i = 0; i < arr.length; i++) { pbf.writeFixed32(arr[i]); } } 1115 | function writePackedSFixed32(arr, pbf) { for (var i = 0; i < arr.length; i++) { pbf.writeSFixed32(arr[i]); } } 1116 | function writePackedFixed64(arr, pbf) { for (var i = 0; i < arr.length; i++) { pbf.writeFixed64(arr[i]); } } 1117 | function writePackedSFixed64(arr, pbf) { for (var i = 0; i < arr.length; i++) { pbf.writeSFixed64(arr[i]); } } 1118 | 1119 | // Buffer code below from https://github.com/feross/buffer, MIT-licensed 1120 | 1121 | function readUInt32(buf, pos) { 1122 | return ((buf[pos]) | 1123 | (buf[pos + 1] << 8) | 1124 | (buf[pos + 2] << 16)) + 1125 | (buf[pos + 3] * 0x1000000); 1126 | } 1127 | 1128 | function writeInt32(buf, val, pos) { 1129 | buf[pos] = val; 1130 | buf[pos + 1] = (val >>> 8); 1131 | buf[pos + 2] = (val >>> 16); 1132 | buf[pos + 3] = (val >>> 24); 1133 | } 1134 | 1135 | function readInt32(buf, pos) { 1136 | return ((buf[pos]) | 1137 | (buf[pos + 1] << 8) | 1138 | (buf[pos + 2] << 16)) + 1139 | (buf[pos + 3] << 24); 1140 | } 1141 | 1142 | function readUtf8(buf, pos, end) { 1143 | var str = ''; 1144 | var i = pos; 1145 | 1146 | while (i < end) { 1147 | var b0 = buf[i]; 1148 | var c = null; // codepoint 1149 | var bytesPerSequence = 1150 | b0 > 0xEF ? 4 : 1151 | b0 > 0xDF ? 3 : 1152 | b0 > 0xBF ? 2 : 1; 1153 | 1154 | if (i + bytesPerSequence > end) { break; } 1155 | 1156 | var b1, b2, b3; 1157 | 1158 | if (bytesPerSequence === 1) { 1159 | if (b0 < 0x80) { 1160 | c = b0; 1161 | } 1162 | } else if (bytesPerSequence === 2) { 1163 | b1 = buf[i + 1]; 1164 | if ((b1 & 0xC0) === 0x80) { 1165 | c = (b0 & 0x1F) << 0x6 | (b1 & 0x3F); 1166 | if (c <= 0x7F) { 1167 | c = null; 1168 | } 1169 | } 1170 | } else if (bytesPerSequence === 3) { 1171 | b1 = buf[i + 1]; 1172 | b2 = buf[i + 2]; 1173 | if ((b1 & 0xC0) === 0x80 && (b2 & 0xC0) === 0x80) { 1174 | c = (b0 & 0xF) << 0xC | (b1 & 0x3F) << 0x6 | (b2 & 0x3F); 1175 | if (c <= 0x7FF || (c >= 0xD800 && c <= 0xDFFF)) { 1176 | c = null; 1177 | } 1178 | } 1179 | } else if (bytesPerSequence === 4) { 1180 | b1 = buf[i + 1]; 1181 | b2 = buf[i + 2]; 1182 | b3 = buf[i + 3]; 1183 | if ((b1 & 0xC0) === 0x80 && (b2 & 0xC0) === 0x80 && (b3 & 0xC0) === 0x80) { 1184 | c = (b0 & 0xF) << 0x12 | (b1 & 0x3F) << 0xC | (b2 & 0x3F) << 0x6 | (b3 & 0x3F); 1185 | if (c <= 0xFFFF || c >= 0x110000) { 1186 | c = null; 1187 | } 1188 | } 1189 | } 1190 | 1191 | if (c === null) { 1192 | c = 0xFFFD; 1193 | bytesPerSequence = 1; 1194 | 1195 | } else if (c > 0xFFFF) { 1196 | c -= 0x10000; 1197 | str += String.fromCharCode(c >>> 10 & 0x3FF | 0xD800); 1198 | c = 0xDC00 | c & 0x3FF; 1199 | } 1200 | 1201 | str += String.fromCharCode(c); 1202 | i += bytesPerSequence; 1203 | } 1204 | 1205 | return str; 1206 | } 1207 | 1208 | function writeUtf8(buf, str, pos) { 1209 | for (var i = 0, c, lead; i < str.length; i++) { 1210 | c = str.charCodeAt(i); // code point 1211 | 1212 | if (c > 0xD7FF && c < 0xE000) { 1213 | if (lead) { 1214 | if (c < 0xDC00) { 1215 | buf[pos++] = 0xEF; 1216 | buf[pos++] = 0xBF; 1217 | buf[pos++] = 0xBD; 1218 | lead = c; 1219 | continue; 1220 | } else { 1221 | c = lead - 0xD800 << 10 | c - 0xDC00 | 0x10000; 1222 | lead = null; 1223 | } 1224 | } else { 1225 | if (c > 0xDBFF || (i + 1 === str.length)) { 1226 | buf[pos++] = 0xEF; 1227 | buf[pos++] = 0xBF; 1228 | buf[pos++] = 0xBD; 1229 | } else { 1230 | lead = c; 1231 | } 1232 | continue; 1233 | } 1234 | } else if (lead) { 1235 | buf[pos++] = 0xEF; 1236 | buf[pos++] = 0xBF; 1237 | buf[pos++] = 0xBD; 1238 | lead = null; 1239 | } 1240 | 1241 | if (c < 0x80) { 1242 | buf[pos++] = c; 1243 | } else { 1244 | if (c < 0x800) { 1245 | buf[pos++] = c >> 0x6 | 0xC0; 1246 | } else { 1247 | if (c < 0x10000) { 1248 | buf[pos++] = c >> 0xC | 0xE0; 1249 | } else { 1250 | buf[pos++] = c >> 0x12 | 0xF0; 1251 | buf[pos++] = c >> 0xC & 0x3F | 0x80; 1252 | } 1253 | buf[pos++] = c >> 0x6 & 0x3F | 0x80; 1254 | } 1255 | buf[pos++] = c & 0x3F | 0x80; 1256 | } 1257 | } 1258 | return pos; 1259 | } 1260 | 1261 | var index$5 = Point$1; 1262 | 1263 | function Point$1(x, y) { 1264 | this.x = x; 1265 | this.y = y; 1266 | } 1267 | 1268 | Point$1.prototype = { 1269 | clone: function() { return new Point$1(this.x, this.y); }, 1270 | 1271 | add: function(p) { return this.clone()._add(p); }, 1272 | sub: function(p) { return this.clone()._sub(p); }, 1273 | mult: function(k) { return this.clone()._mult(k); }, 1274 | div: function(k) { return this.clone()._div(k); }, 1275 | rotate: function(a) { return this.clone()._rotate(a); }, 1276 | matMult: function(m) { return this.clone()._matMult(m); }, 1277 | unit: function() { return this.clone()._unit(); }, 1278 | perp: function() { return this.clone()._perp(); }, 1279 | round: function() { return this.clone()._round(); }, 1280 | 1281 | mag: function() { 1282 | return Math.sqrt(this.x * this.x + this.y * this.y); 1283 | }, 1284 | 1285 | equals: function(p) { 1286 | return this.x === p.x && 1287 | this.y === p.y; 1288 | }, 1289 | 1290 | dist: function(p) { 1291 | return Math.sqrt(this.distSqr(p)); 1292 | }, 1293 | 1294 | distSqr: function(p) { 1295 | var dx = p.x - this.x, 1296 | dy = p.y - this.y; 1297 | return dx * dx + dy * dy; 1298 | }, 1299 | 1300 | angle: function() { 1301 | return Math.atan2(this.y, this.x); 1302 | }, 1303 | 1304 | angleTo: function(b) { 1305 | return Math.atan2(this.y - b.y, this.x - b.x); 1306 | }, 1307 | 1308 | angleWith: function(b) { 1309 | return this.angleWithSep(b.x, b.y); 1310 | }, 1311 | 1312 | // Find the angle of the two vectors, solving the formula for the cross product a x b = |a||b|sin(θ) for θ. 1313 | angleWithSep: function(x, y) { 1314 | return Math.atan2( 1315 | this.x * y - this.y * x, 1316 | this.x * x + this.y * y); 1317 | }, 1318 | 1319 | _matMult: function(m) { 1320 | var x = m[0] * this.x + m[1] * this.y, 1321 | y = m[2] * this.x + m[3] * this.y; 1322 | this.x = x; 1323 | this.y = y; 1324 | return this; 1325 | }, 1326 | 1327 | _add: function(p) { 1328 | this.x += p.x; 1329 | this.y += p.y; 1330 | return this; 1331 | }, 1332 | 1333 | _sub: function(p) { 1334 | this.x -= p.x; 1335 | this.y -= p.y; 1336 | return this; 1337 | }, 1338 | 1339 | _mult: function(k) { 1340 | this.x *= k; 1341 | this.y *= k; 1342 | return this; 1343 | }, 1344 | 1345 | _div: function(k) { 1346 | this.x /= k; 1347 | this.y /= k; 1348 | return this; 1349 | }, 1350 | 1351 | _unit: function() { 1352 | this._div(this.mag()); 1353 | return this; 1354 | }, 1355 | 1356 | _perp: function() { 1357 | var y = this.y; 1358 | this.y = this.x; 1359 | this.x = -y; 1360 | return this; 1361 | }, 1362 | 1363 | _rotate: function(angle) { 1364 | var cos = Math.cos(angle), 1365 | sin = Math.sin(angle), 1366 | x = cos * this.x - sin * this.y, 1367 | y = sin * this.x + cos * this.y; 1368 | this.x = x; 1369 | this.y = y; 1370 | return this; 1371 | }, 1372 | 1373 | _round: function() { 1374 | this.x = Math.round(this.x); 1375 | this.y = Math.round(this.y); 1376 | return this; 1377 | } 1378 | }; 1379 | 1380 | // constructs Point from an array if necessary 1381 | Point$1.convert = function (a) { 1382 | if (a instanceof Point$1) { 1383 | return a; 1384 | } 1385 | if (Array.isArray(a)) { 1386 | return new Point$1(a[0], a[1]); 1387 | } 1388 | return a; 1389 | }; 1390 | 1391 | var Point = index$5; 1392 | 1393 | var vectortilefeature = VectorTileFeature$2; 1394 | 1395 | function VectorTileFeature$2(pbf, end, extent, keys, values) { 1396 | // Public 1397 | this.properties = {}; 1398 | this.extent = extent; 1399 | this.type = 0; 1400 | 1401 | // Private 1402 | this._pbf = pbf; 1403 | this._geometry = -1; 1404 | this._keys = keys; 1405 | this._values = values; 1406 | 1407 | pbf.readFields(readFeature, this, end); 1408 | } 1409 | 1410 | function readFeature(tag, feature, pbf) { 1411 | if (tag == 1) { feature.id = pbf.readVarint(); } 1412 | else if (tag == 2) { readTag(pbf, feature); } 1413 | else if (tag == 3) { feature.type = pbf.readVarint(); } 1414 | else if (tag == 4) { feature._geometry = pbf.pos; } 1415 | } 1416 | 1417 | function readTag(pbf, feature) { 1418 | var end = pbf.readVarint() + pbf.pos; 1419 | 1420 | while (pbf.pos < end) { 1421 | var key = feature._keys[pbf.readVarint()], 1422 | value = feature._values[pbf.readVarint()]; 1423 | feature.properties[key] = value; 1424 | } 1425 | } 1426 | 1427 | VectorTileFeature$2.types = ['Unknown', 'Point', 'LineString', 'Polygon']; 1428 | 1429 | VectorTileFeature$2.prototype.loadGeometry = function() { 1430 | var pbf = this._pbf; 1431 | pbf.pos = this._geometry; 1432 | 1433 | var end = pbf.readVarint() + pbf.pos, 1434 | cmd = 1, 1435 | length = 0, 1436 | x = 0, 1437 | y = 0, 1438 | lines = [], 1439 | line; 1440 | 1441 | while (pbf.pos < end) { 1442 | if (!length) { 1443 | var cmdLen = pbf.readVarint(); 1444 | cmd = cmdLen & 0x7; 1445 | length = cmdLen >> 3; 1446 | } 1447 | 1448 | length--; 1449 | 1450 | if (cmd === 1 || cmd === 2) { 1451 | x += pbf.readSVarint(); 1452 | y += pbf.readSVarint(); 1453 | 1454 | if (cmd === 1) { // moveTo 1455 | if (line) { lines.push(line); } 1456 | line = []; 1457 | } 1458 | 1459 | line.push(new Point(x, y)); 1460 | 1461 | } else if (cmd === 7) { 1462 | 1463 | // Workaround for https://github.com/mapbox/mapnik-vector-tile/issues/90 1464 | if (line) { 1465 | line.push(line[0].clone()); // closePolygon 1466 | } 1467 | 1468 | } else { 1469 | throw new Error('unknown command ' + cmd); 1470 | } 1471 | } 1472 | 1473 | if (line) { lines.push(line); } 1474 | 1475 | return lines; 1476 | }; 1477 | 1478 | VectorTileFeature$2.prototype.bbox = function() { 1479 | var pbf = this._pbf; 1480 | pbf.pos = this._geometry; 1481 | 1482 | var end = pbf.readVarint() + pbf.pos, 1483 | cmd = 1, 1484 | length = 0, 1485 | x = 0, 1486 | y = 0, 1487 | x1 = Infinity, 1488 | x2 = -Infinity, 1489 | y1 = Infinity, 1490 | y2 = -Infinity; 1491 | 1492 | while (pbf.pos < end) { 1493 | if (!length) { 1494 | var cmdLen = pbf.readVarint(); 1495 | cmd = cmdLen & 0x7; 1496 | length = cmdLen >> 3; 1497 | } 1498 | 1499 | length--; 1500 | 1501 | if (cmd === 1 || cmd === 2) { 1502 | x += pbf.readSVarint(); 1503 | y += pbf.readSVarint(); 1504 | if (x < x1) { x1 = x; } 1505 | if (x > x2) { x2 = x; } 1506 | if (y < y1) { y1 = y; } 1507 | if (y > y2) { y2 = y; } 1508 | 1509 | } else if (cmd !== 7) { 1510 | throw new Error('unknown command ' + cmd); 1511 | } 1512 | } 1513 | 1514 | return [x1, y1, x2, y2]; 1515 | }; 1516 | 1517 | VectorTileFeature$2.prototype.toGeoJSON = function(x, y, z) { 1518 | var size = this.extent * Math.pow(2, z), 1519 | x0 = this.extent * x, 1520 | y0 = this.extent * y, 1521 | coords = this.loadGeometry(), 1522 | type = VectorTileFeature$2.types[this.type], 1523 | i, j; 1524 | 1525 | function project(line) { 1526 | for (var j = 0; j < line.length; j++) { 1527 | var p = line[j], y2 = 180 - (p.y + y0) * 360 / size; 1528 | line[j] = [ 1529 | (p.x + x0) * 360 / size - 180, 1530 | 360 / Math.PI * Math.atan(Math.exp(y2 * Math.PI / 180)) - 90 1531 | ]; 1532 | } 1533 | } 1534 | 1535 | switch (this.type) { 1536 | case 1: 1537 | var points = []; 1538 | for (i = 0; i < coords.length; i++) { 1539 | points[i] = coords[i][0]; 1540 | } 1541 | coords = points; 1542 | project(coords); 1543 | break; 1544 | 1545 | case 2: 1546 | for (i = 0; i < coords.length; i++) { 1547 | project(coords[i]); 1548 | } 1549 | break; 1550 | 1551 | case 3: 1552 | coords = classifyRings(coords); 1553 | for (i = 0; i < coords.length; i++) { 1554 | for (j = 0; j < coords[i].length; j++) { 1555 | project(coords[i][j]); 1556 | } 1557 | } 1558 | break; 1559 | } 1560 | 1561 | if (coords.length === 1) { 1562 | coords = coords[0]; 1563 | } else { 1564 | type = 'Multi' + type; 1565 | } 1566 | 1567 | var result = { 1568 | type: "Feature", 1569 | geometry: { 1570 | type: type, 1571 | coordinates: coords 1572 | }, 1573 | properties: this.properties 1574 | }; 1575 | 1576 | if ('id' in this) { 1577 | result.id = this.id; 1578 | } 1579 | 1580 | return result; 1581 | }; 1582 | 1583 | // classifies an array of rings into polygons with outer rings and holes 1584 | 1585 | function classifyRings(rings) { 1586 | var len = rings.length; 1587 | 1588 | if (len <= 1) { return [rings]; } 1589 | 1590 | var polygons = [], 1591 | polygon, 1592 | ccw; 1593 | 1594 | for (var i = 0; i < len; i++) { 1595 | var area = signedArea(rings[i]); 1596 | if (area === 0) { continue; } 1597 | 1598 | if (ccw === undefined) { ccw = area < 0; } 1599 | 1600 | if (ccw === area < 0) { 1601 | if (polygon) { polygons.push(polygon); } 1602 | polygon = [rings[i]]; 1603 | 1604 | } else { 1605 | polygon.push(rings[i]); 1606 | } 1607 | } 1608 | if (polygon) { polygons.push(polygon); } 1609 | 1610 | return polygons; 1611 | } 1612 | 1613 | function signedArea(ring) { 1614 | var sum = 0; 1615 | for (var i = 0, len = ring.length, j = len - 1, p1, p2; i < len; j = i++) { 1616 | p1 = ring[i]; 1617 | p2 = ring[j]; 1618 | sum += (p2.x - p1.x) * (p1.y + p2.y); 1619 | } 1620 | return sum; 1621 | } 1622 | 1623 | var VectorTileFeature$1 = vectortilefeature; 1624 | 1625 | var vectortilelayer = VectorTileLayer$2; 1626 | 1627 | function VectorTileLayer$2(pbf, end) { 1628 | // Public 1629 | this.version = 1; 1630 | this.name = null; 1631 | this.extent = 4096; 1632 | this.length = 0; 1633 | 1634 | // Private 1635 | this._pbf = pbf; 1636 | this._keys = []; 1637 | this._values = []; 1638 | this._features = []; 1639 | 1640 | pbf.readFields(readLayer, this, end); 1641 | 1642 | this.length = this._features.length; 1643 | } 1644 | 1645 | function readLayer(tag, layer, pbf) { 1646 | if (tag === 15) { layer.version = pbf.readVarint(); } 1647 | else if (tag === 1) { layer.name = pbf.readString(); } 1648 | else if (tag === 5) { layer.extent = pbf.readVarint(); } 1649 | else if (tag === 2) { layer._features.push(pbf.pos); } 1650 | else if (tag === 3) { layer._keys.push(pbf.readString()); } 1651 | else if (tag === 4) { layer._values.push(readValueMessage(pbf)); } 1652 | } 1653 | 1654 | function readValueMessage(pbf) { 1655 | var value = null, 1656 | end = pbf.readVarint() + pbf.pos; 1657 | 1658 | while (pbf.pos < end) { 1659 | var tag = pbf.readVarint() >> 3; 1660 | 1661 | value = tag === 1 ? pbf.readString() : 1662 | tag === 2 ? pbf.readFloat() : 1663 | tag === 3 ? pbf.readDouble() : 1664 | tag === 4 ? pbf.readVarint64() : 1665 | tag === 5 ? pbf.readVarint() : 1666 | tag === 6 ? pbf.readSVarint() : 1667 | tag === 7 ? pbf.readBoolean() : null; 1668 | } 1669 | 1670 | return value; 1671 | } 1672 | 1673 | // return feature `i` from this layer as a `VectorTileFeature` 1674 | VectorTileLayer$2.prototype.feature = function(i) { 1675 | if (i < 0 || i >= this._features.length) { throw new Error('feature index out of bounds'); } 1676 | 1677 | this._pbf.pos = this._features[i]; 1678 | 1679 | var end = this._pbf.readVarint() + this._pbf.pos; 1680 | return new VectorTileFeature$1(this._pbf, end, this.extent, this._keys, this._values); 1681 | }; 1682 | 1683 | var VectorTileLayer$1 = vectortilelayer; 1684 | 1685 | var vectortile = VectorTile$1; 1686 | 1687 | function VectorTile$1(pbf, end) { 1688 | this.layers = pbf.readFields(readTile, {}, end); 1689 | } 1690 | 1691 | function readTile(tag, layers, pbf) { 1692 | if (tag === 3) { 1693 | var layer = new VectorTileLayer$1(pbf, pbf.readVarint() + pbf.pos); 1694 | if (layer.length) { layers[layer.name] = layer; } 1695 | } 1696 | } 1697 | 1698 | var VectorTile = vectortile; 1699 | 1700 | /* 1701 | * 🍂class VectorGrid.Protobuf 1702 | * 🍂extends VectorGrid 1703 | * 1704 | * A `VectorGrid` for vector tiles fetched from the internet. 1705 | * Tiles are supposed to be protobufs (AKA "protobuffer" or "Protocol Buffers"), 1706 | * containing data which complies with the 1707 | * [MapBox Vector Tile Specification](https://github.com/mapbox/vector-tile-spec/tree/master/2.1). 1708 | * 1709 | * This is the format used by: 1710 | * - Mapbox Vector Tiles 1711 | * - Mapzen Vector Tiles 1712 | * - ESRI Vector Tiles 1713 | * - [OpenMapTiles hosted Vector Tiles](https://openmaptiles.com/hosting/) 1714 | * 1715 | * 🍂example 1716 | * 1717 | * You must initialize a `VectorGrid.Protobuf` with a URL template, just like in 1718 | * `L.TileLayer`s. The difference is that the template must point to vector tiles 1719 | * (usually `.pbf` or `.mvt`) instead of raster (`.png` or `.jpg`) tiles, and that 1720 | * you should define the styling for all the features. 1721 | * 1722 | *

1723 | * 1724 | * For OpenMapTiles, with a key from [https://openmaptiles.org/docs/host/use-cdn/](https://openmaptiles.org/docs/host/use-cdn/), 1725 | * initialization looks like this: 1726 | * 1727 | * ``` 1728 | * L.vectorGrid.protobuf("https://free-{s}.tilehosting.com/data/v3/{z}/{x}/{y}.pbf.pict?key={key}", { 1729 | * vectorTileLayerStyles: { ... }, 1730 | * subdomains: "0123", 1731 | * key: 'abcdefghi01234567890', 1732 | * maxNativeZoom: 14 1733 | * }).addTo(map); 1734 | * ``` 1735 | * 1736 | * And for Mapbox vector tiles, it looks like this: 1737 | * 1738 | * ``` 1739 | * L.vectorGrid.protobuf("https://{s}.tiles.mapbox.com/v4/mapbox.mapbox-streets-v6/{z}/{x}/{y}.vector.pbf?access_token={token}", { 1740 | * vectorTileLayerStyles: { ... }, 1741 | * subdomains: "abcd", 1742 | * token: "pk.abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRTS.TUVWXTZ0123456789abcde" 1743 | * }).addTo(map); 1744 | * ``` 1745 | */ 1746 | L.VectorGrid.Protobuf = L.VectorGrid.extend({ 1747 | 1748 | options: { 1749 | // 🍂section 1750 | // As with `L.TileLayer`, the URL template might contain a reference to 1751 | // any option (see the example above and note the `{key}` or `token` in the URL 1752 | // template, and the corresponding option). 1753 | // 1754 | // 🍂option subdomains: String = 'abc' 1755 | // Akin to the `subdomains` option for `L.TileLayer`. 1756 | subdomains: 'abc', // Like L.TileLayer 1757 | // 1758 | // 🍂option fetchOptions: Object = {} 1759 | // options passed to `fetch`, e.g. {credentials: 'same-origin'} to send cookie for the current domain 1760 | fetchOptions: {} 1761 | }, 1762 | 1763 | initialize: function(url, options) { 1764 | // Inherits options from geojson-vt! 1765 | // this._slicer = geojsonvt(geojson, options); 1766 | this._url = url; 1767 | L.VectorGrid.prototype.initialize.call(this, options); 1768 | }, 1769 | 1770 | // 🍂method setUrl(url: String, noRedraw?: Boolean): this 1771 | // Updates the layer's URL template and redraws it (unless `noRedraw` is set to `true`). 1772 | setUrl: function(url, noRedraw) { 1773 | this._url = url; 1774 | 1775 | if (!noRedraw) { 1776 | this.redraw(); 1777 | } 1778 | 1779 | return this; 1780 | }, 1781 | 1782 | _getSubdomain: L.TileLayer.prototype._getSubdomain, 1783 | 1784 | _getVectorTilePromise: function(coords) { 1785 | var data = { 1786 | s: this._getSubdomain(coords), 1787 | x: coords.x, 1788 | y: coords.y, 1789 | z: coords.z 1790 | // z: this._getZoomForUrl() /// TODO: Maybe replicate TileLayer's maxNativeZoom 1791 | }; 1792 | if (this._map && !this._map.options.crs.infinite) { 1793 | var invertedY = this._globalTileRange.max.y - coords.y; 1794 | if (this.options.tms) { // Should this option be available in Leaflet.VectorGrid? 1795 | data['y'] = invertedY; 1796 | } 1797 | data['-y'] = invertedY; 1798 | } 1799 | 1800 | var tileUrl = L.Util.template(this._url, L.extend(data, this.options)); 1801 | 1802 | return fetch(tileUrl, this.options.fetchOptions).then(function(response){ 1803 | 1804 | if (!response.ok) { 1805 | return {layers:[]}; 1806 | } 1807 | 1808 | return response.blob().then( function (blob) { 1809 | // console.log(blob); 1810 | 1811 | var reader = new FileReader(); 1812 | return new Promise(function(resolve){ 1813 | reader.addEventListener("loadend", function() { 1814 | // reader.result contains the contents of blob as a typed array 1815 | 1816 | // blob.type === 'application/x-protobuf' 1817 | var pbf = new index( reader.result ); 1818 | // console.log(pbf); 1819 | return resolve(new VectorTile( pbf )); 1820 | 1821 | }); 1822 | reader.readAsArrayBuffer(blob); 1823 | }); 1824 | }); 1825 | }).then(function(json){ 1826 | 1827 | // console.log('Vector tile:', json.layers); 1828 | // console.log('Vector tile water:', json.layers.water); // Instance of VectorTileLayer 1829 | 1830 | // Normalize feature getters into actual instanced features 1831 | for (var layerName in json.layers) { 1832 | var feats = []; 1833 | 1834 | for (var i=0; i maxSqDist) {\n index = i;\n maxSqDist = sqDist;\n }\n }\n\n if (maxSqDist > sqTolerance) {\n points[index][2] = maxSqDist; // save the point importance in squared pixels as a z coordinate\n stack.push(first);\n stack.push(index);\n first = index;\n\n } else {\n last = stack.pop();\n first = stack.pop();\n }\n }\n}\n\n// square distance from a point to a segment\nfunction getSqSegDist(p, a, b) {\n\n var x = a[0], y = a[1],\n bx = b[0], by = b[1],\n px = p[0], py = p[1],\n dx = bx - x,\n dy = by - y;\n\n if (dx !== 0 || dy !== 0) {\n\n var t = ((px - x) * dx + (py - y) * dy) / (dx * dx + dy * dy);\n\n if (t > 1) {\n x = bx;\n y = by;\n\n } else if (t > 0) {\n x += dx * t;\n y += dy * t;\n }\n }\n\n dx = px - x;\n dy = py - y;\n\n return dx * dx + dy * dy;\n}\n\nvar convert_1 = convert$1;\n\nvar simplify = simplify_1;\n\n// converts GeoJSON feature into an intermediate projected JSON vector format with simplification data\n\nfunction convert$1(data, tolerance) {\n var features = [];\n\n if (data.type === 'FeatureCollection') {\n for (var i = 0; i < data.features.length; i++) {\n convertFeature(features, data.features[i], tolerance);\n }\n } else if (data.type === 'Feature') {\n convertFeature(features, data, tolerance);\n\n } else {\n // single geometry or a geometry collection\n convertFeature(features, {geometry: data}, tolerance);\n }\n return features;\n}\n\nfunction convertFeature(features, feature, tolerance) {\n if (feature.geometry === null) {\n // ignore features with null geometry\n return;\n }\n\n var geom = feature.geometry,\n type = geom.type,\n coords = geom.coordinates,\n tags = feature.properties,\n i, j, rings, projectedRing;\n\n if (type === 'Point') {\n features.push(create(tags, 1, [projectPoint(coords)]));\n\n } else if (type === 'MultiPoint') {\n features.push(create(tags, 1, project(coords)));\n\n } else if (type === 'LineString') {\n features.push(create(tags, 2, [project(coords, tolerance)]));\n\n } else if (type === 'MultiLineString' || type === 'Polygon') {\n rings = [];\n for (i = 0; i < coords.length; i++) {\n projectedRing = project(coords[i], tolerance);\n if (type === 'Polygon') { projectedRing.outer = (i === 0); }\n rings.push(projectedRing);\n }\n features.push(create(tags, type === 'Polygon' ? 3 : 2, rings));\n\n } else if (type === 'MultiPolygon') {\n rings = [];\n for (i = 0; i < coords.length; i++) {\n for (j = 0; j < coords[i].length; j++) {\n projectedRing = project(coords[i][j], tolerance);\n projectedRing.outer = (j === 0);\n rings.push(projectedRing);\n }\n }\n features.push(create(tags, 3, rings));\n\n } else if (type === 'GeometryCollection') {\n for (i = 0; i < geom.geometries.length; i++) {\n convertFeature(features, {\n geometry: geom.geometries[i],\n properties: tags\n }, tolerance);\n }\n\n } else {\n throw new Error('Input data is not a valid GeoJSON object.');\n }\n}\n\nfunction create(tags, type, geometry) {\n var feature = {\n geometry: geometry,\n type: type,\n tags: tags || null,\n min: [2, 1], // initial bbox values;\n max: [-1, 0] // note that coords are usually in [0..1] range\n };\n calcBBox(feature);\n return feature;\n}\n\nfunction project(lonlats, tolerance) {\n var projected = [];\n for (var i = 0; i < lonlats.length; i++) {\n projected.push(projectPoint(lonlats[i]));\n }\n if (tolerance) {\n simplify(projected, tolerance);\n calcSize(projected);\n }\n return projected;\n}\n\nfunction projectPoint(p) {\n var sin = Math.sin(p[1] * Math.PI / 180),\n x = (p[0] / 360 + 0.5),\n y = (0.5 - 0.25 * Math.log((1 + sin) / (1 - sin)) / Math.PI);\n\n y = y < 0 ? 0 :\n y > 1 ? 1 : y;\n\n return [x, y, 0];\n}\n\n// calculate area and length of the poly\nfunction calcSize(points) {\n var area = 0,\n dist = 0;\n\n for (var i = 0, a, b; i < points.length - 1; i++) {\n a = b || points[i];\n b = points[i + 1];\n\n area += a[0] * b[1] - b[0] * a[1];\n\n // use Manhattan distance instead of Euclidian one to avoid expensive square root computation\n dist += Math.abs(b[0] - a[0]) + Math.abs(b[1] - a[1]);\n }\n points.area = Math.abs(area / 2);\n points.dist = dist;\n}\n\n// calculate the feature bounding box for faster clipping later\nfunction calcBBox(feature) {\n var geometry = feature.geometry,\n min = feature.min,\n max = feature.max;\n\n if (feature.type === 1) { calcRingBBox(min, max, geometry); }\n else { for (var i = 0; i < geometry.length; i++) { calcRingBBox(min, max, geometry[i]); } }\n\n return feature;\n}\n\nfunction calcRingBBox(min, max, points) {\n for (var i = 0, p; i < points.length; i++) {\n p = points[i];\n min[0] = Math.min(p[0], min[0]);\n max[0] = Math.max(p[0], max[0]);\n min[1] = Math.min(p[1], min[1]);\n max[1] = Math.max(p[1], max[1]);\n }\n}\n\nvar tile = transformTile;\nvar point = transformPoint;\n\n// Transforms the coordinates of each feature in the given tile from\n// mercator-projected space into (extent x extent) tile space.\nfunction transformTile(tile, extent) {\n if (tile.transformed) { return tile; }\n\n var z2 = tile.z2,\n tx = tile.x,\n ty = tile.y,\n i, j, k;\n\n for (i = 0; i < tile.features.length; i++) {\n var feature = tile.features[i],\n geom = feature.geometry,\n type = feature.type;\n\n if (type === 1) {\n for (j = 0; j < geom.length; j++) { geom[j] = transformPoint(geom[j], extent, z2, tx, ty); }\n\n } else {\n for (j = 0; j < geom.length; j++) {\n var ring = geom[j];\n for (k = 0; k < ring.length; k++) { ring[k] = transformPoint(ring[k], extent, z2, tx, ty); }\n }\n }\n }\n\n tile.transformed = true;\n\n return tile;\n}\n\nfunction transformPoint(p, extent, z2, tx, ty) {\n var x = Math.round(extent * (p[0] * z2 - tx)),\n y = Math.round(extent * (p[1] * z2 - ty));\n return [x, y];\n}\n\nvar transform$1 = {\n tile: tile,\n point: point\n};\n\nvar clip_1 = clip$1;\n\n/* clip features between two axis-parallel lines:\n * | |\n * ___|___ | /\n * / | \____|____/\n * | |\n */\n\nfunction clip$1(features, scale, k1, k2, axis, intersect, minAll, maxAll) {\n\n k1 /= scale;\n k2 /= scale;\n\n if (minAll >= k1 && maxAll <= k2) { return features; } // trivial accept\n else if (minAll > k2 || maxAll < k1) { return null; } // trivial reject\n\n var clipped = [];\n\n for (var i = 0; i < features.length; i++) {\n\n var feature = features[i],\n geometry = feature.geometry,\n type = feature.type,\n min, max;\n\n min = feature.min[axis];\n max = feature.max[axis];\n\n if (min >= k1 && max <= k2) { // trivial accept\n clipped.push(feature);\n continue;\n } else if (min > k2 || max < k1) { continue; } // trivial reject\n\n var slices = type === 1 ?\n clipPoints(geometry, k1, k2, axis) :\n clipGeometry(geometry, k1, k2, axis, intersect, type === 3);\n\n if (slices.length) {\n // if a feature got clipped, it will likely get clipped on the next zoom level as well,\n // so there's no need to recalculate bboxes\n clipped.push({\n geometry: slices,\n type: type,\n tags: features[i].tags || null,\n min: feature.min,\n max: feature.max\n });\n }\n }\n\n return clipped.length ? clipped : null;\n}\n\nfunction clipPoints(geometry, k1, k2, axis) {\n var slice = [];\n\n for (var i = 0; i < geometry.length; i++) {\n var a = geometry[i],\n ak = a[axis];\n\n if (ak >= k1 && ak <= k2) { slice.push(a); }\n }\n return slice;\n}\n\nfunction clipGeometry(geometry, k1, k2, axis, intersect, closed) {\n\n var slices = [];\n\n for (var i = 0; i < geometry.length; i++) {\n\n var ak = 0,\n bk = 0,\n b = null,\n points = geometry[i],\n area = points.area,\n dist = points.dist,\n outer = points.outer,\n len = points.length,\n a, j, last;\n\n var slice = [];\n\n for (j = 0; j < len - 1; j++) {\n a = b || points[j];\n b = points[j + 1];\n ak = bk || a[axis];\n bk = b[axis];\n\n if (ak < k1) {\n\n if ((bk > k2)) { // ---|-----|-->\n slice.push(intersect(a, b, k1), intersect(a, b, k2));\n if (!closed) { slice = newSlice(slices, slice, area, dist, outer); }\n\n } else if (bk >= k1) { slice.push(intersect(a, b, k1)); } // ---|--> |\n\n } else if (ak > k2) {\n\n if ((bk < k1)) { // <--|-----|---\n slice.push(intersect(a, b, k2), intersect(a, b, k1));\n if (!closed) { slice = newSlice(slices, slice, area, dist, outer); }\n\n } else if (bk <= k2) { slice.push(intersect(a, b, k2)); } // | <--|---\n\n } else {\n\n slice.push(a);\n\n if (bk < k1) { // <--|--- |\n slice.push(intersect(a, b, k1));\n if (!closed) { slice = newSlice(slices, slice, area, dist, outer); }\n\n } else if (bk > k2) { // | ---|-->\n slice.push(intersect(a, b, k2));\n if (!closed) { slice = newSlice(slices, slice, area, dist, outer); }\n }\n // | --> |\n }\n }\n\n // add the last point\n a = points[len - 1];\n ak = a[axis];\n if (ak >= k1 && ak <= k2) { slice.push(a); }\n\n // close the polygon if its endpoints are not the same after clipping\n\n last = slice[slice.length - 1];\n if (closed && last && (slice[0][0] !== last[0] || slice[0][1] !== last[1])) { slice.push(slice[0]); }\n\n // add the final slice\n newSlice(slices, slice, area, dist, outer);\n }\n\n return slices;\n}\n\nfunction newSlice(slices, slice, area, dist, outer) {\n if (slice.length) {\n // we don't recalculate the area/length of the unclipped geometry because the case where it goes\n // below the visibility threshold as a result of clipping is rare, so we avoid doing unnecessary work\n slice.area = area;\n slice.dist = dist;\n if (outer !== undefined) { slice.outer = outer; }\n\n slices.push(slice);\n }\n return [];\n}\n\nvar clip$2 = clip_1;\n\nvar wrap_1 = wrap$1;\n\nfunction wrap$1(features, buffer, intersectX) {\n var merged = features,\n left = clip$2(features, 1, -1 - buffer, buffer, 0, intersectX, -1, 2), // left world copy\n right = clip$2(features, 1, 1 - buffer, 2 + buffer, 0, intersectX, -1, 2); // right world copy\n\n if (left || right) {\n merged = clip$2(features, 1, -buffer, 1 + buffer, 0, intersectX, -1, 2); // center world copy\n\n if (left) { merged = shiftFeatureCoords(left, 1).concat(merged); } // merge left into center\n if (right) { merged = merged.concat(shiftFeatureCoords(right, -1)); } // merge right into center\n }\n\n return merged;\n}\n\nfunction shiftFeatureCoords(features, offset) {\n var newFeatures = [];\n\n for (var i = 0; i < features.length; i++) {\n var feature = features[i],\n type = feature.type;\n\n var newGeometry;\n\n if (type === 1) {\n newGeometry = shiftCoords(feature.geometry, offset);\n } else {\n newGeometry = [];\n for (var j = 0; j < feature.geometry.length; j++) {\n newGeometry.push(shiftCoords(feature.geometry[j], offset));\n }\n }\n\n newFeatures.push({\n geometry: newGeometry,\n type: type,\n tags: feature.tags,\n min: [feature.min[0] + offset, feature.min[1]],\n max: [feature.max[0] + offset, feature.max[1]]\n });\n }\n\n return newFeatures;\n}\n\nfunction shiftCoords(points, offset) {\n var newPoints = [];\n newPoints.area = points.area;\n newPoints.dist = points.dist;\n\n for (var i = 0; i < points.length; i++) {\n newPoints.push([points[i][0] + offset, points[i][1], points[i][2]]);\n }\n return newPoints;\n}\n\nvar tile$1 = createTile$1;\n\nfunction createTile$1(features, z2, tx, ty, tolerance, noSimplify) {\n var tile = {\n features: [],\n numPoints: 0,\n numSimplified: 0,\n numFeatures: 0,\n source: null,\n x: tx,\n y: ty,\n z2: z2,\n transformed: false,\n min: [2, 1],\n max: [-1, 0]\n };\n for (var i = 0; i < features.length; i++) {\n tile.numFeatures++;\n addFeature(tile, features[i], tolerance, noSimplify);\n\n var min = features[i].min,\n max = features[i].max;\n\n if (min[0] < tile.min[0]) { tile.min[0] = min[0]; }\n if (min[1] < tile.min[1]) { tile.min[1] = min[1]; }\n if (max[0] > tile.max[0]) { tile.max[0] = max[0]; }\n if (max[1] > tile.max[1]) { tile.max[1] = max[1]; }\n }\n return tile;\n}\n\nfunction addFeature(tile, feature, tolerance, noSimplify) {\n\n var geom = feature.geometry,\n type = feature.type,\n simplified = [],\n sqTolerance = tolerance * tolerance,\n i, j, ring, p;\n\n if (type === 1) {\n for (i = 0; i < geom.length; i++) {\n simplified.push(geom[i]);\n tile.numPoints++;\n tile.numSimplified++;\n }\n\n } else {\n\n // simplify and transform projected coordinates for tile geometry\n for (i = 0; i < geom.length; i++) {\n ring = geom[i];\n\n // filter out tiny polylines & polygons\n if (!noSimplify && ((type === 2 && ring.dist < tolerance) ||\n (type === 3 && ring.area < sqTolerance))) {\n tile.numPoints += ring.length;\n continue;\n }\n\n var simplifiedRing = [];\n\n for (j = 0; j < ring.length; j++) {\n p = ring[j];\n // keep points with importance > tolerance\n if (noSimplify || p[2] > sqTolerance) {\n simplifiedRing.push(p);\n tile.numSimplified++;\n }\n tile.numPoints++;\n }\n\n if (type === 3) { rewind(simplifiedRing, ring.outer); }\n\n simplified.push(simplifiedRing);\n }\n }\n\n if (simplified.length) {\n tile.features.push({\n geometry: simplified,\n type: type,\n tags: feature.tags || null\n });\n }\n}\n\nfunction rewind(ring, clockwise) {\n var area = signedArea(ring);\n if (area < 0 === clockwise) { ring.reverse(); }\n}\n\nfunction signedArea(ring) {\n var sum = 0;\n for (var i = 0, len = ring.length, j = len - 1, p1, p2; i < len; j = i++) {\n p1 = ring[i];\n p2 = ring[j];\n sum += (p2[0] - p1[0]) * (p1[1] + p2[1]);\n }\n return sum;\n}\n\nvar index = geojsonvt;\n\nvar convert = convert_1;\nvar transform = transform$1;\nvar clip = clip_1;\nvar wrap = wrap_1;\nvar createTile = tile$1; // final simplified tile generation\n\n\nfunction geojsonvt(data, options) {\n return new GeoJSONVT(data, options);\n}\n\nfunction GeoJSONVT(data, options) {\n options = this.options = extend(Object.create(this.options), options);\n\n var debug = options.debug;\n\n if (debug) { console.time('preprocess data'); }\n\n var z2 = 1 << options.maxZoom, // 2^z\n features = convert(data, options.tolerance / (z2 * options.extent));\n\n this.tiles = {};\n this.tileCoords = [];\n\n if (debug) {\n console.timeEnd('preprocess data');\n console.log('index: maxZoom: %d, maxPoints: %d', options.indexMaxZoom, options.indexMaxPoints);\n console.time('generate tiles');\n this.stats = {};\n this.total = 0;\n }\n\n features = wrap(features, options.buffer / options.extent, intersectX);\n\n // start slicing from the top tile down\n if (features.length) { this.splitTile(features, 0, 0, 0); }\n\n if (debug) {\n if (features.length) { console.log('features: %d, points: %d', this.tiles[0].numFeatures, this.tiles[0].numPoints); }\n console.timeEnd('generate tiles');\n console.log('tiles generated:', this.total, JSON.stringify(this.stats));\n }\n}\n\nGeoJSONVT.prototype.options = {\n maxZoom: 14, // max zoom to preserve detail on\n indexMaxZoom: 5, // max zoom in the tile index\n indexMaxPoints: 100000, // max number of points per tile in the tile index\n solidChildren: false, // whether to tile solid square tiles further\n tolerance: 3, // simplification tolerance (higher means simpler)\n extent: 4096, // tile extent\n buffer: 64, // tile buffer on each side\n debug: 0 // logging level (0, 1 or 2)\n};\n\nGeoJSONVT.prototype.splitTile = function (features, z, x, y, cz, cx, cy) {\n var this$1 = this;\n\n\n var stack = [features, z, x, y],\n options = this.options,\n debug = options.debug,\n solid = null;\n\n // avoid recursion by using a processing queue\n while (stack.length) {\n y = stack.pop();\n x = stack.pop();\n z = stack.pop();\n features = stack.pop();\n\n var z2 = 1 << z,\n id = toID(z, x, y),\n tile = this$1.tiles[id],\n tileTolerance = z === options.maxZoom ? 0 : options.tolerance / (z2 * options.extent);\n\n if (!tile) {\n if (debug > 1) { console.time('creation'); }\n\n tile = this$1.tiles[id] = createTile(features, z2, x, y, tileTolerance, z === options.maxZoom);\n this$1.tileCoords.push({z: z, x: x, y: y});\n\n if (debug) {\n if (debug > 1) {\n console.log('tile z%d-%d-%d (features: %d, points: %d, simplified: %d)',\n z, x, y, tile.numFeatures, tile.numPoints, tile.numSimplified);\n console.timeEnd('creation');\n }\n var key = 'z' + z;\n this$1.stats[key] = (this$1.stats[key] || 0) + 1;\n this$1.total++;\n }\n }\n\n // save reference to original geometry in tile so that we can drill down later if we stop now\n tile.source = features;\n\n // if it's the first-pass tiling\n if (!cz) {\n // stop tiling if we reached max zoom, or if the tile is too simple\n if (z === options.indexMaxZoom || tile.numPoints <= options.indexMaxPoints) { continue; }\n\n // if a drilldown to a specific tile\n } else {\n // stop tiling if we reached base zoom or our target tile zoom\n if (z === options.maxZoom || z === cz) { continue; }\n\n // stop tiling if it's not an ancestor of the target tile\n var m = 1 << (cz - z);\n if (x !== Math.floor(cx / m) || y !== Math.floor(cy / m)) { continue; }\n }\n\n // stop tiling if the tile is solid clipped square\n if (!options.solidChildren && isClippedSquare(tile, options.extent, options.buffer)) {\n if (cz) { solid = z; } // and remember the zoom if we're drilling down\n continue;\n }\n\n // if we slice further down, no need to keep source geometry\n tile.source = null;\n\n if (debug > 1) { console.time('clipping'); }\n\n // values we'll use for clipping\n var k1 = 0.5 * options.buffer / options.extent,\n k2 = 0.5 - k1,\n k3 = 0.5 + k1,\n k4 = 1 + k1,\n tl, bl, tr, br, left, right;\n\n tl = bl = tr = br = null;\n\n left = clip(features, z2, x - k1, x + k3, 0, intersectX, tile.min[0], tile.max[0]);\n right = clip(features, z2, x + k2, x + k4, 0, intersectX, tile.min[0], tile.max[0]);\n\n if (left) {\n tl = clip(left, z2, y - k1, y + k3, 1, intersectY, tile.min[1], tile.max[1]);\n bl = clip(left, z2, y + k2, y + k4, 1, intersectY, tile.min[1], tile.max[1]);\n }\n\n if (right) {\n tr = clip(right, z2, y - k1, y + k3, 1, intersectY, tile.min[1], tile.max[1]);\n br = clip(right, z2, y + k2, y + k4, 1, intersectY, tile.min[1], tile.max[1]);\n }\n\n if (debug > 1) { console.timeEnd('clipping'); }\n\n if (tl) { stack.push(tl, z + 1, x * 2, y * 2); }\n if (bl) { stack.push(bl, z + 1, x * 2, y * 2 + 1); }\n if (tr) { stack.push(tr, z + 1, x * 2 + 1, y * 2); }\n if (br) { stack.push(br, z + 1, x * 2 + 1, y * 2 + 1); }\n }\n\n return solid;\n};\n\nGeoJSONVT.prototype.getTile = function (z, x, y) {\n var this$1 = this;\n\n var options = this.options,\n extent = options.extent,\n debug = options.debug;\n\n var z2 = 1 << z;\n x = ((x % z2) + z2) % z2; // wrap tile x coordinate\n\n var id = toID(z, x, y);\n if (this.tiles[id]) { return transform.tile(this.tiles[id], extent); }\n\n if (debug > 1) { console.log('drilling down to z%d-%d-%d', z, x, y); }\n\n var z0 = z,\n x0 = x,\n y0 = y,\n parent;\n\n while (!parent && z0 > 0) {\n z0--;\n x0 = Math.floor(x0 / 2);\n y0 = Math.floor(y0 / 2);\n parent = this$1.tiles[toID(z0, x0, y0)];\n }\n\n if (!parent || !parent.source) { return null; }\n\n // if we found a parent tile containing the original geometry, we can drill down from it\n if (debug > 1) { console.log('found parent tile z%d-%d-%d', z0, x0, y0); }\n\n // it parent tile is a solid clipped square, return it instead since it's identical\n if (isClippedSquare(parent, extent, options.buffer)) { return transform.tile(parent, extent); }\n\n if (debug > 1) { console.time('drilling down'); }\n var solid = this.splitTile(parent.source, z0, x0, y0, z, x, y);\n if (debug > 1) { console.timeEnd('drilling down'); }\n\n // one of the parent tiles was a solid clipped square\n if (solid !== null) {\n var m = 1 << (z - solid);\n id = toID(solid, Math.floor(x / m), Math.floor(y / m));\n }\n\n return this.tiles[id] ? transform.tile(this.tiles[id], extent) : null;\n};\n\nfunction toID(z, x, y) {\n return (((1 << z) * y + x) * 32) + z;\n}\n\nfunction intersectX(a, b, x) {\n return [x, (x - a[0]) * (b[1] - a[1]) / (b[0] - a[0]) + a[1], 1];\n}\nfunction intersectY(a, b, y) {\n return [(y - a[1]) * (b[0] - a[0]) / (b[1] - a[1]) + a[0], y, 1];\n}\n\nfunction extend(dest, src) {\n for (var i in src) { dest[i] = src[i]; }\n return dest;\n}\n\n// checks whether a tile is a whole-area fill after clipping; if it is, there's no sense slicing it further\nfunction isClippedSquare(tile, extent, buffer) {\n\n var features = tile.source;\n if (features.length !== 1) { return false; }\n\n var feature = features[0];\n if (feature.type !== 3 || feature.geometry.length > 1) { return false; }\n\n var len = feature.geometry[0].length;\n if (len !== 5) { return false; }\n\n for (var i = 0; i < len; i++) {\n var p = transform.point(feature.geometry[0][i], extent, tile.z2, tile.x, tile.y);\n if ((p[0] !== -buffer && p[0] !== extent + buffer) ||\n (p[1] !== -buffer && p[1] !== extent + buffer)) { return false; }\n }\n\n return true;\n}\n\nvar identity = function(x) {\n return x;\n};\n\nvar transform$3 = function(topology) {\n if ((transform = topology.transform) == null) { return identity; }\n var transform,\n x0,\n y0,\n kx = transform.scale[0],\n ky = transform.scale[1],\n dx = transform.translate[0],\n dy = transform.translate[1];\n return function(point, i) {\n if (!i) { x0 = y0 = 0; }\n point[0] = (x0 += point[0]) * kx + dx;\n point[1] = (y0 += point[1]) * ky + dy;\n return point;\n };\n};\n\nvar bbox = function(topology) {\n var bbox = topology.bbox;\n\n function bboxPoint(p0) {\n p1[0] = p0[0], p1[1] = p0[1], t(p1);\n if (p1[0] < x0) { x0 = p1[0]; }\n if (p1[0] > x1) { x1 = p1[0]; }\n if (p1[1] < y0) { y0 = p1[1]; }\n if (p1[1] > y1) { y1 = p1[1]; }\n }\n\n function bboxGeometry(o) {\n switch (o.type) {\n case \"GeometryCollection\": o.geometries.forEach(bboxGeometry); break;\n case \"Point\": bboxPoint(o.coordinates); break;\n case \"MultiPoint\": o.coordinates.forEach(bboxPoint); break;\n }\n }\n\n if (!bbox) {\n var t = transform$3(topology), p0, p1 = new Array(2), name,\n x0 = Infinity, y0 = x0, x1 = -x0, y1 = -x0;\n\n topology.arcs.forEach(function(arc) {\n var i = -1, n = arc.length;\n while (++i < n) {\n p0 = arc[i], p1[0] = p0[0], p1[1] = p0[1], t(p1, i);\n if (p1[0] < x0) { x0 = p1[0]; }\n if (p1[0] > x1) { x1 = p1[0]; }\n if (p1[1] < y0) { y0 = p1[1]; }\n if (p1[1] > y1) { y1 = p1[1]; }\n }\n });\n\n for (name in topology.objects) {\n bboxGeometry(topology.objects[name]);\n }\n\n bbox = topology.bbox = [x0, y0, x1, y1];\n }\n\n return bbox;\n};\n\nvar reverse = function(array, n) {\n var t, j = array.length, i = j - n;\n while (i < --j) { t = array[i], array[i++] = array[j], array[j] = t; }\n};\n\nvar feature = function(topology, o) {\n return o.type === \"GeometryCollection\"\n ? {type: \"FeatureCollection\", features: o.geometries.map(function(o) { return feature$1(topology, o); })}\n : feature$1(topology, o);\n};\n\nfunction feature$1(topology, o) {\n var id = o.id,\n bbox = o.bbox,\n properties = o.properties == null ? {} : o.properties,\n geometry = object(topology, o);\n return id == null && bbox == null ? {type: \"Feature\", properties: properties, geometry: geometry}\n : bbox == null ? {type: \"Feature\", id: id, properties: properties, geometry: geometry}\n : {type: \"Feature\", id: id, bbox: bbox, properties: properties, geometry: geometry};\n}\n\nfunction object(topology, o) {\n var transformPoint = transform$3(topology),\n arcs = topology.arcs;\n\n function arc(i, points) {\n if (points.length) { points.pop(); }\n for (var a = arcs[i < 0 ? ~i : i], k = 0, n = a.length; k < n; ++k) {\n points.push(transformPoint(a[k].slice(), k));\n }\n if (i < 0) { reverse(points, n); }\n }\n\n function point(p) {\n return transformPoint(p.slice());\n }\n\n function line(arcs) {\n var points = [];\n for (var i = 0, n = arcs.length; i < n; ++i) { arc(arcs[i], points); }\n if (points.length < 2) { points.push(points[0].slice()); }\n return points;\n }\n\n function ring(arcs) {\n var points = line(arcs);\n while (points.length < 4) { points.push(points[0].slice()); }\n return points;\n }\n\n function polygon(arcs) {\n return arcs.map(ring);\n }\n\n function geometry(o) {\n var type = o.type, coordinates;\n switch (type) {\n case \"GeometryCollection\": return {type: type, geometries: o.geometries.map(geometry)};\n case \"Point\": coordinates = point(o.coordinates); break;\n case \"MultiPoint\": coordinates = o.coordinates.map(point); break;\n case \"LineString\": coordinates = line(o.arcs); break;\n case \"MultiLineString\": coordinates = o.arcs.map(line); break;\n case \"Polygon\": coordinates = polygon(o.arcs); break;\n case \"MultiPolygon\": coordinates = o.arcs.map(polygon); break;\n default: return null;\n }\n return {type: type, coordinates: coordinates};\n }\n\n return geometry(o);\n}\n\nvar stitch = function(topology, arcs) {\n var stitchedArcs = {},\n fragmentByStart = {},\n fragmentByEnd = {},\n fragments = [],\n emptyIndex = -1;\n\n // Stitch empty arcs first, since they may be subsumed by other arcs.\n arcs.forEach(function(i, j) {\n var arc = topology.arcs[i < 0 ? ~i : i], t;\n if (arc.length < 3 && !arc[1][0] && !arc[1][1]) {\n t = arcs[++emptyIndex], arcs[emptyIndex] = i, arcs[j] = t;\n }\n });\n\n arcs.forEach(function(i) {\n var e = ends(i),\n start = e[0],\n end = e[1],\n f, g;\n\n if (f = fragmentByEnd[start]) {\n delete fragmentByEnd[f.end];\n f.push(i);\n f.end = end;\n if (g = fragmentByStart[end]) {\n delete fragmentByStart[g.start];\n var fg = g === f ? f : f.concat(g);\n fragmentByStart[fg.start = f.start] = fragmentByEnd[fg.end = g.end] = fg;\n } else {\n fragmentByStart[f.start] = fragmentByEnd[f.end] = f;\n }\n } else if (f = fragmentByStart[end]) {\n delete fragmentByStart[f.start];\n f.unshift(i);\n f.start = start;\n if (g = fragmentByEnd[start]) {\n delete fragmentByEnd[g.end];\n var gf = g === f ? f : g.concat(f);\n fragmentByStart[gf.start = g.start] = fragmentByEnd[gf.end = f.end] = gf;\n } else {\n fragmentByStart[f.start] = fragmentByEnd[f.end] = f;\n }\n } else {\n f = [i];\n fragmentByStart[f.start = start] = fragmentByEnd[f.end = end] = f;\n }\n });\n\n function ends(i) {\n var arc = topology.arcs[i < 0 ? ~i : i], p0 = arc[0], p1;\n if (topology.transform) { p1 = [0, 0], arc.forEach(function(dp) { p1[0] += dp[0], p1[1] += dp[1]; }); }\n else { p1 = arc[arc.length - 1]; }\n return i < 0 ? [p1, p0] : [p0, p1];\n }\n\n function flush(fragmentByEnd, fragmentByStart) {\n for (var k in fragmentByEnd) {\n var f = fragmentByEnd[k];\n delete fragmentByStart[f.start];\n delete f.start;\n delete f.end;\n f.forEach(function(i) { stitchedArcs[i < 0 ? ~i : i] = 1; });\n fragments.push(f);\n }\n }\n\n flush(fragmentByEnd, fragmentByStart);\n flush(fragmentByStart, fragmentByEnd);\n arcs.forEach(function(i) { if (!stitchedArcs[i < 0 ? ~i : i]) { fragments.push([i]); } });\n\n return fragments;\n};\n\nfunction extractArcs(topology, object$$1, filter) {\n var arcs = [],\n geomsByArc = [],\n geom;\n\n function extract0(i) {\n var j = i < 0 ? ~i : i;\n (geomsByArc[j] || (geomsByArc[j] = [])).push({i: i, g: geom});\n }\n\n function extract1(arcs) {\n arcs.forEach(extract0);\n }\n\n function extract2(arcs) {\n arcs.forEach(extract1);\n }\n\n function extract3(arcs) {\n arcs.forEach(extract2);\n }\n\n function geometry(o) {\n switch (geom = o, o.type) {\n case \"GeometryCollection\": o.geometries.forEach(geometry); break;\n case \"LineString\": extract1(o.arcs); break;\n case \"MultiLineString\": case \"Polygon\": extract2(o.arcs); break;\n case \"MultiPolygon\": extract3(o.arcs); break;\n }\n }\n\n geometry(object$$1);\n\n geomsByArc.forEach(filter == null\n ? function(geoms) { arcs.push(geoms[0].i); }\n : function(geoms) { if (filter(geoms[0].g, geoms[geoms.length - 1].g)) { arcs.push(geoms[0].i); } });\n\n return arcs;\n}\n\nfunction planarRingArea(ring) {\n var i = -1, n = ring.length, a, b = ring[n - 1], area = 0;\n while (++i < n) { a = b, b = ring[i], area += a[0] * b[1] - a[1] * b[0]; }\n return Math.abs(area); // Note: doubled area!\n}\n\nvar bisect = function(a, x) {\n var lo = 0, hi = a.length;\n while (lo < hi) {\n var mid = lo + hi >>> 1;\n if (a[mid] < x) { lo = mid + 1; }\n else { hi = mid; }\n }\n return lo;\n};\n\nvar slicers = {};\nvar options;\n\nonmessage = function (e) {\n if (e.data[0] === 'slice') {\n // Given a blob of GeoJSON and some topojson/geojson-vt options, do the slicing.\n var geojson = e.data[1];\n options = e.data[2];\n\n if (geojson.type && geojson.type === 'Topology') {\n for (var layerName in geojson.objects) {\n slicers[layerName] = index(\n feature(geojson, geojson.objects[layerName])\n , options);\n }\n } else {\n slicers[options.vectorTileLayerName] = index(geojson, options);\n }\n\n } else if (e.data[0] === 'get') {\n // Gets the vector tile for the given coordinates, sends it back as a message\n var coords = e.data[1];\n\n var tileLayers = {};\n for (var layerName in slicers) {\n var slicedTileLayer = slicers[layerName].getTile(coords.z, coords.x, coords.y);\n\n if (slicedTileLayer) {\n var vectorTileLayer = {\n features: [],\n extent: options.extent,\n name: options.vectorTileLayerName,\n length: slicedTileLayer.features.length\n };\n\n for (var i in slicedTileLayer.features) {\n var feat = {\n geometry: slicedTileLayer.features[i].geometry,\n properties: slicedTileLayer.features[i].tags,\n type: slicedTileLayer.features[i].type // 1 = point, 2 = line, 3 = polygon\n };\n vectorTileLayer.features.push(feat);\n }\n tileLayers[layerName] = vectorTileLayer;\n }\n }\n postMessage({ layers: tileLayers, coords: coords });\n }\n};\n//# sourceMap" + "pingURL=slicerWebWorker.js.worker.map\n", "text/plain; charset=us-ascii", false); 1856 | 1857 | // The geojson/topojson is sliced into tiles via a web worker. 1858 | // This import statement depends on rollup-file-as-blob, so that the 1859 | // variable 'workerCode' is a blob URL. 1860 | 1861 | /* 1862 | * 🍂class VectorGrid.Slicer 1863 | * 🍂extends VectorGrid 1864 | * 1865 | * A `VectorGrid` for slicing up big GeoJSON or TopoJSON documents in vector 1866 | * tiles, leveraging [`geojson-vt`](https://github.com/mapbox/geojson-vt). 1867 | * 1868 | * 🍂example 1869 | * 1870 | * ``` 1871 | * var geoJsonDocument = { 1872 | * type: 'FeatureCollection', 1873 | * features: [ ... ] 1874 | * }; 1875 | * 1876 | * L.vectorGrid.slicer(geoJsonDocument, { 1877 | * vectorTileLayerStyles: { 1878 | * sliced: { ... } 1879 | * } 1880 | * }).addTo(map); 1881 | * 1882 | * ``` 1883 | * 1884 | * `VectorGrid.Slicer` can also handle [TopoJSON](https://github.com/mbostock/topojson) transparently: 1885 | * ```js 1886 | * var layer = L.vectorGrid.slicer(topojson, options); 1887 | * ``` 1888 | * 1889 | * The TopoJSON format [implicitly groups features into "objects"](https://github.com/mbostock/topojson-specification/blob/master/README.md#215-objects). 1890 | * These will be transformed into vector tile layer names when styling (the 1891 | * `vectorTileLayerName` option is ignored when using TopoJSON). 1892 | * 1893 | */ 1894 | 1895 | L.VectorGrid.Slicer = L.VectorGrid.extend({ 1896 | 1897 | options: { 1898 | // 🍂section 1899 | // Additionally to these options, `VectorGrid.Slicer` can take in any 1900 | // of the [`geojson-vt` options](https://github.com/mapbox/geojson-vt#options). 1901 | 1902 | // 🍂option vectorTileLayerName: String = 'sliced' 1903 | // Vector tiles contain a set of *data layers*, and those data layers 1904 | // contain features. Thus, the slicer creates one data layer, with 1905 | // the name given in this option. This is important for symbolizing the data. 1906 | vectorTileLayerName: 'sliced', 1907 | 1908 | extent: 4096, // Default for geojson-vt 1909 | maxZoom: 14 // Default for geojson-vt 1910 | }, 1911 | 1912 | initialize: function(geojson, options) { 1913 | L.VectorGrid.prototype.initialize.call(this, options); 1914 | 1915 | // Create a shallow copy of this.options, excluding things that might 1916 | // be functions - we only care about topojson/geojsonvt options 1917 | var options = {}; 1918 | for (var i in this.options) { 1919 | if (i !== 'rendererFactory' && 1920 | i !== 'vectorTileLayerStyles' && 1921 | typeof (this.options[i]) !== 'function' 1922 | ) { 1923 | options[i] = this.options[i]; 1924 | } 1925 | } 1926 | 1927 | // this._worker = new Worker(window.URL.createObjectURL(new Blob([workerCode]))); 1928 | this._worker = new Worker(workerCode); 1929 | 1930 | // Send initial data to worker. 1931 | this._worker.postMessage(['slice', geojson, options]); 1932 | 1933 | }, 1934 | 1935 | 1936 | _getVectorTilePromise: function(coords) { 1937 | 1938 | var _this = this; 1939 | 1940 | var p = new Promise( function waitForWorker(res) { 1941 | _this._worker.addEventListener('message', function recv(m) { 1942 | if (m.data.coords && 1943 | m.data.coords.x === coords.x && 1944 | m.data.coords.y === coords.y && 1945 | m.data.coords.z === coords.z ) { 1946 | 1947 | res(m.data); 1948 | _this._worker.removeEventListener('message', recv); 1949 | } 1950 | }); 1951 | }); 1952 | 1953 | this._worker.postMessage(['get', coords]); 1954 | 1955 | return p; 1956 | }, 1957 | 1958 | }); 1959 | 1960 | 1961 | L.vectorGrid.slicer = function (geojson, options) { 1962 | return new L.VectorGrid.Slicer(geojson, options); 1963 | }; 1964 | 1965 | L.Canvas.Tile = L.Canvas.extend({ 1966 | 1967 | initialize: function (tileCoord, tileSize, options) { 1968 | L.Canvas.prototype.initialize.call(this, options); 1969 | this._tileCoord = tileCoord; 1970 | this._size = tileSize; 1971 | 1972 | this._initContainer(); 1973 | this._container.setAttribute('width', this._size.x); 1974 | this._container.setAttribute('height', this._size.y); 1975 | this._layers = {}; 1976 | this._drawnLayers = {}; 1977 | this._drawing = true; 1978 | 1979 | if (options.interactive) { 1980 | // By default, Leaflet tiles do not have pointer events 1981 | this._container.style.pointerEvents = 'auto'; 1982 | } 1983 | }, 1984 | 1985 | getCoord: function() { 1986 | return this._tileCoord; 1987 | }, 1988 | 1989 | getContainer: function() { 1990 | return this._container; 1991 | }, 1992 | 1993 | getOffset: function() { 1994 | return this._tileCoord.scaleBy(this._size).subtract(this._map.getPixelOrigin()); 1995 | }, 1996 | 1997 | onAdd: L.Util.falseFn, 1998 | 1999 | addTo: function(map) { 2000 | this._map = map; 2001 | }, 2002 | 2003 | removeFrom: function (map) { 2004 | delete this._map; 2005 | }, 2006 | 2007 | _onClick: function (e) { 2008 | var point = this._map.mouseEventToLayerPoint(e).subtract(this.getOffset()), layer, clickedLayer; 2009 | 2010 | for (var id in this._layers) { 2011 | layer = this._layers[id]; 2012 | if (layer.options.interactive && layer._containsPoint(point) && !this._map._draggableMoved(layer)) { 2013 | clickedLayer = layer; 2014 | } 2015 | } 2016 | if (clickedLayer) { 2017 | L.DomEvent.fakeStop(e); 2018 | this._fireEvent([clickedLayer], e); 2019 | } 2020 | }, 2021 | 2022 | _onMouseMove: function (e) { 2023 | if (!this._map || this._map.dragging.moving() || this._map._animatingZoom) { return; } 2024 | 2025 | var point = this._map.mouseEventToLayerPoint(e).subtract(this.getOffset()); 2026 | this._handleMouseHover(e, point); 2027 | }, 2028 | 2029 | /// TODO: Modify _initPath to include an extra parameter, a group name 2030 | /// to order symbolizers by z-index 2031 | 2032 | _updateIcon: function (layer) { 2033 | if (!this._drawing) { return; } 2034 | 2035 | var icon = layer.options.icon, 2036 | options = icon.options, 2037 | size = L.point(options.iconSize), 2038 | anchor = options.iconAnchor || 2039 | size && size.divideBy(2, true), 2040 | p = layer._point.subtract(anchor), 2041 | ctx = this._ctx, 2042 | img = layer._getImage(); 2043 | 2044 | if (img.complete) { 2045 | ctx.drawImage(img, p.x, p.y, size.x, size.y); 2046 | } else { 2047 | L.DomEvent.on(img, 'load', function() { 2048 | ctx.drawImage(img, p.x, p.y, size.x, size.y); 2049 | }); 2050 | } 2051 | 2052 | this._drawnLayers[layer._leaflet_id] = layer; 2053 | } 2054 | }); 2055 | 2056 | 2057 | L.canvas.tile = function(tileCoord, tileSize, opts){ 2058 | return new L.Canvas.Tile(tileCoord, tileSize, opts); 2059 | }; 2060 | 2061 | // Aux file to bundle everything together 2062 | //# sourceMappingURL=Leaflet.VectorGrid.js.map 2063 | -------------------------------------------------------------------------------- /tiles/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | Transit Vector Tile Service 17 | 18 | 233 | 234 | 235 | 236 |
237 |
238 |
239 | 242 |
243 |
244 | 245 |
246 |
247 |

248 | We provide free and weekly updated vector tiles for transit maps generated from the entire OpenStreetMap data, in various layouts. Currently we offer tiles for the following network types: tram, subway-lightrail, rail-commuter, (long distance) rail and the following layouts: geo, octi, octi-geo, orthorad. 249 |

250 |

251 | The code for generating these tiles can be found on GitHub. 253 |

254 | 255 |

256 | An example map is shown on this page. The example code can be used to quickly add our vector tiles to a Leaflet web map. See also our full example map. 257 |

258 | 259 |

260 | We offer the following TMS endpoints: 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 |
URLDescriptionLayout
https://loom.cs.uni-freiburg.de/tiles/tram/geo/{z}/{x}/{y}.mvtAll tram networks in OSMGeographical
https://loom.cs.uni-freiburg.de/tiles/tram/octi/{z}/{x}/{y}.mvtOctilinear
https://loom.cs.uni-freiburg.de/tiles/tram/geo-octi/{z}/{x}/{y}.mvtGeo-Octilinear
https://loom.cs.uni-freiburg.de/tiles/tram/orthorad/{z}/{x}/{y}.mvtOrthoradial
https://loom.cs.uni-freiburg.de/tiles/subway-lightrail/geo/{z}/{x}/{y}.mvtAll subway/lightrail networks in OSMGeographical
https://loom.cs.uni-freiburg.de/tiles/subway-lightrail/octi/{z}/{x}/{y}.mvtOctilinear
https://loom.cs.uni-freiburg.de/tiles/subway-lightrail/geo-octi/{z}/{x}/{y}.mvtGeo-Octilinear
https://loom.cs.uni-freiburg.de/tiles/subway-lightrail/orthorad/{z}/{x}/{y}.mvtOrthoradial
https://loom.cs.uni-freiburg.de/tiles/rail-commuter/geo/{z}/{x}/{y}.mvtAll commuter rail networks in OSMGeographical
https://loom.cs.uni-freiburg.de/tiles/rail-commuter/octi/{z}/{x}/{y}.mvtOctilinear
https://loom.cs.uni-freiburg.de/tiles/rail-commuter/geo-octi/{z}/{x}/{y}.mvtGeo-Octilinear
https://loom.cs.uni-freiburg.de/tiles/rail-commuter/orthorad/{z}/{x}/{y}.mvtOrthoradial
https://loom.cs.uni-freiburg.de/tiles/rail/geo/{z}/{x}/{y}.mvtAll long-distance rail networks in OSMGeographical
https://loom.cs.uni-freiburg.de/tiles/rail/octi/{z}/{x}/{y}.mvtOctilinear
https://loom.cs.uni-freiburg.de/tiles/rail/geo-octi/{z}/{x}/{y}.mvtGeo-Octilinear
https://loom.cs.uni-freiburg.de/tiles/rail/orthorad/{z}/{x}/{y}.mvtOrthoradial
293 |

294 | 295 | 296 |
297 |
298 | 299 |
300 |
301 | 302 | 303 |
304 |
305 |
306 | 307 |
308 |
const map = L.map('map');
309 | map.setView({lat: 48.145, lng: 11.574}, 12);
310 | 
311 | var baseLayer = L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
312 |     maxZoom: 17,
313 |     opacity: 1
314 | }).addTo(map);
315 | 
316 | L.vectorGrid.protobuf("https://loom.cs.uni-freiburg.de/tiles/subway-lightrail/orthorad/{z}/{x}/{y}.mvt", {
317 |     vectorTileLayerStyles: {
318 |         "lines": function(properties, zoom) {
319 |             return {
320 |                 lineCap: properties.lineCap,
321 |                 weight: properties.width,
322 |                 color: '#' + properties.color
323 |             }
324 |         },
325 |         "inner-connections": function(properties, zoom) {
326 |             return {
327 |                 lineCap: properties.lineCap,
328 |                 weight: properties.width,
329 |                 color: '#' + properties.color
330 |             }
331 |         },
332 |         "stations": function(properties, zoom) {
333 |             return {
334 |                 lineCap: properties.lineCap,
335 |                 weight: properties.width,
336 |                 color: '#' + properties.color,
337 |                 fillColor: '#' + properties.fillColor,
338 |                 fillOpacity: 1,
339 |                 fill: true
340 |             }
341 |         },
342 |     }
343 | }).addTo(map);        	
344 |
345 | 346 |
347 |
348 | 353 |
354 | 355 | 356 | 392 | 393 | 394 | -------------------------------------------------------------------------------- /tiles/leaflet.css: -------------------------------------------------------------------------------- 1 | /* required styles */ 2 | 3 | .leaflet-pane, 4 | .leaflet-tile, 5 | .leaflet-marker-icon, 6 | .leaflet-marker-shadow, 7 | .leaflet-tile-container, 8 | .leaflet-pane > svg, 9 | .leaflet-pane > canvas, 10 | .leaflet-zoom-box, 11 | .leaflet-image-layer, 12 | .leaflet-layer { 13 | position: absolute; 14 | left: 0; 15 | top: 0; 16 | } 17 | .leaflet-container { 18 | overflow: hidden; 19 | } 20 | .leaflet-tile, 21 | .leaflet-marker-icon, 22 | .leaflet-marker-shadow { 23 | -webkit-user-select: none; 24 | -moz-user-select: none; 25 | user-select: none; 26 | -webkit-user-drag: none; 27 | } 28 | /* Prevents IE11 from highlighting tiles in blue */ 29 | .leaflet-tile::selection { 30 | background: transparent; 31 | } 32 | /* Safari renders non-retina tile on retina better with this, but Chrome is worse */ 33 | .leaflet-safari .leaflet-tile { 34 | image-rendering: -webkit-optimize-contrast; 35 | } 36 | /* hack that prevents hw layers "stretching" when loading new tiles */ 37 | .leaflet-safari .leaflet-tile-container { 38 | width: 1600px; 39 | height: 1600px; 40 | -webkit-transform-origin: 0 0; 41 | } 42 | .leaflet-marker-icon, 43 | .leaflet-marker-shadow { 44 | display: block; 45 | } 46 | /* .leaflet-container svg: reset svg max-width decleration shipped in Joomla! (joomla.org) 3.x */ 47 | /* .leaflet-container img: map is broken in FF if you have max-width: 100% on tiles */ 48 | .leaflet-container .leaflet-overlay-pane svg { 49 | max-width: none !important; 50 | max-height: none !important; 51 | } 52 | .leaflet-container .leaflet-marker-pane img, 53 | .leaflet-container .leaflet-shadow-pane img, 54 | .leaflet-container .leaflet-tile-pane img, 55 | .leaflet-container img.leaflet-image-layer, 56 | .leaflet-container .leaflet-tile { 57 | max-width: none !important; 58 | max-height: none !important; 59 | width: auto; 60 | padding: 0; 61 | } 62 | 63 | .leaflet-container.leaflet-touch-zoom { 64 | -ms-touch-action: pan-x pan-y; 65 | touch-action: pan-x pan-y; 66 | } 67 | .leaflet-container.leaflet-touch-drag { 68 | -ms-touch-action: pinch-zoom; 69 | /* Fallback for FF which doesn't support pinch-zoom */ 70 | touch-action: none; 71 | touch-action: pinch-zoom; 72 | } 73 | .leaflet-container.leaflet-touch-drag.leaflet-touch-zoom { 74 | -ms-touch-action: none; 75 | touch-action: none; 76 | } 77 | .leaflet-container { 78 | -webkit-tap-highlight-color: transparent; 79 | } 80 | .leaflet-container a { 81 | -webkit-tap-highlight-color: rgba(51, 181, 229, 0.4); 82 | } 83 | .leaflet-tile { 84 | filter: inherit; 85 | visibility: hidden; 86 | } 87 | .leaflet-tile-loaded { 88 | visibility: inherit; 89 | } 90 | .leaflet-zoom-box { 91 | width: 0; 92 | height: 0; 93 | -moz-box-sizing: border-box; 94 | box-sizing: border-box; 95 | z-index: 800; 96 | } 97 | /* workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=888319 */ 98 | .leaflet-overlay-pane svg { 99 | -moz-user-select: none; 100 | } 101 | 102 | .leaflet-pane { z-index: 400; } 103 | 104 | .leaflet-tile-pane { z-index: 200; } 105 | .leaflet-overlay-pane { z-index: 400; } 106 | .leaflet-shadow-pane { z-index: 500; } 107 | .leaflet-marker-pane { z-index: 600; } 108 | .leaflet-tooltip-pane { z-index: 650; } 109 | .leaflet-popup-pane { z-index: 700; } 110 | 111 | .leaflet-map-pane canvas { z-index: 100; } 112 | .leaflet-map-pane svg { z-index: 200; } 113 | 114 | .leaflet-vml-shape { 115 | width: 1px; 116 | height: 1px; 117 | } 118 | .lvml { 119 | behavior: url(#default#VML); 120 | display: inline-block; 121 | position: absolute; 122 | } 123 | 124 | 125 | /* control positioning */ 126 | 127 | .leaflet-control { 128 | position: relative; 129 | z-index: 800; 130 | pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */ 131 | pointer-events: auto; 132 | } 133 | .leaflet-top, 134 | .leaflet-bottom { 135 | position: absolute; 136 | z-index: 1000; 137 | pointer-events: none; 138 | } 139 | .leaflet-top { 140 | top: 0; 141 | } 142 | .leaflet-right { 143 | right: 0; 144 | } 145 | .leaflet-bottom { 146 | bottom: 0; 147 | } 148 | .leaflet-left { 149 | left: 0; 150 | } 151 | .leaflet-control { 152 | float: left; 153 | clear: both; 154 | } 155 | .leaflet-right .leaflet-control { 156 | float: right; 157 | } 158 | .leaflet-top .leaflet-control { 159 | margin-top: 10px; 160 | } 161 | .leaflet-bottom .leaflet-control { 162 | margin-bottom: 10px; 163 | } 164 | .leaflet-left .leaflet-control { 165 | margin-left: 10px; 166 | } 167 | .leaflet-right .leaflet-control { 168 | margin-right: 10px; 169 | } 170 | 171 | 172 | /* zoom and fade animations */ 173 | 174 | .leaflet-fade-anim .leaflet-popup { 175 | opacity: 0; 176 | -webkit-transition: opacity 0.2s linear; 177 | -moz-transition: opacity 0.2s linear; 178 | transition: opacity 0.2s linear; 179 | } 180 | .leaflet-fade-anim .leaflet-map-pane .leaflet-popup { 181 | opacity: 1; 182 | } 183 | .leaflet-zoom-animated { 184 | -webkit-transform-origin: 0 0; 185 | -ms-transform-origin: 0 0; 186 | transform-origin: 0 0; 187 | } 188 | svg.leaflet-zoom-animated { 189 | will-change: transform; 190 | } 191 | 192 | .leaflet-zoom-anim .leaflet-zoom-animated { 193 | -webkit-transition: -webkit-transform 0.25s cubic-bezier(0,0,0.25,1); 194 | -moz-transition: -moz-transform 0.25s cubic-bezier(0,0,0.25,1); 195 | transition: transform 0.25s cubic-bezier(0,0,0.25,1); 196 | } 197 | .leaflet-zoom-anim .leaflet-tile, 198 | .leaflet-pan-anim .leaflet-tile { 199 | -webkit-transition: none; 200 | -moz-transition: none; 201 | transition: none; 202 | } 203 | 204 | .leaflet-zoom-anim .leaflet-zoom-hide { 205 | visibility: hidden; 206 | } 207 | 208 | 209 | /* cursors */ 210 | 211 | .leaflet-interactive { 212 | cursor: pointer; 213 | } 214 | .leaflet-grab { 215 | cursor: -webkit-grab; 216 | cursor: -moz-grab; 217 | cursor: grab; 218 | } 219 | .leaflet-crosshair, 220 | .leaflet-crosshair .leaflet-interactive { 221 | cursor: crosshair; 222 | } 223 | .leaflet-popup-pane, 224 | .leaflet-control { 225 | cursor: auto; 226 | } 227 | .leaflet-dragging .leaflet-grab, 228 | .leaflet-dragging .leaflet-grab .leaflet-interactive, 229 | .leaflet-dragging .leaflet-marker-draggable { 230 | cursor: move; 231 | cursor: -webkit-grabbing; 232 | cursor: -moz-grabbing; 233 | cursor: grabbing; 234 | } 235 | 236 | /* marker & overlays interactivity */ 237 | .leaflet-marker-icon, 238 | .leaflet-marker-shadow, 239 | .leaflet-image-layer, 240 | .leaflet-pane > svg path, 241 | .leaflet-tile-container { 242 | pointer-events: none; 243 | } 244 | 245 | .leaflet-marker-icon.leaflet-interactive, 246 | .leaflet-image-layer.leaflet-interactive, 247 | .leaflet-pane > svg path.leaflet-interactive, 248 | svg.leaflet-image-layer.leaflet-interactive path { 249 | pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */ 250 | pointer-events: auto; 251 | } 252 | 253 | /* visual tweaks */ 254 | 255 | .leaflet-container { 256 | background: #ddd; 257 | outline-offset: 1px; 258 | } 259 | .leaflet-container a { 260 | color: #0078A8; 261 | } 262 | .leaflet-zoom-box { 263 | border: 2px dotted #38f; 264 | background: rgba(255,255,255,0.5); 265 | } 266 | 267 | 268 | /* general typography */ 269 | .leaflet-container { 270 | font-family: "Helvetica Neue", Arial, Helvetica, sans-serif; 271 | font-size: 12px; 272 | font-size: 0.75rem; 273 | line-height: 1.5; 274 | } 275 | 276 | 277 | /* general toolbar styles */ 278 | 279 | .leaflet-bar { 280 | box-shadow: 0 1px 5px rgba(0,0,0,0.65); 281 | border-radius: 4px; 282 | } 283 | .leaflet-bar a { 284 | background-color: #fff; 285 | border-bottom: 1px solid #ccc; 286 | width: 26px; 287 | height: 26px; 288 | line-height: 26px; 289 | display: block; 290 | text-align: center; 291 | text-decoration: none; 292 | color: black; 293 | } 294 | .leaflet-bar a, 295 | .leaflet-control-layers-toggle { 296 | background-position: 50% 50%; 297 | background-repeat: no-repeat; 298 | display: block; 299 | } 300 | .leaflet-bar a:hover, 301 | .leaflet-bar a:focus { 302 | background-color: #f4f4f4; 303 | } 304 | .leaflet-bar a:first-child { 305 | border-top-left-radius: 4px; 306 | border-top-right-radius: 4px; 307 | } 308 | .leaflet-bar a:last-child { 309 | border-bottom-left-radius: 4px; 310 | border-bottom-right-radius: 4px; 311 | border-bottom: none; 312 | } 313 | .leaflet-bar a.leaflet-disabled { 314 | cursor: default; 315 | background-color: #f4f4f4; 316 | color: #bbb; 317 | } 318 | 319 | .leaflet-touch .leaflet-bar a { 320 | width: 30px; 321 | height: 30px; 322 | line-height: 30px; 323 | } 324 | .leaflet-touch .leaflet-bar a:first-child { 325 | border-top-left-radius: 2px; 326 | border-top-right-radius: 2px; 327 | } 328 | .leaflet-touch .leaflet-bar a:last-child { 329 | border-bottom-left-radius: 2px; 330 | border-bottom-right-radius: 2px; 331 | } 332 | 333 | /* zoom control */ 334 | 335 | .leaflet-control-zoom-in, 336 | .leaflet-control-zoom-out { 337 | font: bold 18px 'Lucida Console', Monaco, monospace; 338 | text-indent: 1px; 339 | } 340 | 341 | .leaflet-touch .leaflet-control-zoom-in, .leaflet-touch .leaflet-control-zoom-out { 342 | font-size: 22px; 343 | } 344 | 345 | 346 | /* layers control */ 347 | 348 | .leaflet-control-layers { 349 | box-shadow: 0 1px 5px rgba(0,0,0,0.4); 350 | background: #fff; 351 | border-radius: 5px; 352 | } 353 | .leaflet-control-layers-toggle { 354 | background-image: url(images/layers.png); 355 | width: 36px; 356 | height: 36px; 357 | } 358 | .leaflet-retina .leaflet-control-layers-toggle { 359 | background-image: url(images/layers-2x.png); 360 | background-size: 26px 26px; 361 | } 362 | .leaflet-touch .leaflet-control-layers-toggle { 363 | width: 44px; 364 | height: 44px; 365 | } 366 | .leaflet-control-layers .leaflet-control-layers-list, 367 | .leaflet-control-layers-expanded .leaflet-control-layers-toggle { 368 | display: none; 369 | } 370 | .leaflet-control-layers-expanded .leaflet-control-layers-list { 371 | display: block; 372 | position: relative; 373 | } 374 | .leaflet-control-layers-expanded { 375 | padding: 6px 10px 6px 6px; 376 | color: #333; 377 | background: #fff; 378 | } 379 | .leaflet-control-layers-scrollbar { 380 | overflow-y: scroll; 381 | overflow-x: hidden; 382 | padding-right: 5px; 383 | } 384 | .leaflet-control-layers-selector { 385 | margin-top: 2px; 386 | position: relative; 387 | top: 1px; 388 | } 389 | .leaflet-control-layers label { 390 | display: block; 391 | font-size: 13px; 392 | font-size: 1.08333em; 393 | } 394 | .leaflet-control-layers-separator { 395 | height: 0; 396 | border-top: 1px solid #ddd; 397 | margin: 5px -10px 5px -6px; 398 | } 399 | 400 | /* Default icon URLs */ 401 | .leaflet-default-icon-path { /* used only in path-guessing heuristic, see L.Icon.Default */ 402 | background-image: url(images/marker-icon.png); 403 | } 404 | 405 | 406 | /* attribution and scale controls */ 407 | 408 | .leaflet-container .leaflet-control-attribution { 409 | background: #fff; 410 | background: rgba(255, 255, 255, 0.8); 411 | margin: 0; 412 | } 413 | .leaflet-control-attribution, 414 | .leaflet-control-scale-line { 415 | padding: 0 5px; 416 | color: #333; 417 | line-height: 1.4; 418 | } 419 | .leaflet-control-attribution a { 420 | text-decoration: none; 421 | } 422 | .leaflet-control-attribution a:hover, 423 | .leaflet-control-attribution a:focus { 424 | text-decoration: underline; 425 | } 426 | .leaflet-attribution-flag { 427 | display: inline !important; 428 | vertical-align: baseline !important; 429 | width: 1em; 430 | height: 0.6669em; 431 | } 432 | .leaflet-left .leaflet-control-scale { 433 | margin-left: 5px; 434 | } 435 | .leaflet-bottom .leaflet-control-scale { 436 | margin-bottom: 5px; 437 | } 438 | .leaflet-control-scale-line { 439 | border: 2px solid #777; 440 | border-top: none; 441 | line-height: 1.1; 442 | padding: 2px 5px 1px; 443 | white-space: nowrap; 444 | -moz-box-sizing: border-box; 445 | box-sizing: border-box; 446 | background: rgba(255, 255, 255, 0.8); 447 | text-shadow: 1px 1px #fff; 448 | } 449 | .leaflet-control-scale-line:not(:first-child) { 450 | border-top: 2px solid #777; 451 | border-bottom: none; 452 | margin-top: -2px; 453 | } 454 | .leaflet-control-scale-line:not(:first-child):not(:last-child) { 455 | border-bottom: 2px solid #777; 456 | } 457 | 458 | .leaflet-touch .leaflet-control-attribution, 459 | .leaflet-touch .leaflet-control-layers, 460 | .leaflet-touch .leaflet-bar { 461 | box-shadow: none; 462 | } 463 | .leaflet-touch .leaflet-control-layers, 464 | .leaflet-touch .leaflet-bar { 465 | border: 2px solid rgba(0,0,0,0.2); 466 | background-clip: padding-box; 467 | } 468 | 469 | 470 | /* popup */ 471 | 472 | .leaflet-popup { 473 | position: absolute; 474 | text-align: center; 475 | margin-bottom: 20px; 476 | } 477 | .leaflet-popup-content-wrapper { 478 | padding: 1px; 479 | text-align: left; 480 | border-radius: 12px; 481 | } 482 | .leaflet-popup-content { 483 | margin: 13px 24px 13px 20px; 484 | line-height: 1.3; 485 | font-size: 13px; 486 | font-size: 1.08333em; 487 | min-height: 1px; 488 | } 489 | .leaflet-popup-content p { 490 | margin: 17px 0; 491 | margin: 1.3em 0; 492 | } 493 | .leaflet-popup-tip-container { 494 | width: 40px; 495 | height: 20px; 496 | position: absolute; 497 | left: 50%; 498 | margin-top: -1px; 499 | margin-left: -20px; 500 | overflow: hidden; 501 | pointer-events: none; 502 | } 503 | .leaflet-popup-tip { 504 | width: 17px; 505 | height: 17px; 506 | padding: 1px; 507 | 508 | margin: -10px auto 0; 509 | pointer-events: auto; 510 | 511 | -webkit-transform: rotate(45deg); 512 | -moz-transform: rotate(45deg); 513 | -ms-transform: rotate(45deg); 514 | transform: rotate(45deg); 515 | } 516 | .leaflet-popup-content-wrapper, 517 | .leaflet-popup-tip { 518 | background: white; 519 | color: #333; 520 | box-shadow: 0 3px 14px rgba(0,0,0,0.4); 521 | } 522 | .leaflet-container a.leaflet-popup-close-button { 523 | position: absolute; 524 | top: 0; 525 | right: 0; 526 | border: none; 527 | text-align: center; 528 | width: 24px; 529 | height: 24px; 530 | font: 16px/24px Tahoma, Verdana, sans-serif; 531 | color: #757575; 532 | text-decoration: none; 533 | background: transparent; 534 | } 535 | .leaflet-container a.leaflet-popup-close-button:hover, 536 | .leaflet-container a.leaflet-popup-close-button:focus { 537 | color: #585858; 538 | } 539 | .leaflet-popup-scrolled { 540 | overflow: auto; 541 | } 542 | 543 | .leaflet-oldie .leaflet-popup-content-wrapper { 544 | -ms-zoom: 1; 545 | } 546 | .leaflet-oldie .leaflet-popup-tip { 547 | width: 24px; 548 | margin: 0 auto; 549 | 550 | -ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)"; 551 | filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678); 552 | } 553 | 554 | .leaflet-oldie .leaflet-control-zoom, 555 | .leaflet-oldie .leaflet-control-layers, 556 | .leaflet-oldie .leaflet-popup-content-wrapper, 557 | .leaflet-oldie .leaflet-popup-tip { 558 | border: 1px solid #999; 559 | } 560 | 561 | 562 | /* div icon */ 563 | 564 | .leaflet-div-icon { 565 | background: #fff; 566 | border: 1px solid #666; 567 | } 568 | 569 | 570 | /* Tooltip */ 571 | /* Base styles for the element that has a tooltip */ 572 | .leaflet-tooltip { 573 | position: absolute; 574 | padding: 6px; 575 | background-color: #fff; 576 | border: 1px solid #fff; 577 | border-radius: 3px; 578 | color: #222; 579 | white-space: nowrap; 580 | -webkit-user-select: none; 581 | -moz-user-select: none; 582 | -ms-user-select: none; 583 | user-select: none; 584 | pointer-events: none; 585 | box-shadow: 0 1px 3px rgba(0,0,0,0.4); 586 | } 587 | .leaflet-tooltip.leaflet-interactive { 588 | cursor: pointer; 589 | pointer-events: auto; 590 | } 591 | .leaflet-tooltip-top:before, 592 | .leaflet-tooltip-bottom:before, 593 | .leaflet-tooltip-left:before, 594 | .leaflet-tooltip-right:before { 595 | position: absolute; 596 | pointer-events: none; 597 | border: 6px solid transparent; 598 | background: transparent; 599 | content: ""; 600 | } 601 | 602 | /* Directions */ 603 | 604 | .leaflet-tooltip-bottom { 605 | margin-top: 6px; 606 | } 607 | .leaflet-tooltip-top { 608 | margin-top: -6px; 609 | } 610 | .leaflet-tooltip-bottom:before, 611 | .leaflet-tooltip-top:before { 612 | left: 50%; 613 | margin-left: -6px; 614 | } 615 | .leaflet-tooltip-top:before { 616 | bottom: 0; 617 | margin-bottom: -12px; 618 | border-top-color: #fff; 619 | } 620 | .leaflet-tooltip-bottom:before { 621 | top: 0; 622 | margin-top: -12px; 623 | margin-left: -6px; 624 | border-bottom-color: #fff; 625 | } 626 | .leaflet-tooltip-left { 627 | margin-left: -6px; 628 | } 629 | .leaflet-tooltip-right { 630 | margin-left: 6px; 631 | } 632 | .leaflet-tooltip-left:before, 633 | .leaflet-tooltip-right:before { 634 | top: 50%; 635 | margin-top: -6px; 636 | } 637 | .leaflet-tooltip-left:before { 638 | right: 0; 639 | margin-right: -12px; 640 | border-left-color: #fff; 641 | } 642 | .leaflet-tooltip-right:before { 643 | left: 0; 644 | margin-left: -12px; 645 | border-right-color: #fff; 646 | } 647 | 648 | /* Printing */ 649 | 650 | @media print { 651 | /* Prevent printers from removing background-images of controls. */ 652 | .leaflet-control { 653 | -webkit-print-color-adjust: exact; 654 | print-color-adjust: exact; 655 | } 656 | } 657 | -------------------------------------------------------------------------------- /web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | LOOM Global Transit Maps 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 96 | 97 | 98 |
99 | 100 |
101 | 107 | 113 |
114 | 115 | 122 | 123 | 418 | 419 | 420 | -------------------------------------------------------------------------------- /web/leaflet.css: -------------------------------------------------------------------------------- 1 | /* required styles */ 2 | 3 | .leaflet-pane, 4 | .leaflet-tile, 5 | .leaflet-marker-icon, 6 | .leaflet-marker-shadow, 7 | .leaflet-tile-container, 8 | .leaflet-pane > svg, 9 | .leaflet-pane > canvas, 10 | .leaflet-zoom-box, 11 | .leaflet-image-layer, 12 | .leaflet-layer { 13 | position: absolute; 14 | left: 0; 15 | top: 0; 16 | } 17 | .leaflet-container { 18 | overflow: hidden; 19 | } 20 | .leaflet-tile, 21 | .leaflet-marker-icon, 22 | .leaflet-marker-shadow { 23 | -webkit-user-select: none; 24 | -moz-user-select: none; 25 | user-select: none; 26 | -webkit-user-drag: none; 27 | } 28 | /* Prevents IE11 from highlighting tiles in blue */ 29 | .leaflet-tile::selection { 30 | background: transparent; 31 | } 32 | /* Safari renders non-retina tile on retina better with this, but Chrome is worse */ 33 | .leaflet-safari .leaflet-tile { 34 | image-rendering: -webkit-optimize-contrast; 35 | } 36 | /* hack that prevents hw layers "stretching" when loading new tiles */ 37 | .leaflet-safari .leaflet-tile-container { 38 | width: 1600px; 39 | height: 1600px; 40 | -webkit-transform-origin: 0 0; 41 | } 42 | .leaflet-marker-icon, 43 | .leaflet-marker-shadow { 44 | display: block; 45 | } 46 | /* .leaflet-container svg: reset svg max-width decleration shipped in Joomla! (joomla.org) 3.x */ 47 | /* .leaflet-container img: map is broken in FF if you have max-width: 100% on tiles */ 48 | .leaflet-container .leaflet-overlay-pane svg { 49 | max-width: none !important; 50 | max-height: none !important; 51 | } 52 | .leaflet-container .leaflet-marker-pane img, 53 | .leaflet-container .leaflet-shadow-pane img, 54 | .leaflet-container .leaflet-tile-pane img, 55 | .leaflet-container img.leaflet-image-layer, 56 | .leaflet-container .leaflet-tile { 57 | max-width: none !important; 58 | max-height: none !important; 59 | width: auto; 60 | padding: 0; 61 | } 62 | 63 | .leaflet-container.leaflet-touch-zoom { 64 | -ms-touch-action: pan-x pan-y; 65 | touch-action: pan-x pan-y; 66 | } 67 | .leaflet-container.leaflet-touch-drag { 68 | -ms-touch-action: pinch-zoom; 69 | /* Fallback for FF which doesn't support pinch-zoom */ 70 | touch-action: none; 71 | touch-action: pinch-zoom; 72 | } 73 | .leaflet-container.leaflet-touch-drag.leaflet-touch-zoom { 74 | -ms-touch-action: none; 75 | touch-action: none; 76 | } 77 | .leaflet-container { 78 | -webkit-tap-highlight-color: transparent; 79 | } 80 | .leaflet-container a { 81 | -webkit-tap-highlight-color: rgba(51, 181, 229, 0.4); 82 | } 83 | .leaflet-tile { 84 | filter: inherit; 85 | visibility: hidden; 86 | } 87 | .leaflet-tile-loaded { 88 | visibility: inherit; 89 | } 90 | .leaflet-zoom-box { 91 | width: 0; 92 | height: 0; 93 | -moz-box-sizing: border-box; 94 | box-sizing: border-box; 95 | z-index: 800; 96 | } 97 | /* workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=888319 */ 98 | .leaflet-overlay-pane svg { 99 | -moz-user-select: none; 100 | } 101 | 102 | .leaflet-pane { z-index: 400; } 103 | 104 | .leaflet-tile-pane { z-index: 200; } 105 | .leaflet-overlay-pane { z-index: 400; } 106 | .leaflet-shadow-pane { z-index: 500; } 107 | .leaflet-marker-pane { z-index: 600; } 108 | .leaflet-tooltip-pane { z-index: 650; } 109 | .leaflet-popup-pane { z-index: 700; } 110 | 111 | .leaflet-map-pane canvas { z-index: 100; } 112 | .leaflet-map-pane svg { z-index: 200; } 113 | 114 | .leaflet-vml-shape { 115 | width: 1px; 116 | height: 1px; 117 | } 118 | .lvml { 119 | behavior: url(#default#VML); 120 | display: inline-block; 121 | position: absolute; 122 | } 123 | 124 | 125 | /* control positioning */ 126 | 127 | .leaflet-control { 128 | position: relative; 129 | z-index: 800; 130 | pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */ 131 | pointer-events: auto; 132 | } 133 | .leaflet-top, 134 | .leaflet-bottom { 135 | position: absolute; 136 | z-index: 1000; 137 | pointer-events: none; 138 | } 139 | .leaflet-top { 140 | top: 0; 141 | } 142 | .leaflet-right { 143 | right: 0; 144 | } 145 | .leaflet-bottom { 146 | bottom: 0; 147 | } 148 | .leaflet-left { 149 | left: 0; 150 | } 151 | .leaflet-control { 152 | float: left; 153 | clear: both; 154 | } 155 | .leaflet-right .leaflet-control { 156 | float: right; 157 | } 158 | .leaflet-top .leaflet-control { 159 | margin-top: 10px; 160 | } 161 | .leaflet-bottom .leaflet-control { 162 | margin-bottom: 10px; 163 | } 164 | .leaflet-left .leaflet-control { 165 | margin-left: 10px; 166 | } 167 | .leaflet-right .leaflet-control { 168 | margin-right: 10px; 169 | } 170 | 171 | 172 | /* zoom and fade animations */ 173 | 174 | .leaflet-fade-anim .leaflet-popup { 175 | opacity: 0; 176 | -webkit-transition: opacity 0.2s linear; 177 | -moz-transition: opacity 0.2s linear; 178 | transition: opacity 0.2s linear; 179 | } 180 | .leaflet-fade-anim .leaflet-map-pane .leaflet-popup { 181 | opacity: 1; 182 | } 183 | .leaflet-zoom-animated { 184 | -webkit-transform-origin: 0 0; 185 | -ms-transform-origin: 0 0; 186 | transform-origin: 0 0; 187 | } 188 | svg.leaflet-zoom-animated { 189 | will-change: transform; 190 | } 191 | 192 | .leaflet-zoom-anim .leaflet-zoom-animated { 193 | -webkit-transition: -webkit-transform 0.25s cubic-bezier(0,0,0.25,1); 194 | -moz-transition: -moz-transform 0.25s cubic-bezier(0,0,0.25,1); 195 | transition: transform 0.25s cubic-bezier(0,0,0.25,1); 196 | } 197 | .leaflet-zoom-anim .leaflet-tile, 198 | .leaflet-pan-anim .leaflet-tile { 199 | -webkit-transition: none; 200 | -moz-transition: none; 201 | transition: none; 202 | } 203 | 204 | .leaflet-zoom-anim .leaflet-zoom-hide { 205 | visibility: hidden; 206 | } 207 | 208 | 209 | /* cursors */ 210 | 211 | .leaflet-interactive { 212 | cursor: pointer; 213 | } 214 | .leaflet-grab { 215 | cursor: -webkit-grab; 216 | cursor: -moz-grab; 217 | cursor: grab; 218 | } 219 | .leaflet-crosshair, 220 | .leaflet-crosshair .leaflet-interactive { 221 | cursor: crosshair; 222 | } 223 | .leaflet-popup-pane, 224 | .leaflet-control { 225 | cursor: auto; 226 | } 227 | .leaflet-dragging .leaflet-grab, 228 | .leaflet-dragging .leaflet-grab .leaflet-interactive, 229 | .leaflet-dragging .leaflet-marker-draggable { 230 | cursor: move; 231 | cursor: -webkit-grabbing; 232 | cursor: -moz-grabbing; 233 | cursor: grabbing; 234 | } 235 | 236 | /* marker & overlays interactivity */ 237 | .leaflet-marker-icon, 238 | .leaflet-marker-shadow, 239 | .leaflet-image-layer, 240 | .leaflet-pane > svg path, 241 | .leaflet-tile-container { 242 | pointer-events: none; 243 | } 244 | 245 | .leaflet-marker-icon.leaflet-interactive, 246 | .leaflet-image-layer.leaflet-interactive, 247 | .leaflet-pane > svg path.leaflet-interactive, 248 | svg.leaflet-image-layer.leaflet-interactive path { 249 | pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */ 250 | pointer-events: auto; 251 | } 252 | 253 | /* visual tweaks */ 254 | 255 | .leaflet-container { 256 | background: #ddd; 257 | outline-offset: 1px; 258 | } 259 | .leaflet-container a { 260 | color: #0078A8; 261 | } 262 | .leaflet-zoom-box { 263 | border: 2px dotted #38f; 264 | background: rgba(255,255,255,0.5); 265 | } 266 | 267 | 268 | /* general typography */ 269 | .leaflet-container { 270 | font-family: "Helvetica Neue", Arial, Helvetica, sans-serif; 271 | font-size: 12px; 272 | font-size: 0.75rem; 273 | line-height: 1.5; 274 | } 275 | 276 | 277 | /* general toolbar styles */ 278 | 279 | .leaflet-bar { 280 | box-shadow: 0 1px 5px rgba(0,0,0,0.65); 281 | border-radius: 4px; 282 | } 283 | .leaflet-bar a { 284 | background-color: #fff; 285 | border-bottom: 1px solid #ccc; 286 | width: 26px; 287 | height: 26px; 288 | line-height: 26px; 289 | display: block; 290 | text-align: center; 291 | text-decoration: none; 292 | color: black; 293 | } 294 | .leaflet-bar a, 295 | .leaflet-control-layers-toggle { 296 | background-position: 50% 50%; 297 | background-repeat: no-repeat; 298 | display: block; 299 | } 300 | .leaflet-bar a:hover, 301 | .leaflet-bar a:focus { 302 | background-color: #f4f4f4; 303 | } 304 | .leaflet-bar a:first-child { 305 | border-top-left-radius: 4px; 306 | border-top-right-radius: 4px; 307 | } 308 | .leaflet-bar a:last-child { 309 | border-bottom-left-radius: 4px; 310 | border-bottom-right-radius: 4px; 311 | border-bottom: none; 312 | } 313 | .leaflet-bar a.leaflet-disabled { 314 | cursor: default; 315 | background-color: #f4f4f4; 316 | color: #bbb; 317 | } 318 | 319 | .leaflet-touch .leaflet-bar a { 320 | width: 30px; 321 | height: 30px; 322 | line-height: 30px; 323 | } 324 | .leaflet-touch .leaflet-bar a:first-child { 325 | border-top-left-radius: 2px; 326 | border-top-right-radius: 2px; 327 | } 328 | .leaflet-touch .leaflet-bar a:last-child { 329 | border-bottom-left-radius: 2px; 330 | border-bottom-right-radius: 2px; 331 | } 332 | 333 | /* zoom control */ 334 | 335 | .leaflet-control-zoom-in, 336 | .leaflet-control-zoom-out { 337 | font: bold 18px 'Lucida Console', Monaco, monospace; 338 | text-indent: 1px; 339 | } 340 | 341 | .leaflet-touch .leaflet-control-zoom-in, .leaflet-touch .leaflet-control-zoom-out { 342 | font-size: 22px; 343 | } 344 | 345 | 346 | /* layers control */ 347 | 348 | .leaflet-control-layers { 349 | box-shadow: 0 1px 5px rgba(0,0,0,0.4); 350 | background: #fff; 351 | border-radius: 5px; 352 | } 353 | .leaflet-control-layers-toggle { 354 | background-image: url(images/layers.png); 355 | width: 36px; 356 | height: 36px; 357 | } 358 | .leaflet-retina .leaflet-control-layers-toggle { 359 | background-image: url(images/layers-2x.png); 360 | background-size: 26px 26px; 361 | } 362 | .leaflet-touch .leaflet-control-layers-toggle { 363 | width: 44px; 364 | height: 44px; 365 | } 366 | .leaflet-control-layers .leaflet-control-layers-list, 367 | .leaflet-control-layers-expanded .leaflet-control-layers-toggle { 368 | display: none; 369 | } 370 | .leaflet-control-layers-expanded .leaflet-control-layers-list { 371 | display: block; 372 | position: relative; 373 | } 374 | .leaflet-control-layers-expanded { 375 | padding: 6px 10px 6px 6px; 376 | color: #333; 377 | background: #fff; 378 | } 379 | .leaflet-control-layers-scrollbar { 380 | overflow-y: scroll; 381 | overflow-x: hidden; 382 | padding-right: 5px; 383 | } 384 | .leaflet-control-layers-selector { 385 | margin-top: 2px; 386 | position: relative; 387 | top: 1px; 388 | } 389 | .leaflet-control-layers label { 390 | display: block; 391 | font-size: 13px; 392 | font-size: 1.08333em; 393 | } 394 | .leaflet-control-layers-separator { 395 | height: 0; 396 | border-top: 1px solid #ddd; 397 | margin: 5px -10px 5px -6px; 398 | } 399 | 400 | /* Default icon URLs */ 401 | .leaflet-default-icon-path { /* used only in path-guessing heuristic, see L.Icon.Default */ 402 | background-image: url(images/marker-icon.png); 403 | } 404 | 405 | 406 | /* attribution and scale controls */ 407 | 408 | .leaflet-container .leaflet-control-attribution { 409 | background: #fff; 410 | background: rgba(255, 255, 255, 0.8); 411 | margin: 0; 412 | } 413 | .leaflet-control-attribution, 414 | .leaflet-control-scale-line { 415 | padding: 0 5px; 416 | color: #333; 417 | line-height: 1.4; 418 | } 419 | .leaflet-control-attribution a { 420 | text-decoration: none; 421 | } 422 | .leaflet-control-attribution a:hover, 423 | .leaflet-control-attribution a:focus { 424 | text-decoration: underline; 425 | } 426 | .leaflet-attribution-flag { 427 | display: inline !important; 428 | vertical-align: baseline !important; 429 | width: 1em; 430 | height: 0.6669em; 431 | } 432 | .leaflet-left .leaflet-control-scale { 433 | margin-left: 5px; 434 | } 435 | .leaflet-bottom .leaflet-control-scale { 436 | margin-bottom: 5px; 437 | } 438 | .leaflet-control-scale-line { 439 | border: 2px solid #777; 440 | border-top: none; 441 | line-height: 1.1; 442 | padding: 2px 5px 1px; 443 | white-space: nowrap; 444 | -moz-box-sizing: border-box; 445 | box-sizing: border-box; 446 | background: rgba(255, 255, 255, 0.8); 447 | text-shadow: 1px 1px #fff; 448 | } 449 | .leaflet-control-scale-line:not(:first-child) { 450 | border-top: 2px solid #777; 451 | border-bottom: none; 452 | margin-top: -2px; 453 | } 454 | .leaflet-control-scale-line:not(:first-child):not(:last-child) { 455 | border-bottom: 2px solid #777; 456 | } 457 | 458 | .leaflet-touch .leaflet-control-attribution, 459 | .leaflet-touch .leaflet-control-layers, 460 | .leaflet-touch .leaflet-bar { 461 | box-shadow: none; 462 | } 463 | .leaflet-touch .leaflet-control-layers, 464 | .leaflet-touch .leaflet-bar { 465 | border: 2px solid rgba(0,0,0,0.2); 466 | background-clip: padding-box; 467 | } 468 | 469 | 470 | /* popup */ 471 | 472 | .leaflet-popup { 473 | position: absolute; 474 | text-align: center; 475 | margin-bottom: 20px; 476 | } 477 | .leaflet-popup-content-wrapper { 478 | padding: 1px; 479 | text-align: left; 480 | border-radius: 12px; 481 | } 482 | .leaflet-popup-content { 483 | margin: 13px 24px 13px 20px; 484 | line-height: 1.3; 485 | font-size: 13px; 486 | font-size: 1.08333em; 487 | min-height: 1px; 488 | } 489 | .leaflet-popup-content p { 490 | margin: 17px 0; 491 | margin: 1.3em 0; 492 | } 493 | .leaflet-popup-tip-container { 494 | width: 40px; 495 | height: 20px; 496 | position: absolute; 497 | left: 50%; 498 | margin-top: -1px; 499 | margin-left: -20px; 500 | overflow: hidden; 501 | pointer-events: none; 502 | } 503 | .leaflet-popup-tip { 504 | width: 17px; 505 | height: 17px; 506 | padding: 1px; 507 | 508 | margin: -10px auto 0; 509 | pointer-events: auto; 510 | 511 | -webkit-transform: rotate(45deg); 512 | -moz-transform: rotate(45deg); 513 | -ms-transform: rotate(45deg); 514 | transform: rotate(45deg); 515 | } 516 | .leaflet-popup-content-wrapper, 517 | .leaflet-popup-tip { 518 | background: white; 519 | color: #333; 520 | box-shadow: 0 3px 14px rgba(0,0,0,0.4); 521 | } 522 | .leaflet-container a.leaflet-popup-close-button { 523 | position: absolute; 524 | top: 0; 525 | right: 0; 526 | border: none; 527 | text-align: center; 528 | width: 24px; 529 | height: 24px; 530 | font: 16px/24px Tahoma, Verdana, sans-serif; 531 | color: #757575; 532 | text-decoration: none; 533 | background: transparent; 534 | } 535 | .leaflet-container a.leaflet-popup-close-button:hover, 536 | .leaflet-container a.leaflet-popup-close-button:focus { 537 | color: #585858; 538 | } 539 | .leaflet-popup-scrolled { 540 | overflow: auto; 541 | } 542 | 543 | .leaflet-oldie .leaflet-popup-content-wrapper { 544 | -ms-zoom: 1; 545 | } 546 | .leaflet-oldie .leaflet-popup-tip { 547 | width: 24px; 548 | margin: 0 auto; 549 | 550 | -ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)"; 551 | filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678); 552 | } 553 | 554 | .leaflet-oldie .leaflet-control-zoom, 555 | .leaflet-oldie .leaflet-control-layers, 556 | .leaflet-oldie .leaflet-popup-content-wrapper, 557 | .leaflet-oldie .leaflet-popup-tip { 558 | border: 1px solid #999; 559 | } 560 | 561 | 562 | /* div icon */ 563 | 564 | .leaflet-div-icon { 565 | background: #fff; 566 | border: 1px solid #666; 567 | } 568 | 569 | 570 | /* Tooltip */ 571 | /* Base styles for the element that has a tooltip */ 572 | .leaflet-tooltip { 573 | position: absolute; 574 | padding: 6px; 575 | background-color: #fff; 576 | border: 1px solid #fff; 577 | border-radius: 3px; 578 | color: #222; 579 | white-space: nowrap; 580 | -webkit-user-select: none; 581 | -moz-user-select: none; 582 | -ms-user-select: none; 583 | user-select: none; 584 | pointer-events: none; 585 | box-shadow: 0 1px 3px rgba(0,0,0,0.4); 586 | } 587 | .leaflet-tooltip.leaflet-interactive { 588 | cursor: pointer; 589 | pointer-events: auto; 590 | } 591 | .leaflet-tooltip-top:before, 592 | .leaflet-tooltip-bottom:before, 593 | .leaflet-tooltip-left:before, 594 | .leaflet-tooltip-right:before { 595 | position: absolute; 596 | pointer-events: none; 597 | border: 6px solid transparent; 598 | background: transparent; 599 | content: ""; 600 | } 601 | 602 | /* Directions */ 603 | 604 | .leaflet-tooltip-bottom { 605 | margin-top: 6px; 606 | } 607 | .leaflet-tooltip-top { 608 | margin-top: -6px; 609 | } 610 | .leaflet-tooltip-bottom:before, 611 | .leaflet-tooltip-top:before { 612 | left: 50%; 613 | margin-left: -6px; 614 | } 615 | .leaflet-tooltip-top:before { 616 | bottom: 0; 617 | margin-bottom: -12px; 618 | border-top-color: #fff; 619 | } 620 | .leaflet-tooltip-bottom:before { 621 | top: 0; 622 | margin-top: -12px; 623 | margin-left: -6px; 624 | border-bottom-color: #fff; 625 | } 626 | .leaflet-tooltip-left { 627 | margin-left: -6px; 628 | } 629 | .leaflet-tooltip-right { 630 | margin-left: 6px; 631 | } 632 | .leaflet-tooltip-left:before, 633 | .leaflet-tooltip-right:before { 634 | top: 50%; 635 | margin-top: -6px; 636 | } 637 | .leaflet-tooltip-left:before { 638 | right: 0; 639 | margin-right: -12px; 640 | border-left-color: #fff; 641 | } 642 | .leaflet-tooltip-right:before { 643 | left: 0; 644 | margin-left: -12px; 645 | border-right-color: #fff; 646 | } 647 | 648 | /* Printing */ 649 | 650 | @media print { 651 | /* Prevent printers from removing background-images of controls. */ 652 | .leaflet-control { 653 | -webkit-print-color-adjust: exact; 654 | print-color-adjust: exact; 655 | } 656 | } 657 | --------------------------------------------------------------------------------