Skip to content

Commit

Permalink
Merge pull request #1009 from ahocevar/update-mapbox-source
Browse files Browse the repository at this point in the history
New 'updateMapboxSource()' utility function
  • Loading branch information
ahocevar authored Nov 2, 2023
2 parents 6926508 + 6d5bec6 commit 985a20f
Show file tree
Hide file tree
Showing 4 changed files with 237 additions and 34 deletions.
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ and open a browser on the host and port indicated in the console output (usually
- [setFeatureState](#setFeatureState)
- [stylefunction](#stylefunction)
- [updateMapboxLayer](#updateMapboxLayer)
- [updateMapboxSource](#updateMapboxSource)

### References

Expand Down Expand Up @@ -616,6 +617,29 @@ Update a Mapbox Layer object in the style. The map will be re-rendered with the

`void`

* * *

#### updateMapboxSource

**updateMapboxSource**(`mapOrGroup`, `id`, `mapboxSource`): `Promise`<`Source`>

Updates a Mapbox source object in the style. The according OpenLayers source will be replaced
and the map will be re-rendered.

##### Parameters

| Name | Type | Description |
| :------------- | :-------------------- | :------------------------------------------------- |
| `mapOrGroup` | `Map` \| `LayerGroup` | The Map or LayerGroup `apply` was called on. |
| `id` | `string` | Key of the source in the `sources` object literal. |
| `mapboxSource` | `any` | Mapbox source object. |

##### Returns

`Promise`<`Source`>

Promise that resolves when the source has been updated.

<a name="classesmapboxvectorlayermd"></a>

## Class: MapboxVectorLayer
Expand Down
137 changes: 104 additions & 33 deletions src/apply.js
Original file line number Diff line number Diff line change
Expand Up @@ -652,43 +652,55 @@ function getBboxTemplate(projection) {
return `{bbox-${projCode.toLowerCase().replace(/[^a-z0-9]/g, '-')}}`;
}

function setupRasterLayer(glSource, styleUrl, options) {
const layer = new TileLayer();
getTileJson(glSource, styleUrl, options)
.then(function ({tileJson, tileLoadFunction}) {
const source = new TileJSON({
interpolate:
options.interpolate === undefined ? true : options.interpolate,
transition: 0,
crossOrigin: 'anonymous',
tileJSON: tileJson,
});
source.tileGrid = sourceOptionsFromTileJSON(
glSource,
tileJson,
options
).tileGrid;
if (options.projection) {
//@ts-ignore
source.projection = getProjection(options.projection);
}
const getTileUrl = source.getTileUrlFunction();
if (tileLoadFunction) {
source.setTileLoadFunction(tileLoadFunction);
}
source.setTileUrlFunction(function (tileCoord, pixelRatio, projection) {
const bboxTemplate = getBboxTemplate(projection);
let src = getTileUrl(tileCoord, pixelRatio, projection);
if (src.indexOf(bboxTemplate) != -1) {
const bbox = source.getTileGrid().getTileCoordExtent(tileCoord);
src = src.replace(bboxTemplate, bbox.toString());
function setupRasterSource(glSource, styleUrl, options) {
return new Promise(function (resolve, reject) {
getTileJson(glSource, styleUrl, options)
.then(function ({tileJson, tileLoadFunction}) {
const source = new TileJSON({
interpolate:
options.interpolate === undefined ? true : options.interpolate,
transition: 0,
crossOrigin: 'anonymous',
tileJSON: tileJson,
});
source.tileGrid = sourceOptionsFromTileJSON(
glSource,
tileJson,
options
).tileGrid;
if (options.projection) {
//@ts-ignore
source.projection = getProjection(options.projection);
}
return src;
const getTileUrl = source.getTileUrlFunction();
if (tileLoadFunction) {
source.setTileLoadFunction(tileLoadFunction);
}
source.setTileUrlFunction(function (tileCoord, pixelRatio, projection) {
const bboxTemplate = getBboxTemplate(projection);
let src = getTileUrl(tileCoord, pixelRatio, projection);
if (src.indexOf(bboxTemplate) != -1) {
const bbox = source.getTileGrid().getTileCoordExtent(tileCoord);
src = src.replace(bboxTemplate, bbox.toString());
}
return src;
});
source.set('mapbox-source', glSource);
resolve(source);
})
.catch(function (error) {
reject(error);
});
source.set('mapbox-source', glSource);
});
}

function setupRasterLayer(glSource, styleUrl, options) {
const layer = new TileLayer();
setupRasterSource(glSource, styleUrl, options)
.then(function (source) {
layer.setSource(source);
})
.catch(function (error) {
.catch(function () {
layer.setSource(undefined);
});
return layer;
Expand Down Expand Up @@ -1407,6 +1419,65 @@ export function updateMapboxLayer(mapOrGroup, mapboxLayer) {
}
}

/**
* Updates a Mapbox source object in the style. The according OpenLayers source will be replaced
* and the map will be re-rendered.
* @param {Map|LayerGroup} mapOrGroup The Map or LayerGroup `apply` was called on.
* @param {string} id Key of the source in the `sources` object literal.
* @param {Object} mapboxSource Mapbox source object.
* @return {Promise<Source>} Promise that resolves when the source has been updated.
*/
export function updateMapboxSource(mapOrGroup, id, mapboxSource) {
const currentSource = getSource(mapOrGroup, id);
const layers = /** @type {Array<VectorLayer|TileLayer|VectorTileLayer>} */ (
mapOrGroup
.getLayers()
.getArray()
.filter(function (layer) {
return (
(layer instanceof VectorLayer ||
layer instanceof TileLayer ||
layer instanceof VectorTileLayer) &&
layer.getSource() === currentSource
);
})
);
const metadata = mapOrGroup.get('mapbox-metadata');
let newSourcePromise;
switch (mapboxSource.type) {
case 'vector':
newSourcePromise = setupVectorSource(
mapboxSource,
metadata.styleUrl,
metadata.options
);
break;
case 'geojson':
newSourcePromise = Promise.resolve(
setupGeoJSONSource(mapboxSource, metadata.styleUrl, metadata.options)
);
break;
case 'raster':
case 'raster-dem':
newSourcePromise = setupRasterSource(
mapboxSource,
metadata.styleUrl,
metadata.options
);
break;
default:
return Promise.reject(
new Error('Unsupported source type ' + mapboxSource.type)
);
}
newSourcePromise.then(function (newSource) {
layers.forEach(function (layer) {
layer.setSource(newSource);
});
});
return newSourcePromise;
}

/**
* Remove a Mapbox Layer object from the style. The map will be re-rendered.
* @param {Map|LayerGroup} mapOrGroup The Map or LayerGroup `apply` was called on.
Expand Down
1 change: 1 addition & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export {
getLayers,
getSource,
getMapboxLayer,
updateMapboxSource,
updateMapboxLayer,
addMapboxLayer,
removeMapboxLayer,
Expand Down
109 changes: 108 additions & 1 deletion test/util.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
removeMapboxLayer,
setupVectorSource,
updateMapboxLayer,
updateMapboxSource,
} from '../src/apply.js';
import {fetchResource} from '../src/util.js';

Expand Down Expand Up @@ -434,13 +435,119 @@ describe('util', function () {
});
});

describe('updateMapboxSource', function () {
let map, target, source1, source2, source3;
beforeEach(function () {
target = document.createElement('div');
map = new Map({target: target});
source1 = {
type: 'geojson',
data: {
type: 'FeatureCollection',
features: [
{
type: 'Feature',
properties: {},
geometry: {type: 'Point', coordinates: [1, 1]},
},
],
},
};
source2 = {
type: 'vector',
tiles: ['http://example.com/{z}/{x}/{y}.pbf'],
};
source3 = {
type: 'raster',
tiles: ['http://example.com/{z}/{x}/{y}.png'],
};
return apply(map, {
version: 8,
sources: {
source1: source1,
source2: source2,
source3: source3,
},
layers: [
{
id: 'layer1',
source: 'source1',
type: 'circle',
},
{
id: 'layer2',
source: 'source2',
'source-layer': 'layer2',
type: 'circle',
},
{
id: 'layer3',
source: 'source3',
type: 'raster',
},
],
});
});
it('updates a geojson source', function (done) {
should(getSource(map, 'source1').getFeatures()[0].get('modified')).eql(
undefined
);
source1.data.features[0].properties.modified = true;
updateMapboxSource(map, 'source1', source1).then(function () {
try {
const source = getSource(map, 'source1');
should(source).eql(getLayer(map, 'layer1').getSource());
should(source.getFeatures()[0].get('modified')).eql(true);
should();
done();
} catch (err) {
done(err);
}
});
});
it('updates a vector source', function (done) {
should(getSource(map, 'source2').getUrls()[0]).eql(
'http://example.com/{z}/{x}/{y}.pbf'
);
source2.tiles[0] = 'http://example.com/{z}/{x}/{y}.mvt';
updateMapboxSource(map, 'source2', source2).then(function () {
try {
const source = getSource(map, 'source2');
should(source).eql(getLayer(map, 'layer2').getSource());
should(source.getUrls()[0]).eql('http://example.com/{z}/{x}/{y}.mvt');
done();
} catch (err) {
done(err);
}
});
});
it('updates a raster source', function (done) {
should(getSource(map, 'source3').getTileUrlFunction()([0, 0, 0])).eql(
'http://example.com/0/0/0.png'
);
source3.tiles[0] = 'http://example.com/{z}/{x}/{y}.jpg';
updateMapboxSource(map, 'source3', source3).then(function () {
try {
const source = getSource(map, 'source3');
should(source).eql(getLayer(map, 'layer3').getSource());
should(source.getTileUrlFunction()([0, 0, 0])).eql(
'http://example.com/0/0/0.jpg'
);
done();
} catch (err) {
done(err);
}
});
});
});

describe('updateMapboxLayer', function () {
let target;
beforeEach(function () {
target = document.createElement('div');
});

it('updates a mapbox layer', function (done) {
it('updates a geojson source', function (done) {
apply(target, JSON.parse(JSON.stringify(brightV9)))
.then(function (map) {
// add another layer that has no 'mapbox-layers' set
Expand Down

0 comments on commit 985a20f

Please sign in to comment.