I have a React app that should render a base map and a VectorTileLayer with OpenLayers.
OpenLayers makes it easy to put a dynamic map in any web page. It can display map tiles, vector data and markers loaded from any source. OpenLayers has been developed to further the use of geographic information of all kinds. It is completely free, Open Source JavaScript, released under the 2-clause BSD License (also known as the FreeBSD).
(froth for let StackOverflow post this).
App.js
:
function App() {
const [keycloak, setKeycloak] = useState(null);
const [authenticated, setAuthenticated] = useState(false);
const [isMapInitialized, setIsMapInitialized] = useState(false);
useEffect(() => {
// Initialize Keycloak
const keycloakConfig = {
url: 'myURL',
realm: 'myREALM',
clientId: 'myCLIENTID'
};
const kc = new Keycloak(keycloakConfig);
kc.init({ onLoad: 'login-required' })
.then((auth) => {
if (auth) {
setKeycloak(kc);
setAuthenticated(true);
} else {
setAuthenticated(false);
}
})
.catch((err) => {
console.error("Keycloak initialization failed", err);
});
}, []);
// Define the style function
const yourVectorTileStyleFunction = function (feature, resolution) {
let style;
const geometryType = feature.getGeometry().getType();
switch (geometryType) {
case 'Polygon':
case 'MultiPolygon':
style = new Style({
fill: new Fill({
color: 'rgba(0, 255, 0, 0.5)',
}),
stroke: new Stroke({
color: '#00FF00',
width: 2,
}),
});
break;
case 'LineString':
case 'MultiLineString':
style = new Style({
stroke: new Stroke({
color: '#FF0000',
width: 2,
}),
});
break;
case 'Point':
case 'MultiPoint':
style = new Style({
image: new Circle({
fill: new Fill({
color: 'rgba(0, 0, 255, 0.5)',
}),
stroke: new Stroke({
color: '#0000FF',
width: 2,
}),
radius: 5,
}),
});
break;
default:
style = new Style({
fill: new Fill({
color: 'rgba(255, 255, 255, 0.5)',
}),
stroke: new Stroke({
color: '#FFFFFF',
width: 2,
}),
});
break;
}
return [style];
};
useEffect(() => {
// Initialize OpenLayers map only if authenticated and not already initialized
if (authenticated && !isMapInitialized) {
console.log('Initializing map');
const map = new Map({
target: 'map',
layers: [
new TileLayer({
zIndex: 0,
source: new XYZ({
url: 'http://{a-d}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png'
})
}),
new VectorTileLayer({
source: new VectorTileSource({
zIndex: 1,
format: new MVT(), // the format could be MVT
url: 'http://localhost:8080/tiles/test1/{z}/{x}/{y}?srid=3857',
tileLoadFunction: async function (tile, src) {
try {
// Your authentication and fetching logic here
const headers = new Headers();
headers.append('Authorization', `Bearer ${keycloak.token}`);
const response = await fetch(src, { headers });
if (!response.ok) {
console.error(`Failed to fetch tile from ${src}`);
return;
}
const arrayBuffer = await response.arrayBuffer();
// Pass the ArrayBuffer to the tile
tile.setLoader(function () {
return new Promise((resolve) => {
resolve(arrayBuffer);
});
});
} catch (error) {
console.error("An error occurred while loading the tile:", error);
}
}
}),
style: yourVectorTileStyleFunction, // you'll need to define this
})
],
view: new View({
center: [0, 0],
zoom: 2,
projection: 'EPSG:3857',
}),
});
console.log('Map initialized');
console.log(map.getAllLayers());
// map.renderSync();
setIsMapInitialized(true);
}
}, [authenticated, isMapInitialized, keycloak]);
if (keycloak) {
if (authenticated) {
return (
<div>
<div id="map" style={{ width: '100%', height: '100vh' }}></div>
<button onClick={() => keycloak.logout()}>Logout</button>
</div>
);
} else {
return <div>Unable to authenticate!</div>;
}
}
return <div>Initializing Keycloak...</div>;
}
export default App;
The base map is showed but the VectorTileLayer isn't rendered. Why?
EDIT: I added feature in TileLoadFunction:
tileLoadFunction: async function (tile, src) {
try {
// Your authentication and fetching logic here
const headers = new Headers();
headers.append('Authorization', `Bearer ${keycloak.token}`);
const response = await fetch(src, { headers });
if (!response.ok) {
console.error(`Failed to fetch tile from ${src}`);
return;
}
const arrayBuffer = await response.arrayBuffer();
// Pass the ArrayBuffer to the tile
tile.setLoader(function () {
return new Promise((resolve) => {
resolve(arrayBuffer);
});
});
const features = tile.getFormat().readFeatures(arrayBuffer);
tile.setFeatures(features);
console.log('FEATURES:', JSON.stringify(tile.getFeatures(), null, 2));
} catch (error) {
console.error("An error occurred while loading the tile:", error);
}
}
where FEATURES are:
FEATURES: [
{
"type_": "Polygon",
"flatCoordinates_": [
989,
331,
1009,
340,
1002,
354,
998,
352,
989,
331
],
"flatInteriorPoints_": null,
"flatMidpoints_": null,
"ends_": [
10
],
"properties_": {
"id": 468,
"name": "geometry1-468",
"layer": "default"
}
},
...
but the VectorTileLayer ins't rendered yet!
EDIT2:
With this code for TileLoadFunction
useEffect(() => {
// Initialize OpenLayers map only if authenticated and not already initialized
if (authenticated && !isMapInitialized) {
console.log('Initializing map');
async function fetchTileData(url, token) {
const headers = new Headers();
headers.append('Authorization', `Bearer ${token}`);
const response = await fetch(url, { headers });
if (!response.ok) {
throw new Error(`Failed to fetch tile from ${url}`);
}
return await response.arrayBuffer();
}
function setTileLoader(tile, arrayBuffer) {
tile.setLoader(async function (extent, resolution, projection) {
return arrayBuffer;
});
}
async function processTileFeatures(tile, arrayBuffer) {
const features = tile.getFormat().readFeatures(arrayBuffer);
tile.setFeatures(features);
console.log('FEATURES:', JSON.stringify(tile.getFeatures(), null, 2));
}
const tileLoadFunction = async function(tile, url) {
try {
const arrayBuffer = await fetchTileData(url, keycloak.token);
setTileLoader(tile, arrayBuffer);
await processTileFeatures(tile, arrayBuffer);
} catch (error) {
console.error("An error occurred while loading the tile:", error);
}
};
const map = new Map({
target: 'map',
layers: [
new TileLayer({
zIndex: 0,
source: new XYZ({
url: 'http://{a-d}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png'
})
}),
new VectorTileLayer({
source: new VectorTileSource({
zIndex: 1,
format: new MVT(), // the format could be MVT
url: 'http://localhost:8080/tiles/test1/{z}/{x}/{y}',
tileLoadFunction: tileLoadFunction,
}),
style: yourVectorTileStyleFunction, // you'll need to define this
})
],
view: new View({
center: [0, 0],
zoom: 2,
projection: 'EPSG:3857',
}),
});
console.log('Map initialized');
console.log(map.getAllLayers());
// map.renderSync();
setIsMapInitialized(true);
}
}, [authenticated, isMapInitialized, keycloak]);
With this piece of code:
useEffect(() => {
// Initialize OpenLayers map only if authenticated and not already initialized
if (authenticated && !isMapInitialized) {
async function fetchTileData(url, token) {
const headers = new Headers();
headers.append('Authorization', `Bearer ${token}`);
const response = await fetch(url, { headers });
if (!response.ok) {
throw new Error(`Failed to fetch tile from ${url}`);
}
return await response.arrayBuffer();
}
async function processTileFeatures(tile, arrayBuffer, extent, projection) {
const options = {
featureProjection: projection,
extent: extent
};
const features = tile.getFormat().readFeatures(arrayBuffer, options);
tile.setFeatures(features);
console.log('FEATURES:', JSON.stringify(tile.getFeatures(), null, 2));
}
const tileLoadFunction = async function (tile, url) {
try {
const arrayBuffer = await fetchTileData(url, keycloak.token);
// extent and projection would be defined or obtained from your specific application context
const extent = map.getView().calculateExtent(map.getSize());
const projection = 'EPSG:3857'; // or whatever your projection code is
await processTileFeatures(tile, arrayBuffer, extent, projection);
} catch (error) {
console.error("An error occurred while loading the tile:", error);
}
};
console.log('Initializing map');
const map = new Map({
target: 'map',
layers: [
new TileLayer({
zIndex: 0,
source: new XYZ({
url: 'http://{a-d}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png'
})
}),
new VectorTileLayer({
source: new VectorTileSource({
zIndex: 1,
format: new MVT(), // the format could be MVT
url: 'http://localhost:8080/tiles/test1/{z}/{x}/{y}',
tileLoadFunction: tileLoadFunction,
}),
style: yourVectorTileStyleFunction,
})
],
view: new View({
center: [0, 0],
zoom: 2,
projection: 'EPSG:3857',
}),
});
console.log('Map initialized');
console.log(map.getAllLayers());
// map.renderSync();
setIsMapInitialized(true);
}
}, [authenticated, isMapInitialized, keycloak]);
I was able to solve the issue.