noruka-google-vector-tiles
Version:
A vector map tiler for google map
484 lines (428 loc) • 16.5 kB
JavaScript
/*
* Created by Jes�s Barrio on 04/2021
*/
import Pbf from '!../lib/protobuf/Protobuf'
import { MERCATOR } from '../lib/mercator'
import { VectorTile } from '../lib/vectortiles'
import { MVTLayer } from './MVTLayer'
class MVTSource {
constructor (map, options) {
var self = this;
// set id for further reference
this.id = options.id
this.map = map;
this._url = options.url || ""; //Url TO Vector Tile Source,
this._sourceMaxZoom = options.sourceMaxZoom || false; // Source maxzoom to enable overzoom
this._debug = options.debug || false; // Draw tiles lines and ids
this.getIDForLayerFeature = options.getIDForLayerFeature || function (feature) {
return feature.properties.id || feature.properties.Id || feature.properties.ID;
};
this._visibleLayers = options.visibleLayers || false; // List of visible layers
this._xhrHeaders = options.xhrHeaders || {}; // Headers added to every url request
this._clickableLayers = options.clickableLayers || false; // List of layers that are clickable
this._filter = options.filter || false; // Filter features
this._cache = options.cache || false; // Load tiles in cache to avoid duplicated requests
this._tileSize = options.tileSize || 256; // Default tile size
this.tileSize = new google.maps.Size(this._tileSize, this._tileSize);
this.style = options.style || function (feature) {
var style = {};
switch (feature.type) {
case 1: //'Point'
style.fillStyle = 'rgba(49,79,79,1)';
style.radius = 5;
style.selected = {
fillStyle: 'rgba(255,255,0,0.5)',
radius: 6
}
break;
case 2: //'LineString'
style.strokeStyle = 'rgba(136, 86, 167, 1)';
style.lineWidth = 3;
style.selected = {
strokeStyle: 'rgba(255,25,0,0.5)',
lineWidth: 4
}
break;
case 3: //'Polygon'
style.fillStyle = 'rgba(188, 189, 220, 0.5)';
style.strokeStyle = 'rgba(136, 86, 167, 1)';
style.lineWidth = 1;
style.selected = {
fillStyle: 'rgba(255,140,0,0.3)',
strokeStyle: 'rgba(255,140,0,1)',
lineWidth: 2
}
break;
}
return style;
};
this.mVTLayers = []; //Keep a list of the layers contained in the PBFs
this._tilesDrawn = []; // List of tiles drawn (when cache enabled)
this._visibleTiles = []; // tiles currently in the viewport
this._selectedFeatures = []; // list of selected features
if (options.selectedFeatures) {
this.setSelectedFeatures(options.selectedFeatures);
}
this.map.addListener("zoom_changed", () => {
self._zoomChanged();
});
}
getTile (coord, zoom, ownerDocument) {
var tileContext = this.drawTile(coord, zoom, ownerDocument);
this._setVisibleTile(tileContext);
return tileContext.canvas;
}
releaseTile (canvas) {
//this._deleteVisibleTile(canvas.id);
}
_zoomChanged () {
this._resetVisibleTiles();
if (!this._cache) {
this._resetMVTLayers();
}
}
_resetMVTLayers () {
this.mVTLayers = [];
}
_deleteVisibleTile (id) {
delete this._visibleTiles[id];
}
_resetVisibleTiles () {
this._visibleTiles = [];
}
_setVisibleTile (tileContext) {
this._visibleTiles[tileContext.id] = tileContext;
}
drawTile (coord, zoom, ownerDocument) {
var id = this.getTileId(zoom, coord.x, coord.y);
var tileContext = this._tilesDrawn[id];
if (tileContext) {
return tileContext;
}
tileContext = this._createTileContext(coord, zoom, ownerDocument);
this._xhrRequest(tileContext);
return tileContext;
}
_createTileContext (coord, zoom, ownerDocument) {
var id = this.getTileId(zoom, coord.x, coord.y);
var canvas = this._createCanvas(ownerDocument, id);
var parentId = this._getParentId(id);
return {
id: id,
canvas: canvas,
zoom: zoom,
tileSize: this._tileSize,
parentId: parentId
};
}
_getParentId (id) {
var parentId = false;
if (this._sourceMaxZoom) {
var tile = this.getTileObject(id);
if (tile.zoom > this._sourceMaxZoom) {
var zoomDistance = tile.zoom - this._sourceMaxZoom;
var zoom = tile.zoom - zoomDistance;
var x = tile.x >> zoomDistance;
var y = tile.y >> zoomDistance;
parentId = this.getTileId(zoom, x, y);
}
}
return parentId;
}
_createCanvas (ownerDocument, id) {
const canvas = ownerDocument.createElement("canvas");
canvas.width = this._tileSize;
canvas.height = this._tileSize;
canvas.id = id;
return canvas;
}
getTileId (zoom, x, y) {
return [zoom, x, y].join(":");
}
getTileObject (id) {
var values = id.split(":");
return {
zoom: values[0],
x: values[1],
y: values[2]
}
}
_xhrRequest (tileContext) {
var self = this;
var id = tileContext.parentId || tileContext.id;
var tile = this.getTileObject(id);
var src = this._url
.replace("{z}", tile.zoom)
.replace("{x}", tile.x)
.replace("{y}", tile.y);
var xmlHttpRequest = new XMLHttpRequest();
xmlHttpRequest.onload = function () {
if (xmlHttpRequest.status == "200" && xmlHttpRequest.response) {
return self._xhrResponseOk(tileContext, xmlHttpRequest.response)
}
self._drawDebugInfo(tileContext);
};
xmlHttpRequest.open('GET', src, true);
for (var header in this._xhrHeaders) {
xmlHttpRequest.setRequestHeader(header, this._xhrHeaders[header]);
}
xmlHttpRequest.responseType = 'arraybuffer';
xmlHttpRequest.send();
}
_xhrResponseOk (tileContext, response) {
if (this.map.getZoom() != tileContext.zoom) {
return;
}
var uint8Array = new Uint8Array(response);
var pbf = new Pbf(uint8Array);
var vectorTile = new VectorTile(pbf);
this._drawVectorTile(vectorTile, tileContext);
}
_setTileDrawn (tileContext) {
if (!this._cache) return;
this._tilesDrawn[tileContext.id] = tileContext;
}
deleteTileDrawn (id) {
delete this._tilesDrawn[id];
}
_resetTileDrawn () {
this._tilesDrawn = [];
}
_drawVectorTile (vectorTile, tileContext) {
if (this._visibleLayers) {
for (var i = 0, length = this._visibleLayers.length; i < length; i++) {
var key = this._visibleLayers[i];
if (vectorTile.layers[key]) {
var vectorTileLayer = vectorTile.layers[key];
this._drawVectorTileLayer(vectorTileLayer, key, tileContext);
}
}
} else {
for (var key in vectorTile.layers) {
var vectorTileLayer = vectorTile.layers[key];
this._drawVectorTileLayer(vectorTileLayer, key, tileContext);
}
}
tileContext.vectorTile = vectorTile;
this._drawDebugInfo(tileContext);
this._setTileDrawn(tileContext);
}
_drawVectorTileLayer (vectorTileLayer, key, tileContext) {
if (!this.mVTLayers[key]) {
this.mVTLayers[key] = this._createMVTLayer(key);
}
var mVTLayer = this.mVTLayers[key];
mVTLayer.parseVectorTileFeatures(this, vectorTileLayer.parsedFeatures, tileContext);
}
_createMVTLayer (key) {
var options = {
getIDForLayerFeature: this.getIDForLayerFeature,
filter: this._filter,
style: this.style,
name: key
};
return new MVTLayer(options);
}
_drawDebugInfo (tileContext) {
if (!this._debug) return;
var tile = this.getTileObject(tileContext.id)
var width = this._tileSize;
var height = this._tileSize;
var context2d = tileContext.canvas.getContext('2d');
context2d.strokeStyle = '#000000';
context2d.fillStyle = '#FFFF00';
context2d.strokeRect(0, 0, width, height);
context2d.font = "12px Arial";
context2d.fillRect(0, 0, 5, 5);
context2d.fillRect(0, height - 5, 5, 5);
context2d.fillRect(width - 5, 0, 5, 5);
context2d.fillRect(width - 5, height - 5, 5, 5);
context2d.fillRect(width / 2 - 5, height / 2 - 5, 10, 10);
context2d.strokeText(tileContext.zoom + ' ' + tile.x + ' ' + tile.y, width / 2 - 30, height / 2 - 10);
}
onClick (event, callbackFunction, options) {
this._multipleSelection = (options && options.multipleSelection) || false;
options = this._getMouseOptions(options, false);
this._mouseEvent(event, callbackFunction, options);
}
onMouseHover (event, callbackFunction, options) {
this._multipleSelection = false;
options = this._getMouseOptions(options, true);
this._mouseEvent(event, callbackFunction, options);
}
_getMouseOptions (options, mouseHover) {
return {
mouseHover: mouseHover,
setSelected: options.setSelected || false,
toggleSelection: (options.toggleSelection === undefined || options.toggleSelection),
limitToFirstVisibleLayer: options.limitToFirstVisibleLayer || false
}
}
_mouseEvent (event, callbackFunction, options) {
if (!event.pixel || !event.latLng) return;
callbackFunction = callbackFunction || function () { };
var limitToFirstVisibleLayer = options.limitToFirstVisibleLayer || false;
var zoom = this.map.getZoom();
var tile = MERCATOR.getTileAtLatLng(event.latLng, zoom);
var id = this.getTileId(tile.z, tile.x, tile.y);
var tileContext = this._visibleTiles[id];
if (!tileContext) {
return;
}
event.tileContext = tileContext;
event.tilePoint = MERCATOR.fromLatLngToTilePoint(this.map, event);
var clickableLayers = this._clickableLayers || Object.keys(this.mVTLayers) || [];
for (var i = clickableLayers.length - 1; i >= 0; i--) {
var key = clickableLayers[i];
var layer = this.mVTLayers[key];
if (layer) {
var event = layer.handleClickEvent(event);
this._mouseSelectedFeature(event, callbackFunction, options);
if (limitToFirstVisibleLayer && event.feature) {
break;
}
}
}
}
_mouseSelectedFeature (event, callbackFunction, options) {
if (options.setSelected) {
var feature = event.feature;
if (feature) {
if (options.mouseHover) {
if (!feature.selected) {
feature.select();
}
}
else {
if (options.toggleSelection) {
feature.toggle();
}
else {
if (!feature.selected) {
feature.select();
}
}
}
}
else {
if (options.mouseHover) {
this.deselectAllFeatures();
}
}
}
callbackFunction(event);
}
deselectAllFeatures () {
var zoom = this.map.getZoom();
var tilesToRedraw = [];
for (var featureId in this._selectedFeatures) {
var mVTFeature = this._selectedFeatures[featureId];
if (!mVTFeature) continue;
mVTFeature.setSelected(false);
var tiles = mVTFeature.getTiles();
for (var id in tiles) {
this.deleteTileDrawn(id);
var idObject = this.getTileObject(id);
if (idObject.zoom == zoom) {
tilesToRedraw[id] = true;
}
}
}
this.redrawTiles(tilesToRedraw);
this._selectedFeatures = [];
}
featureSelected (mVTFeature) {
if (!this._multipleSelection) {
this.deselectAllFeatures();
}
this._selectedFeatures[mVTFeature.featureId] = mVTFeature;
}
featureDeselected (mvtFeature) {
delete this._selectedFeatures[mvtFeature.featureId];
}
setSelectedFeatures (featuresIds) {
if (featuresIds.length > 1) {
this._multipleSelection = true;
}
this.deselectAllFeatures();
for (var i = 0, length = featuresIds.length; i < length; i++) {
var featureId = featuresIds[i];
this._selectedFeatures[featureId] = false;
for (var key in this.mVTLayers) {
this.mVTLayers[key].setSelected(featureId);
}
}
}
isFeatureSelected (featureId) {
return this._selectedFeatures[featureId] != undefined;
}
getSelectedFeatures () {
var selectedFeatures = [];
for (var featureId in this._selectedFeatures) {
selectedFeatures.push(this._selectedFeatures[featureId]);
}
return selectedFeatures;
}
setFilter (filter, redrawTiles) {
redrawTiles = (redrawTiles === undefined || redrawTiles);
this._filter = filter;
for (var key in this.mVTLayers) {
this.mVTLayers[key].setFilter(filter);
}
if (redrawTiles) {
this.redrawAllTiles();
}
}
setStyle (style, redrawTiles) {
redrawTiles = (redrawTiles === undefined || redrawTiles);
this.style = style
for (var key in this.mVTLayers) {
this.mVTLayers[key].setStyle(style);
}
if (redrawTiles) {
this.redrawAllTiles();
}
}
setVisibleLayers (visibleLayers, redrawTiles) {
redrawTiles = (redrawTiles === undefined || redrawTiles);
this._visibleLayers = visibleLayers;
if (redrawTiles) {
this.redrawAllTiles();
}
}
getVisibleLayers () {
return this._visibleLayers;
}
setClickableLayers (clickableLayers) {
this._clickableLayers = clickableLayers;
}
redrawAllTiles () {
this._resetTileDrawn();
this.redrawTiles(this._visibleTiles);
}
redrawTiles (tiles) {
for (var id in tiles) {
this.redrawTile(id);
}
}
redrawTile (id) {
this.deleteTileDrawn(id);
var tileContext = this._visibleTiles[id];
if (!tileContext || !tileContext.vectorTile) return;
this.clearTile(tileContext.canvas);
this._drawVectorTile(tileContext.vectorTile, tileContext);
}
clearTile (canvas) {
var context = canvas.getContext('2d');
context.clearRect(0, 0, canvas.width, canvas.height);
}
setUrl (url, redrawTiles) {
redrawTiles = (redrawTiles === undefined || redrawTiles);
this._url = url;
this._resetMVTLayers();
if (redrawTiles) {
this.redrawAllTiles();
}
}
}
export { MVTSource }