Skip to content

Commit

Permalink
Add GeoJSON option to display local data.
Browse files Browse the repository at this point in the history
  • Loading branch information
francois2metz committed Dec 9, 2021
1 parent 3db4d8d commit bdf08d0
Show file tree
Hide file tree
Showing 3 changed files with 99 additions and 23 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ Load the indoor= source and layers in your map.
* `options` **[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** (optional, default `{}`)

* `options.url` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)?** Override the default tiles URL (<https://tiles.indoorequal.org/>).
* `options.geojson` **[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)?** GeoJSON data with with key as layer name and value with geojson features
* `options.apiKey` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)?** The API key if you use the default tile URL (get your free key at [indoorequal.com](https://indoorequal.com)).
* `options.layers` **[array](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array)?** The layers to be used to style indoor= tiles. Take a look a the [layers.js file](https://github.com/indoorequal/mapbox-gl-indoorequal/blob/master/src/layers.js) file and the [vector schema](https://indoorequal.com/schema)
* `options.heatmap` **[boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** Should the heatmap layer be visible at start (true : visible, false : hidden). Defaults to true/visible.
Expand Down
106 changes: 83 additions & 23 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,80 @@ import LevelControl from './level_control';
import { layers } from './layers';
import loadSprite from './sprite';

const SOURCE_ID = 'indoorequal';
class GeoJSONSource {
constructor(map, options = {}) {
const defaultOpts = { layers, geojson: {} };
const opts = { ...defaultOpts, ...options };
this.map = map;
this.geojson = opts.geojson;
this.layers = opts.layers;
this.baseSourceId = 'indoorequal';
this.sourceId = `${this.baseSourceId}_area`;
}

addSource() {
Object.keys(this.geojson).forEach((layerName) => {
this.map.addSource(`${this.baseSourceId}_${layerName}`, {
type: 'geojson',
data: this.geojson[layerName]
});
});
}

addLayers() {
const sourceLayers = Object.keys(this.geojson);
const layers = this.layers;
this.layers = layers.filter((layer) => {
return sourceLayers.includes(layer['source-layer']);
});
this.layers.forEach((layer) => {
this.map.addLayer({
source: `${this.baseSourceId}_${layer['source-layer']}`,
...layer,
'source-layer': ''
});
});
}
}

class VectorTileSource {
constructor(map, options = {}) {
const defaultOpts = { url: 'https://tiles.indoorequal.org/', layers };
const opts = { ...defaultOpts, ...options };
if (opts.url === defaultOpts.url && !opts.apiKey) {
throw 'You must register your apiKey at https://indoorequal.com before and set it as apiKey param.';
}
this.map = map;
this.url = opts.url;
this.apiKey = opts.apiKey;
this.layers = opts.layers;
this.sourceId = 'indoorequal';
}

addSource() {
const queryParams = this.apiKey ? `?key=${this.apiKey}` : '';
this.map.addSource(this.sourceId, {
type: 'vector',
url: `${this.url}${queryParams}`
});
}

addLayers() {
this.layers.forEach((layer) => {
this.map.addLayer({
source: this.sourceId,
...layer
});
});
}
}

/**
* Load the indoor= source and layers in your map.
* @param {object} map the mapbox-gl instance of the map
* @param {object} options
* @param {string} [options.url] Override the default tiles URL (https://tiles.indoorequal.org/).
* @param {object} [options.geojson] GeoJSON data with with key as layer name and value with geojson features
* @param {string} [options.apiKey] The API key if you use the default tile URL (get your free key at [indoorequal.com](https://indoorequal.com)).
* @param {array} [options.layers] The layers to be used to style indoor= tiles. Take a look a the [layers.js file](https://github.com/indoorequal/mapbox-gl-indoorequal/blob/master/src/layers.js) file and the [vector schema](https://indoorequal.com/schema)
* @param {boolean} [options.heatmap] Should the heatmap layer be visible at start (true : visible, false : hidden). Defaults to true/visible.
Expand All @@ -23,15 +90,11 @@ const SOURCE_ID = 'indoorequal';
*/
export default class IndoorEqual {
constructor(map, options = {}) {
const defaultOpts = { url: 'https://tiles.indoorequal.org/', layers, heatmap: true };
const SourceKlass = options.geojson ? GeoJSONSource : VectorTileSource;
const defaultOpts = { heatmap: true };
const opts = { ...defaultOpts, ...options };
if (opts.url === defaultOpts.url && !opts.apiKey) {
throw 'You must register your apiKey at https://indoorequal.com before and set it as apiKey param.';
}
this.source = new SourceKlass(map, options);
this.map = map;
this.url = opts.url;
this.apiKey = opts.apiKey;
this.layers = opts.layers;
this.levels = [];
this.level = '0';
this.events = {};
Expand Down Expand Up @@ -139,21 +202,14 @@ export default class IndoorEqual {
* @param {boolean} visible True to make it visible, false to hide it
*/
setHeatmapVisible(visible) {
this.map.setLayoutProperty('indoor-heat', 'visibility', visible ? 'visible' : 'none');
if (this.map.getLayer('indoor-heat')) {
this.map.setLayoutProperty('indoor-heat', 'visibility', visible ? 'visible' : 'none');
}
}

_addSource() {
const queryParams = this.apiKey ? `?key=${this.apiKey}` : '';
this.map.addSource(SOURCE_ID, {
type: 'vector',
url: `${this.url}${queryParams}`
});
this.layers.forEach((layer) => {
this.map.addLayer({
source: SOURCE_ID,
...layer
})
});
this.source.addSource();
this._addLayers();
this._updateFilters();
const updateLevels = debounce(this._updateLevels.bind(this), 1000);

Expand All @@ -162,8 +218,12 @@ export default class IndoorEqual {
this.map.on('move', updateLevels);
}

_addLayers() {
this.source.addLayers();
}

_updateFilters() {
this.layers
this.source.layers
.filter(layer => layer.type !== 'heatmap')
.forEach((layer) => {
this.map.setFilter(layer.id, [ ...layer.filter || ['all'], ['==', 'level', this.level]]);
Expand All @@ -177,8 +237,8 @@ export default class IndoorEqual {
}

_updateLevels() {
if (this.map.isSourceLoaded(SOURCE_ID)) {
const features = this.map.querySourceFeatures(SOURCE_ID, { sourceLayer: 'area' });
if (this.map.isSourceLoaded(this.source.sourceId)) {
const features = this.map.querySourceFeatures(this.source.sourceId, { sourceLayer: 'area' });
const levels = findAllLevels(features);
if (!arrayEqual(levels, this.levels)) {
this.levels = levels;
Expand Down
15 changes: 15 additions & 0 deletions test/mapbox-gl-indoorequal.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ describe('IndoorEqual', () => {
let map;
let addSource;
let addLayer;
let getLayer;
let setFilter;
let setLayoutProperty;
let on;
Expand All @@ -21,10 +22,12 @@ describe('IndoorEqual', () => {
on = {};
addSource = jest.fn();
addLayer = jest.fn();
getLayer = jest.fn();
setFilter = jest.fn();
setLayoutProperty = jest.fn();
map.addSource = addSource;
map.addLayer = addLayer;
map.getLayer = getLayer;
map.setFilter = setFilter;
map.setLayoutProperty = setLayoutProperty;
map.on = (name, fn) => { on[name] = fn};
Expand Down Expand Up @@ -53,6 +56,16 @@ describe('IndoorEqual', () => {
}).toThrow('You must register your apiKey at https://indoorequal.com before and set it as apiKey param.');
});

it('allows to set geojson data', () => {
map.isStyleLoaded = () => true;
const indoorEqual = new IndoorEqual(map, { geojson: { area: { id: 1 }, area_name: { id: 2} } });
expect(addSource.mock.calls.length).toEqual(2);
expect(addSource.mock.calls[0]).toEqual(['indoorequal_area', { type: 'geojson', data: { id: 1 } }]);
expect(addSource.mock.calls[1]).toEqual(['indoorequal_area_name', { type: 'geojson', data: { id: 2 } }]);
expect(addLayer.mock.calls.length).toEqual(5);
expect(setFilter.mock.calls.length).toEqual(5);
});

it('load the source and the layers once the map style is loaded', () => {
map.isStyleLoaded = () => false;
const indoorEqual = new IndoorEqual(map, { apiKey: 'myapikey' });
Expand Down Expand Up @@ -183,6 +196,7 @@ describe('IndoorEqual', () => {
});

it('changes heatmap visibility', () => {
getLayer.mockReturnValue(true);
const indoorEqual = new IndoorEqual(map, { apiKey: 'myapikey' });
expect(setLayoutProperty.mock.calls.length).toEqual(0);
indoorEqual.setHeatmapVisible(false);
Expand All @@ -195,6 +209,7 @@ describe('IndoorEqual', () => {

it('changes heatmap visibility at start', () => {
map.isStyleLoaded = () => true;
getLayer.mockReturnValue(true);
const indoorEqual = new IndoorEqual(map, { apiKey: 'myapikey', heatmap: false });
expect(setLayoutProperty.mock.calls.length).toEqual(1);
expect(setLayoutProperty.mock.calls[0]).toEqual(['indoor-heat', 'visibility', 'none']);
Expand Down

0 comments on commit bdf08d0

Please sign in to comment.