Is there any API for using Open Historical Map?

Hello!
Is it possible to use Open Historical Map in a personal coding project using leaftet ?
Here is my code that is currently using Open Street Map:

import React from 'react';
import ".././index.css"
import "leaflet/dist/leaflet.css"
import L from 'leaflet';
import data from '../data.json';

import { MapContainer, TileLayer, Marker, Tooltip, Popup } from "react-leaflet";

const Home = () => {
    const position = [51.505, -0.09]; // Latitude and Longitude for initial map center
    return (
        <main>
            <MapContainer center={position} zoom={13} style={{ height: "100vh", width: "100%" }}>
                <TileLayer
                    url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
                    attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
                />
            </MapContainer>
        </main>
    );
};

export default Home;

How can I put your map instead ?

I want to use the map from 1939.

Thank you,

José Lopes

You’re more than welcome to use OpenHistoricalMap in your personal project! OHM publishes vector tiles, not a raster tile layer like you’d get from the official OSM tile server. This wiki page outlines the packages and resources you’ll need to set it up.

Essentially, you need to install both MapLibre GL JS and the MapLibre GL Leaflet plugin. Then you’d create an L.maplibreGL using the recommended stylesheet URL and attribution string. Finally, you’d install the MapLibre GL Dates plugin to filter the tiles to a particular date. (Otherwise, you’d see everything from all of time overlapping all at once.)

I haven’t used react-leaflet before, but it sounds like useMap() gives you access to Leaflet’s Map object. From there, you can create the L.maplibreGL and call addTo on it, passing in the Map.

We also provide a time slider control as a Leaflet plugin, but unfortunately it isn’t packaged up for NPM and isn’t compatible with React. If you need the user to be able to select the date interactively, consider using an off-the-shelf component for a date picker or slider, then hook it up to MapLibre GL Dates.

Alternatively, you can embed OHM in an iframe if you don’t need to interact directly with features inside the map.

I hope this helps! Our documentation for embedding OHM needs more detail for more frameworks, so if you get it working, please share some tips or let us know about things that can be improved.

1 Like

I’m sorry bother you once more. What URL should i use on the addSource Method ?

Btw I’m not sure if I am doing this right ?

Here is what im getting:

Here is my code:

import L from 'leaflet';
import 'leaflet/dist/leaflet.css';
import 'maplibre-gl/dist/maplibre-gl.css';
import '@maplibre/maplibre-gl-leaflet';

const Home = () => {
    const mapContainerRef = useRef(null);

    useEffect(() => {
        const map = L.map(mapContainerRef.current).setView([40, -120], 3);

        const gl = L.maplibreGL({
            style: 'https://api.maptiler.com/maps/topo-v2/style.json?key=' + MAPTILER_KEY,
            attributionControl: '<a href="https://www.openhistoricalmap.org/">OpenHistoricalMap</a>'
        }).addTo(map);

        const maplibreMap = gl.getMaplibreMap();

        maplibreMap.on('style.load', () => {
            maplibreMap.addSource('my-data', {
                type: 'vector',
                url: 'https://demotiles.maplibre.org/tiles/tiles.json',
            });
        });

        map.once('styledata', function (event) {
            map.filterByDate('1939-08-31');
        });

        return () => {
            map.remove();
        };
    }, []);

    return (
        <div ref={mapContainerRef} style={{ width: '100%', height: '95vh' }} />
    );
};

export default Home;

You only need to use a raw TileJSON or source if you want to define every aspect of the appearance of the data from the ground up, or if you only need to use a very small subset of the data. To display an OHM layer that looks similar to what’s on openhistoricalmap.org, you should input an existing stylesheet, like this:

const gl = L.maplibreGL({
    style: 'https://www.openhistoricalmap.org/map-styles/main/main.json', // Historic
    attributionControl: '<a href="https://www.openhistoricalmap.org/">OpenHistoricalMap</a>'
}).addTo(map);

This URL is for the default Historic style; we also publish stylesheets for the other styles you can switch to on the main site.

You can delete the style.load listener, since the stylesheet comes with the required sources already.

You need to filter the MapLibreGL map, not the Leaflet map:

maplibreMap.once('styledata', function (event) {
    maplibreMap.filterByDate('1939-08-31');
});
1 Like

Thank you for all the help.

Just one more thing, sorry :frowning:

I have the map, but it’s not filtering by date. Im calling the gl.filterByDate but its not working

        gl.once('styledata', function (event) {
            gl.filterByDate('1939-08-31');
        });

I saw the code of the plugin I think it needs also the map,

Plugin code:

/**
 * Filters the map’s features by a date.
 *
 * @param map The MapboxGL map object to filter the style of.
 * @param date The date object or date string to filter by.
 */
function filterByDate(map, date) {
  let dateRange = dateRangeFromDate(date);
  map.getStyle().layers.map(function (layer) {
    if (!('source-layer' in layer)) return;

    let filter = constrainFilterByDateRange(map.getFilter(layer.id), dateRange);
    map.setFilter(layer.id, filter);
  });
}

I tried this aswel:

     gl.once('styledata', function (event) {
            gl.filterByDate( map ,'1939-08-31');
        });

didn’t work :frowning:

Again sorry for being dumb

No worries, it’s pretty confusing because, between all these packages, there are three different types of objects that call themselves a “map” and two that call themselves “layers”. :face_with_head_bandage:

Based on the code you’ve posted, I think you need something like this:

maplibreMap.once('styledata', function (event) {
    maplibreMap.filterByDate('1939-08-31');
});

where maplibreMap is the return value of gl.getMaplibreMap().

1 Like

It gives me an error it says that maplibreMap.filterByDate is not a function.

The console.log in the picture is this console.log(maplibreMap).
Is the correct thing right ?

If it is problaly im not importing the MapLibre GL Dates correctly, im not sure.

I will send you the full code bellow.

Btw Thank You :pray:

import React, { useEffect, useRef } from 'react';
import L from 'leaflet';
import 'leaflet/dist/leaflet.css';
import 'maplibre-gl/dist/maplibre-gl.css';
import '@maplibre/maplibre-gl-leaflet';
import '@openhistoricalmap/maplibre-gl-dates';

const Home = () => {
    const mapContainerRef = useRef(null);
    const mapRef = useRef(null);
    
    const addMarkers = (flag, coords, name, size) => {
        const map = mapRef.current; 

        const customIcon = L.icon({
            iconUrl: flag,
            iconSize: size,
        });

        L.marker(coords, { icon: customIcon })
            .addTo(map)
            .bindPopup(name);
    };

    useEffect(() => {
        const map = L.map(mapContainerRef.current).setView([40, -120], 3);
        mapRef.current = map; 

        const gl = L.maplibreGL({
            style: 'https://www.openhistoricalmap.org/map-styles/main/main.json',
            attributionControl: '<a href="https://www.openhistoricalmap.org/">OpenHistoricalMap</a>'
        }).addTo(map);

        const maplibreMap = gl.getMaplibreMap();
        console.log(maplibreMap)

        maplibreMap.once('styledata', function (event) {
            maplibreMap.filterByDate('1939-08-31');
        });

        // Add nations
        addMarkers("https://upload.wikimedia.org/wikipedia/commons/thumb/7/77/Flag_of_Germany_%281935%E2%80%931945%29.svg/1200px-Flag_of_Germany_%281935%E2%80%931945%29.svg.png", [52.518678928835755, 13.376176468301741], "Nazi Germany", [31, 21]);
        addMarkers("https://upload.wikimedia.org/wikipedia/commons/thumb/4/41/Flag_of_Poland_%281928%E2%80%931980%29.svg/1200px-Flag_of_Poland_%281928%E2%80%931980%29.svg.png", [52.22531377967117, 21.028126138815217], "Poland", [31, 21]);
        addMarkers("https://upload.wikimedia.org/wikipedia/commons/thumb/a/a9/Flag_of_the_Soviet_Union.svg/1200px-Flag_of_the_Soviet_Union.svg.png", [55.75060316924369, 37.6153173584575], "USSR", [31, 21]);
        addMarkers("https://upload.wikimedia.org/wikipedia/commons/thumb/a/a5/Flag_of_the_United_Kingdom_%281-2%29.svg/383px-Flag_of_the_United_Kingdom_%281-2%29.svg.png", [51.50005911412233, -0.12448366423047506], "UK", [31, 21]);

        //Add Ports
        addMarkers("https://static.thenounproject.com/png/4417-200.png", [54.321, 10.134], "Kiel Naval Base", [21, 21]);
        addMarkers("https://static.thenounproject.com/png/4417-200.png", [53.521, 8.106], "Wilhelmshaven Naval Base", [21, 21]);

        return () => {
            map.remove();
        };
    }, []);

    return (
        <div ref={mapContainerRef} style={{ width: '100%', height: '95vh' }} />
    );
};

export default Home;

Also import the maplibre-gl package before @openhistoricalmap/maplibre-gl-dates.

I can’t do it :frowning:

I tried a lot of things nothing worked.

Problably is something to do with react I dont know. It just dont recognize the filter data function.

This is was my last atempt, yes i get rid of leaftet even tho I need it. I just wanted this to work.

Thank you so much anyway. :pray:

import React, { useEffect, useRef } from 'react';
import L from 'leaflet';
import 'leaflet/dist/leaflet.css';
import 'maplibre-gl/dist/maplibre-gl.css';
import maplibregl from 'maplibre-gl';
import '@maplibre/maplibre-gl-leaflet';
import '@openhistoricalmap/maplibre-gl-dates';

const Home = () => {

    useEffect(() => {
        const map = new maplibregl.Map({
            container: 'map',
            style: 'https://www.openhistoricalmap.org/map-styles/main/main.json',
            attributionControl: {
                customAttribution: '<a href="https://www.openhistoricalmap.org/">OpenHistoricalMap</a>',
            },
        });
        map.once('styledata', function (event) {
            map.filterByDate('2013-04-14');
          });

        return () => {
            map.remove();
        };
    }, []);

    return (
        <div
            id="map"
            style={{ width: '100%', height: '100vh' }} 
        ></div>
    );
};

export default Home;

I’m not sure why, but I was also seeing this error when working in an NPM module. Try importing filterByDate as a standalone function before MapLibre:

Then calling it as a standalone function:

1 Like

Its working. :grin:

Thank you so much. :pray:

One thing there are some strange things on the map.

Romania was wierd borders I dont know why, even in latest date (2024) this strange bordes apear.

In Albania there are some strange borders aswell and also I’m using this date 1939-08-31, and Albania is still independet, but it was invaded by italy in April of this year.

Latvia doesn’t exist on the map its part of Poland :joy:, its not accurate, because Latvia was an independent country.

Memel belongs to Germany because they annexed it in march of 1939.

And the Finland has two borders one pre Winter War and one after, but the winter war, but the border after it should appear after March 1940.

Cabinda( it’s a Portuguese Enclave in congo, that now is a part of Angola), is missing on the map.

Last thing this is just minor, there is no label to Portuguese East Timor.

I don’t know if I can help fix that but I would be glad to help fix the map, is there any guide on how change the map that you can send me ?

It’s a way that I can help to return the help that you gave me. :raised_hands:

Btw I dont know if this helps the documentation of the project but here is my code. I think it’s the bare minimmum to put openHistoricalMap with Leaftet to work in react

import React, { useEffect, useRef } from 'react';
import L from 'leaflet';
import 'leaflet/dist/leaflet.css';
import 'maplibre-gl/dist/maplibre-gl.css';
import '@maplibre/maplibre-gl-leaflet';
import { filterByDate, dateRangeFromISODate } from '@openhistoricalmap/maplibre-gl-dates';

const Home = () => {
    const mapContainerRef = useRef(null);
    const mapRef = useRef(null);

    useEffect(() => {
        const map = L.map(mapContainerRef.current).setView([40, -120], 3);
        mapRef.current = map;

        const gl = L.maplibreGL({
            style: 'https://www.openhistoricalmap.org/map-styles/main/main.json',
            attributionControl: '<a href="https://www.openhistoricalmap.org/">OpenHistoricalMap</a>'
        }).addTo(map);

        const maplibreMap = gl.getMaplibreMap();
        console.log(maplibreMap)

        maplibreMap.once('styledata', function (event) {
            filterByDate(maplibreMap, '1939-08-31');
        });

        return () => {
            map.remove();
        };
    }, []);

    return (
        <div ref={mapContainerRef} style={{ width: '100%', height: '95vh' }} />
    );
};

export default Home;```

Once more thank you so much for all the help. 
And I hope my code and sugestions to the map can help.

This appears to be a tagging mistake: Way: 200446057 | OpenHistoricalMap is the outer edge of a boundary relation that only exists until 5000 BCE, but the outer edge itself is tagged as a boundary – and it lacks dates, so it’s assumed to exist throughout all of history, forever. The tags should be removed from that way.

It looks like we don’t have the boundary of Poland or Latvia as of 1939, only a label for the approximate location of Poland in 1939. That’s why you don’t see any boundary along the Latvian coast. Our boundary coverage is still a work in progress, especially going back into the past.

The best place to start is by zooming all the way into a neighborhood, clicking Edit, following the interactive tutorial, and then trying to map something local.

Then when you’re comfortable mapping stuff at that scale, you can map boundaries using the Web-based iD editor. I didn’t see a great tutorial for mapping boundaries in OSM at a glance, so here’s the process in a nutshell:

Draw a line, choose the generic Line feature type, open the Relations section at the bottom of the left sidebar, add a new relation using the :heavy_plus_sign: button, choose Boundary, change the type to Administrative, and add a name, dates, and a source. You’ll see a warning that the way lacks a “role”; you can set it to outer because the way forms the outer edge of the boundary. But if it’s part of an enclave of the country, you’d set inner as the role instead.

The boundary relation should form a closed loop consisting of multiple ways, such as one way that follows just the Polish–Lithuanian border in reality. When you map the next way along the boundary, or if the next way is already mapped as part of a neighboring country’s boundary, you’ll need to reuse the existing boundary relation. Select the way, go down to the Relations section, and use the dropdown menu to select the boundary relation instead of adding a new relation.

It’s a bit tedious, but it gets easier after the first couple boundaries.

1 Like

I fixed those borders in Romania and the pre-Winter War border between USSR and Finland :+1:

1 Like