tileserver-gl-light 5.1.3 → 5.2.0-pre.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -14,3 +14,4 @@ jobs:
14
14
  - uses: fastify/github-action-merge-dependabot@v3
15
15
  with:
16
16
  github-token: ${{ secrets.GITHUB_TOKEN }}
17
+ target: minor
@@ -21,6 +21,11 @@ jobs:
21
21
  with:
22
22
  ref: ${{ github.event.pull_request.head.sha }}
23
23
 
24
+ - name: Install dependencies (Ubuntu) 🚀
25
+ run: >-
26
+ sudo apt-get install -qq libcairo2-dev libjpeg8-dev libpango1.0-dev
27
+ libgif-dev build-essential
28
+
24
29
  - name: Setup node env 📦
25
30
  uses: actions/setup-node@v4
26
31
  with:
package/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # tileserver-gl changelog
2
2
 
3
+ ## 5.2.0-pre.1
4
+ * Use npm packages for public/resources (https://github.com/maptiler/tileserver-gl/pull/1427) by @okimiko
5
+ * use ttf files of googlefonts/opensans (https://github.com/maptiler/tileserver-gl/pull/1447) by @okimiko
6
+ * Limit Elevation Lat/Long Output Length (https://github.com/maptiler/tileserver-gl/pull/1457) by @okimiko
7
+ * Fetch style from url (https://github.com/maptiler/tileserver-gl/pull/1462) by @YoelRidgway
8
+ * fix: memory leak on SIGHUP (https://github.com/maptiler/tileserver-gl/pull/1455) by @okimiko
9
+ * fix: resolves Unimplemented type: 3 error for geojson format #1465
10
+
3
11
  ## 5.1.3
4
12
  * Fix SIGHUP (broken since 5.1.x) (https://github.com/maptiler/tileserver-gl/pull/1452) by @okimiko
5
13
 
package/Dockerfile CHANGED
@@ -5,49 +5,46 @@ ENV \
5
5
  CHOKIDAR_USEPOLLING=1 \
6
6
  CHOKIDAR_INTERVAL=500
7
7
 
8
- RUN set -ex; \
9
- export DEBIAN_FRONTEND=noninteractive; \
10
- groupadd -r node; \
11
- useradd -r -g node node; \
12
- apt-get -qq update; \
13
- apt-get -y --no-install-recommends install \
8
+ SHELL ["/bin/bash", "-o", "pipefail", "-c"]
9
+
10
+ RUN export DEBIAN_FRONTEND=noninteractive && \
11
+ groupadd -r node && \
12
+ useradd -r -g node node && \
13
+ apt-get -qq update && \
14
+ apt-get install -y --no-install-recommends --no-install-suggests \
14
15
  ca-certificates \
15
16
  curl \
16
- gnupg; \
17
- mkdir -p /etc/apt/keyrings; \
18
- curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg; \
19
- echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_22.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list; \
20
- apt-get -qq update; \
21
- apt-get install -y nodejs; \
22
- npm i -g npm@latest; \
23
- apt-get -y remove curl gnupg; \
24
- apt-get -y --purge autoremove; \
25
- apt-get clean; \
26
- rm -rf /var/lib/apt/lists/*;
27
-
28
- EXPOSE 8080
29
-
30
- RUN mkdir -p /data; \
31
- chown node:node /data; \
32
- mkdir -p /usr/src/app;
33
-
17
+ gnupg && \
18
+ mkdir -p /etc/apt/keyrings && \
19
+ curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg && \
20
+ echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_22.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list && \
21
+ apt-get -qq update && \
22
+ apt-get install -y --no-install-recommends --no-install-suggests nodejs && \
23
+ npm i -g npm@latest && \
24
+ apt-get -y remove curl gnupg && \
25
+ apt-get -y --purge autoremove && \
26
+ apt-get clean && \
27
+ rm -rf /var/lib/apt/lists/*
28
+
29
+ WORKDIR /usr/src/app
30
+ COPY . .
31
+
32
+ RUN npm config set maxsockets 1 && \
33
+ npm config set fetch-retries 5 && \
34
+ npm config set fetch-retry-mintimeout 100000 && \
35
+ npm config set fetch-retry-maxtimeout 600000 && \
36
+ npm install --omit=dev && \
37
+ chown -R root:root . && \
38
+ chmod +x ./docker-entrypoint.sh
39
+
40
+ RUN mkdir -p /data && chown node:node /data
34
41
  VOLUME /data
35
-
36
42
  WORKDIR /data
37
43
 
38
- COPY / /usr/src/app
39
-
40
- RUN cd /usr/src/app; \
41
- npm config set maxsockets 1; \
42
- npm config set fetch-retries 5; \
43
- npm config set fetch-retry-mintimeout 100000; \
44
- npm config set fetch-retry-maxtimeout 600000; \
45
- npm install --omit=dev; \
46
- chown -R root:root /usr/src/app; \
47
- chmod +x /usr/src/app/docker-entrypoint.sh;
44
+ EXPOSE 8080
48
45
 
49
46
  USER node:node
50
47
 
51
48
  ENTRYPOINT ["/usr/src/app/docker-entrypoint.sh"]
52
49
 
53
- HEALTHCHECK CMD node /usr/src/app/src/healthcheck.js
50
+ HEALTHCHECK CMD node /usr/src/app/src/healthcheck.js
@@ -0,0 +1,38 @@
1
+ FROM ubuntu:jammy AS builder
2
+
3
+ ENV NODE_ENV="devel"
4
+
5
+ RUN export DEBIAN_FRONTEND=noninteractive && \
6
+ apt-get update && \
7
+ apt-get install -y --no-install-recommends --no-install-suggests \
8
+ build-essential \
9
+ ca-certificates \
10
+ curl \
11
+ gnupg \
12
+ pkg-config \
13
+ xvfb \
14
+ libglfw3-dev \
15
+ libuv1-dev \
16
+ libjpeg-turbo8 \
17
+ libicu70 \
18
+ libcairo2-dev \
19
+ libpango1.0-dev \
20
+ libjpeg-dev \
21
+ libgif-dev \
22
+ librsvg2-dev \
23
+ gir1.2-rsvg-2.0 \
24
+ librsvg2-2 \
25
+ librsvg2-common \
26
+ libcurl4-openssl-dev \
27
+ libpixman-1-dev \
28
+ libpixman-1-0 && \
29
+ mkdir -p /etc/apt/keyrings && \
30
+ curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg && \
31
+ echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_22.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list && \
32
+ apt-get -qq update && \
33
+ apt-get install -y --no-install-recommends --no-install-suggests nodejs && \
34
+ npm i -g npm@latest && \
35
+ apt-get -y remove curl gnupg && \
36
+ apt-get -y --purge autoremove && \
37
+ apt-get clean && \
38
+ rm -rf /var/lib/apt/lists/*
@@ -1,2 +1,7 @@
1
- * Fix SIGHUP (broken since 5.1.x) (https://github.com/maptiler/tileserver-gl/pull/1452) by @okimiko
1
+ * Use npm packages for public/resources (https://github.com/maptiler/tileserver-gl/pull/1427) by @okimiko
2
+ * use ttf files of googlefonts/opensans (https://github.com/maptiler/tileserver-gl/pull/1447) by @okimiko
3
+ * Limit Elevation Lat/Long Output Length (https://github.com/maptiler/tileserver-gl/pull/1457) by @okimiko
4
+ * Fetch style from url (https://github.com/maptiler/tileserver-gl/pull/1462) by @YoelRidgway
5
+ * fix: memory leak on SIGHUP (https://github.com/maptiler/tileserver-gl/pull/1455) by @okimiko
6
+ * fix: resolves Unimplemented type: 3 error for geojson format #1465
2
7
 
package/docs/config.rst CHANGED
@@ -57,6 +57,9 @@ Example:
57
57
  "tilejson": {
58
58
  "format": "webp"
59
59
  }
60
+ },
61
+ "remote": {
62
+ "style": "https://demotiles.maplibre.org/style.json"
60
63
  }
61
64
  },
62
65
  "data": {
@@ -209,7 +212,7 @@ Not used by default.
209
212
 
210
213
  Each item in this object defines one style (map). It can have the following options:
211
214
 
212
- * ``style`` -- name of the style json file [required]
215
+ * ``style`` -- name of the style json file or url of a remote hosted style [required]
213
216
  * ``serve_rendered`` -- whether to render the raster tiles for this style or not
214
217
  * ``serve_data`` -- whether to allow access to the original tiles, sprites and required glyphs
215
218
  * ``tilejson`` -- properties to add to the TileJSON created for the raster data
package/package.json CHANGED
@@ -1,11 +1,16 @@
1
1
  {
2
2
  "name": "tileserver-gl-light",
3
- "version": "5.1.3",
3
+ "version": "5.2.0-pre.1",
4
4
  "description": "Map tile server for JSON GL styles - serving vector tiles",
5
5
  "main": "src/main.js",
6
6
  "bin": "src/main.js",
7
7
  "type": "module",
8
8
  "scripts": {
9
+ "copy:maplibre": "copyfiles -EVf node_modules/maplibre-gl/dist/maplibre-gl.js node_modules/maplibre-gl/dist/maplibre-gl.js.map node_modules/maplibre-gl/dist/maplibre-gl.css public/resources/",
10
+ "copy:maplibre-inspect": "copyfiles -EVf node_modules/@maplibre/maplibre-gl-inspect/dist/maplibre-gl-inspect.js node_modules/@maplibre/maplibre-gl-inspect/dist/maplibre-gl-inspect.js.map node_modules/@maplibre/maplibre-gl-inspect/dist/maplibre-gl-inspect.css public/resources/",
11
+ "copy:mapbox-rtl-text": "copyfiles -EVf node_modules/@mapbox/mapbox-gl-rtl-text/dist/mapbox-gl-rtl-text.js public/resources/",
12
+ "copy:leaflet": "copyfiles -EVf node_modules/leaflet/dist/leaflet.js node_modules/leaflet/dist/leaflet.js.map node_modules/leaflet/dist/leaflet.css node_modules/leaflet/dist/leaflet-hash.js public/resources/",
13
+ "copy:leaflet-hash": "copyfiles -EVf node_modules/leaflet-hash/leaflet-hash.js public/resources/",
9
14
  "test": "mocha test/**.js --timeout 10000 --exit",
10
15
  "test-docker": "xvfb-run npm test",
11
16
  "lint:yml": "yamllint --schema=CORE_SCHEMA *.{yml,yaml}",
@@ -19,14 +24,16 @@
19
24
  },
20
25
  "dependencies": {
21
26
  "@jsse/pbfont": "^0.2.2",
27
+ "@mapbox/mapbox-gl-rtl-text": "0.3.0",
22
28
  "@mapbox/mbtiles": "0.12.1",
23
29
  "@mapbox/polyline": "^1.2.1",
24
30
  "@mapbox/sphericalmercator": "1.2.0",
25
31
  "@mapbox/vector-tile": "2.0.3",
32
+ "@maplibre/maplibre-gl-inspect": "1.7.0",
26
33
  "@maplibre/maplibre-gl-style-spec": "20.3.1",
27
34
  "@sindresorhus/fnv1a": "3.1.0",
28
35
  "advanced-pool": "0.3.3",
29
- "axios": "^1.7.7",
36
+ "axios": "^1.8.2",
30
37
  "chokidar": "3.6.0",
31
38
  "clone": "2.1.2",
32
39
  "color": "4.2.3",
@@ -35,6 +42,9 @@
35
42
  "express": "5.0.1",
36
43
  "handlebars": "4.7.8",
37
44
  "http-shutdown": "1.2.2",
45
+ "leaflet": "1.9.4",
46
+ "leaflet-hash": "0.2.1",
47
+ "maplibre-gl": "4.7.1",
38
48
  "morgan": "1.10.0",
39
49
  "pbf": "4.0.1",
40
50
  "pmtiles": "3.0.7",
@@ -18,7 +18,7 @@ class ElevationInfoControl {
18
18
 
19
19
  map.on('click', (e) => {
20
20
  var url = this.url;
21
- var coord = {"z": Math.floor(map.getZoom()), "x": e.lngLat["lng"], "y": e.lngLat["lat"]};
21
+ var coord = {"z": Math.floor(map.getZoom()), "x": e.lngLat["lng"].toFixed(7), "y": e.lngLat["lat"].toFixed(7)};
22
22
  for(var key in coord) {
23
23
  url = url.replace(new RegExp('{'+ key +'}','g'), coord[key]);
24
24
  }
package/src/serve_data.js CHANGED
@@ -21,6 +21,7 @@ import { openMbTilesWrapper } from './mbtiles_wrapper.js';
21
21
 
22
22
  import fs from 'node:fs';
23
23
  import { fileURLToPath } from 'url';
24
+
24
25
  const packageJson = JSON.parse(
25
26
  fs.readFileSync(
26
27
  path.dirname(fileURLToPath(import.meta.url)) + '/../package.json',
@@ -113,12 +114,13 @@ export const serve_data = {
113
114
  let headers = fetchTile.headers;
114
115
  let isGzipped = data.slice(0, 2).indexOf(Buffer.from([0x1f, 0x8b])) === 0;
115
116
 
117
+ if (isGzipped) {
118
+ data = await gunzipP(data);
119
+ isGzipped = false;
120
+ }
121
+
116
122
  if (tileJSONFormat === 'pbf') {
117
123
  if (options.dataDecoratorFunc) {
118
- if (isGzipped) {
119
- data = await gunzipP(data);
120
- isGzipped = false;
121
- }
122
124
  data = options.dataDecoratorFunc(
123
125
  req.params.id,
124
126
  'data',
@@ -259,8 +261,8 @@ export const serve_data = {
259
261
 
260
262
  let data = fetchTile.data;
261
263
  var param = {
262
- long: bbox[0],
263
- lat: bbox[1],
264
+ long: bbox[0].toFixed(7),
265
+ lat: bbox[1].toFixed(7),
264
266
  encoding,
265
267
  format,
266
268
  tile_size: TILE_SIZE,
@@ -6,6 +6,7 @@ export const serve_rendered = {
6
6
  init: (options, repo, programOpts) => {},
7
7
  add: (options, repo, params, id, programOpts, dataResolver) => {},
8
8
  remove: (repo, id) => {},
9
+ clear: (repo) => {},
9
10
  getTerrainElevation: (data, param) => {
10
11
  param['elevation'] = 'not supported in light';
11
12
  return param;
@@ -15,7 +15,6 @@ import '@maplibre/maplibre-gl-native';
15
15
  import advancedPool from 'advanced-pool';
16
16
  import path from 'path';
17
17
  import url from 'url';
18
- import util from 'util';
19
18
  import sharp from 'sharp';
20
19
  import clone from 'clone';
21
20
  import Color from 'color';
@@ -1028,10 +1027,19 @@ export const serve_rendered = {
1028
1027
  * @param {object} params Parameters object.
1029
1028
  * @param {string} id ID of the item.
1030
1029
  * @param {object} programOpts - An object containing the program options
1030
+ * @param {object} style pre-fetched/read StyleJSON object.
1031
1031
  * @param {Function} dataResolver Function to resolve data.
1032
1032
  * @returns {Promise<void>}
1033
1033
  */
1034
- add: async function (options, repo, params, id, programOpts, dataResolver) {
1034
+ add: async function (
1035
+ options,
1036
+ repo,
1037
+ params,
1038
+ id,
1039
+ programOpts,
1040
+ style,
1041
+ dataResolver,
1042
+ ) {
1035
1043
  const map = {
1036
1044
  renderers: [],
1037
1045
  renderersStatic: [],
@@ -1041,7 +1049,7 @@ export const serve_rendered = {
1041
1049
 
1042
1050
  const { publicUrl, verbose } = programOpts;
1043
1051
 
1044
- let styleJSON;
1052
+ const styleJSON = clone(style);
1045
1053
  /**
1046
1054
  * Creates a pool of renderers.
1047
1055
  * @param {number} ratio Pixel ratio
@@ -1230,12 +1238,6 @@ export const serve_rendered = {
1230
1238
 
1231
1239
  const styleFile = params.style;
1232
1240
  const styleJSONPath = path.resolve(options.paths.styles, styleFile);
1233
- try {
1234
- styleJSON = JSON.parse(await fsp.readFile(styleJSONPath));
1235
- } catch (e) {
1236
- console.log('Error parsing style file');
1237
- return false;
1238
- }
1239
1241
 
1240
1242
  if (styleJSON.sprite) {
1241
1243
  if (!Array.isArray(styleJSON.sprite)) {
@@ -1458,7 +1460,25 @@ export const serve_rendered = {
1458
1460
  }
1459
1461
  delete repo[id];
1460
1462
  },
1461
-
1463
+ /**
1464
+ * Removes all items from the repository.
1465
+ * @param {object} repo Repository object.
1466
+ * @returns {void}
1467
+ */
1468
+ clear: function (repo) {
1469
+ Object.keys(repo).forEach((id) => {
1470
+ const item = repo[id];
1471
+ if (item) {
1472
+ item.map.renderers.forEach((pool) => {
1473
+ pool.close();
1474
+ });
1475
+ item.map.renderersStatic.forEach((pool) => {
1476
+ pool.close();
1477
+ });
1478
+ }
1479
+ delete repo[id];
1480
+ });
1481
+ },
1462
1482
  /**
1463
1483
  * Get the elevation of terrain tile data by rendering it to a canvas image
1464
1484
  * @param {object} data The background color (or empty string for transparent).
@@ -196,9 +196,10 @@ export const serve_style = {
196
196
  * @param {object} params Parameters object containing style path
197
197
  * @param {string} id ID of the style.
198
198
  * @param {object} programOpts - An object containing the program options
199
+ * @param {object} style pre-fetched/read StyleJSON object.
199
200
  * @param {Function} reportTiles Function for reporting tile sources.
200
201
  * @param {Function} reportFont Function for reporting font usage
201
- * @returns {boolean} true if add is succesful
202
+ * @returns {boolean} true if add is successful
202
203
  */
203
204
  add: function (
204
205
  options,
@@ -206,21 +207,14 @@ export const serve_style = {
206
207
  params,
207
208
  id,
208
209
  programOpts,
210
+ style,
209
211
  reportTiles,
210
212
  reportFont,
211
213
  ) {
212
214
  const { publicUrl } = programOpts;
213
215
  const styleFile = path.resolve(options.paths.styles, params.style);
216
+ const styleJSON = clone(style);
214
217
 
215
- let styleFileData;
216
- try {
217
- styleFileData = fs.readFileSync(styleFile); // TODO: could be made async if this function was
218
- } catch (e) {
219
- console.log(`Error reading style file "${params.style}"`);
220
- return false;
221
- }
222
-
223
- const styleJSON = JSON.parse(styleFileData);
224
218
  const validationErrors = validateStyleMin(styleJSON);
225
219
  if (validationErrors.length > 0) {
226
220
  console.log(`The file "${params.style}" is not a valid style file:`);
package/src/server.js CHANGED
@@ -178,10 +178,29 @@ async function start(opts) {
178
178
  * @param {object} item - The style configuration object.
179
179
  * @param {boolean} allowMoreData - Whether to allow adding more data sources.
180
180
  * @param {boolean} reportFonts - Whether to report fonts.
181
- * @returns {void}
181
+ * @returns {Promise<void>}
182
182
  */
183
- function addStyle(id, item, allowMoreData, reportFonts) {
183
+ async function addStyle(id, item, allowMoreData, reportFonts) {
184
184
  let success = true;
185
+
186
+ let styleJSON;
187
+ try {
188
+ if (isValidHttpUrl(item.style)) {
189
+ const res = await fetch(item.style);
190
+ if (!res.ok) {
191
+ throw new Error(`fetch error ${res.status}`);
192
+ }
193
+ styleJSON = await res.json();
194
+ } else {
195
+ const styleFile = path.resolve(options.paths.styles, item.style);
196
+ const styleFileData = await fs.promises.readFile(styleFile);
197
+ styleJSON = JSON.parse(styleFileData);
198
+ }
199
+ } catch (e) {
200
+ console.log(`Error getting style file "${item.style}"`);
201
+ return false;
202
+ }
203
+
185
204
  if (item.serve_data !== false) {
186
205
  success = serve_style.add(
187
206
  options,
@@ -189,6 +208,7 @@ async function start(opts) {
189
208
  item,
190
209
  id,
191
210
  opts,
211
+ styleJSON,
192
212
  (styleSourceId, protocol) => {
193
213
  let dataItemId;
194
214
  for (const id of Object.keys(data)) {
@@ -246,6 +266,7 @@ async function start(opts) {
246
266
  item,
247
267
  id,
248
268
  opts,
269
+ styleJSON,
249
270
  function dataResolver(styleSourceId) {
250
271
  let fileType;
251
272
  let inputFile;
@@ -271,6 +292,7 @@ async function start(opts) {
271
292
  item.serve_rendered = false;
272
293
  }
273
294
  }
295
+ return success;
274
296
  }
275
297
 
276
298
  for (const id of Object.keys(config.styles || {})) {
@@ -279,8 +301,7 @@ async function start(opts) {
279
301
  console.log(`Missing "style" property for ${id}`);
280
302
  continue;
281
303
  }
282
-
283
- addStyle(id, item, true, true);
304
+ startupPromises.push(addStyle(id, item, true, true));
284
305
  }
285
306
  startupPromises.push(
286
307
  serve_font(options, serving.fonts, opts).then((sub) => {
@@ -743,6 +764,7 @@ async function start(opts) {
743
764
  app,
744
765
  server,
745
766
  startupPromise,
767
+ serving,
746
768
  };
747
769
  }
748
770
  /**
@@ -777,8 +799,13 @@ export async function server(opts) {
777
799
 
778
800
  running.server.shutdown(async () => {
779
801
  const restarted = await start(opts);
802
+ if (!isLight) {
803
+ serve_rendered.clear(running.serving.rendered);
804
+ }
780
805
  running.server = restarted.server;
781
806
  running.app = restarted.app;
807
+ running.startupPromise = restarted.startupPromise;
808
+ running.serving = restarted.serving;
782
809
  });
783
810
  });
784
811
  return running;
@@ -1,162 +0,0 @@
1
- (function(window) {
2
- var HAS_HASHCHANGE = (function() {
3
- var doc_mode = window.documentMode;
4
- return ('onhashchange' in window) &&
5
- (doc_mode === undefined || doc_mode > 7);
6
- })();
7
-
8
- L.Hash = function(map) {
9
- this.onHashChange = L.Util.bind(this.onHashChange, this);
10
-
11
- if (map) {
12
- this.init(map);
13
- }
14
- };
15
-
16
- L.Hash.parseHash = function(hash) {
17
- if(hash.indexOf('#') === 0) {
18
- hash = hash.substr(1);
19
- }
20
- var args = hash.split("/");
21
- if (args.length == 3) {
22
- var zoom = parseInt(args[0], 10),
23
- lat = parseFloat(args[1]),
24
- lon = parseFloat(args[2]);
25
- if (isNaN(zoom) || isNaN(lat) || isNaN(lon)) {
26
- return false;
27
- } else {
28
- return {
29
- center: new L.LatLng(lat, lon),
30
- zoom: zoom
31
- };
32
- }
33
- } else {
34
- return false;
35
- }
36
- };
37
-
38
- L.Hash.formatHash = function(map) {
39
- var center = map.getCenter(),
40
- zoom = map.getZoom(),
41
- precision = Math.max(0, Math.ceil(Math.log(zoom) / Math.LN2));
42
-
43
- return "#" + [zoom,
44
- center.lat.toFixed(precision),
45
- center.lng.toFixed(precision)
46
- ].join("/");
47
- },
48
-
49
- L.Hash.prototype = {
50
- map: null,
51
- lastHash: null,
52
-
53
- parseHash: L.Hash.parseHash,
54
- formatHash: L.Hash.formatHash,
55
-
56
- init: function(map) {
57
- this.map = map;
58
-
59
- // reset the hash
60
- this.lastHash = null;
61
- this.onHashChange();
62
-
63
- if (!this.isListening) {
64
- this.startListening();
65
- }
66
- },
67
-
68
- removeFrom: function(map) {
69
- if (this.changeTimeout) {
70
- clearTimeout(this.changeTimeout);
71
- }
72
-
73
- if (this.isListening) {
74
- this.stopListening();
75
- }
76
-
77
- this.map = null;
78
- },
79
-
80
- onMapMove: function() {
81
- // bail if we're moving the map (updating from a hash),
82
- // or if the map is not yet loaded
83
-
84
- if (this.movingMap || !this.map._loaded) {
85
- return false;
86
- }
87
-
88
- var hash = this.formatHash(this.map);
89
- if (this.lastHash != hash) {
90
- location.replace(hash);
91
- this.lastHash = hash;
92
- }
93
- },
94
-
95
- movingMap: false,
96
- update: function() {
97
- var hash = location.hash;
98
- if (hash === this.lastHash) {
99
- return;
100
- }
101
- var parsed = this.parseHash(hash);
102
- if (parsed) {
103
- this.movingMap = true;
104
-
105
- this.map.setView(parsed.center, parsed.zoom);
106
-
107
- this.movingMap = false;
108
- } else {
109
- this.onMapMove(this.map);
110
- }
111
- },
112
-
113
- // defer hash change updates every 100ms
114
- changeDefer: 100,
115
- changeTimeout: null,
116
- onHashChange: function() {
117
- // throttle calls to update() so that they only happen every
118
- // `changeDefer` ms
119
- if (!this.changeTimeout) {
120
- var that = this;
121
- this.changeTimeout = setTimeout(function() {
122
- that.update();
123
- that.changeTimeout = null;
124
- }, this.changeDefer);
125
- }
126
- },
127
-
128
- isListening: false,
129
- hashChangeInterval: null,
130
- startListening: function() {
131
- this.map.on("moveend", this.onMapMove, this);
132
-
133
- if (HAS_HASHCHANGE) {
134
- L.DomEvent.addListener(window, "hashchange", this.onHashChange);
135
- } else {
136
- clearInterval(this.hashChangeInterval);
137
- this.hashChangeInterval = setInterval(this.onHashChange, 50);
138
- }
139
- this.isListening = true;
140
- },
141
-
142
- stopListening: function() {
143
- this.map.off("moveend", this.onMapMove, this);
144
-
145
- if (HAS_HASHCHANGE) {
146
- L.DomEvent.removeListener(window, "hashchange", this.onHashChange);
147
- } else {
148
- clearInterval(this.hashChangeInterval);
149
- }
150
- this.isListening = false;
151
- }
152
- };
153
- L.hash = function(map) {
154
- return new L.Hash(map);
155
- };
156
- L.Map.prototype.addHash = function() {
157
- this._hash = L.hash(this);
158
- };
159
- L.Map.prototype.removeHash = function() {
160
- this._hash.removeFrom();
161
- };
162
- })(window);