Search code examples
javascriptreactjsgisopenlayers

Why OpenLayers can't render the VectorTileLayer?


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]);

I can see a point at the center of the map: enter image description here


Solution

  • 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.