Adding Archival Maps

When it comes to building interactive maps, one of the biggest challenges historians have to contend with is that most of the resources available present contemporary, rather than historical, information. Take, for example, the collection of tile layers we used to find a base maps. The majority of these maps display contemporary roads, cities, and political boundaries that will seem anachronistic maps depicting historical events. Overlaying the image of an archival map is an easy way to display historical street names and addresses. Like this:

Even though the base map is a contemporary map of New Jersey, we can overlay an image of an archival map of the town of Haddonfield from the West Jersey Historical Project so that our map can show historical streets and addresses. This tutorial provides two methods of overlaying an image onto a basemap. Both have their advantages and disadvantages. The first method does not require on any external resources, the second method relies on the Map Warper project.

We’ll begin with the usual formatting to bring in our basemap. Start a new HTML file called image-overlay-rotate.html and enter the usual code to create a basemap:

<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet/v1.0.0-rc.1/leaflet.css" />
<script src="http://cdn.leafletjs.com/leaflet/v1.0.0-rc.1/leaflet.js"></script>
<style> html, body, #map {
margin: 0px;
width: 100%;
height: 100%;
padding: 0px;
} </style>
</head>

<body>

<div id='map'></div>
<script>
var map = L.map('map').setView([39.898197, -75.031492], 15);
L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png',
{ attribution: 'Map data: &copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>, <a href="http://viewfinderpanoramas.org">SRTM</a> | Map style: &copy; <a href="https://opentopomap.org">OpenTopoMap</a> (<a href="https://creativecommons.org/licenses/by-sa/3.0/">CC-BY-SA</a>)'
}).addTo(map);

Notice that we are using Leaflet version 1.0, while the previous two tutorials used Leaflet 0.7. The scripts we are using this time are written for the newer version of Leaflet and will not work if we do not change our code to retrieve this newer version of Leaflet.

To overlay the image of a map, we need to use the ImageOverlay.Rotated plugin written by Ivan Sanchez. This can be downloaded as a .js file from his GitHub page. Note that Ivan shares this plugin under a “Beer-Ware License,” so if you end up using this in your projects you are obliged to buy him a beer next time you see him. Download the plugin as Leaflet.ImageOverlay.Rotated.js and save it in the same folder as the .html file you are currently building. We need to add another line to retrieve this plugin, but we can use the ‘./’ notation rather than a url because this information is stored locally in the same folder as our html file. If you saved Leaflet.ImageOverlay.Rotated.js in a different folder, you would need to write a filepath before the information could be retrieved.

<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet/v1.0.0-rc.1/leaflet.css" />
<script src="http://cdn.leafletjs.com/leaflet/v1.0.0-rc.1/leaflet.js"></script>
<script src="./Leaflet.ImageOverlay.Rotated.js"></script>
<style> html, body, #map {
margin: 0px;
width: 100%;
height: 100%;
padding: 0px;
} </style>
</head>

<body>

<div id='map'></div>
<script>
var map = L.map('map').setView([39.898197, -75.031492], 15);
L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png',
{ attribution: 'Map data: &copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>, <a href="http://viewfinderpanoramas.org">SRTM</a> | Map style: &copy; <a href="https://opentopomap.org">OpenTopoMap</a> (<a href="https://creativecommons.org/licenses/by-sa/3.0/">CC-BY-SA</a>)'
}).addTo(map);

Ivan designed the plugin to be fairly simple to invoke, so the rest of the html file is pretty easy to read:

var topleft = L.latLng(39.9096, -75.0328),
    topright = L.latLng(39.90137, -75.01495),
    bottomleft = L.latLng(39.89258, -75.04827);

var overlay = L.imageOverlay.rotated("http://www.westjerseyhistory.org/maps/hopkinsmaps/Hopkins1877Hdnfld30.jpg", topleft, topright, bottomleft, {
    opacity: 0.75,
    attribution: "&copy; <a href='http://www.westjerseyhistory.org/maps/hopkinsmaps/Hopkins1877Hdnfld30.shtml'>West Jersey History Project</a>"
}).addTo(map);

</script>
</body>
</html>

The first step is to create a series of variables called ‘topleft,’ ‘topright,’ and ‘bottomleft’, each of which uses the L.latLng notation to mark coordinates for the corresponding corners of the image we want to overlay onto the map.

The next step is to create a variable called ‘overlay’ that gives the source of the image we want to paste over our basemap. We can add a few options, such as setting the opacity of the map and adding an attribution for the source of the image.

The last thing to do is close out some of the HTML tags in our header.

That all seems pretty easy to do, but the tricky part is determining the coordinates for the three corners of the image. It’s possible to use approximate coordinates and adjust them using trial-and-error until the archival image lines up with the basemap, but this would be a pretty tedious process.

Luckily, Ivan’s plugin allows us to interact with the images we are overlaying. If we didn’t know the exact coordinates for the three corners of the map, we could approximate and then drag the map into place so that it matches the basemap:

Each corner of the map has a drag-able blue marker (similar to the ones in the last tutorial) attached to each corner of the map. Dragging a marker will re-position that corner of the map. It’s still quite tedious to line up the features on both maps, but this method is definitely quicker than adjusting the coordinates in the HTML file by trial and error.

In order to do this, we need to add some variables to our HTML file. We can change the coordinates on the three points that mark the corners of the image, then add a set of variables for the three markers:

var point1 = L.latLng(39.914108, -75.034309),
    point2 = L.latLng(39.905048, -75.015270),
    point3 = L.latLng(39.888741, -75.032664);

var marker1 = L.marker(point1, {draggable: true} ).addTo(map),
    marker2 = L.marker(point2, {draggable: true} ).addTo(map),
    marker3 = L.marker(point3, {draggable: true} ).addTo(map);

var bounds = new L.LatLngBounds(point1, point2).extend(point3);

map.fitBounds(bounds);

We also need to create a variable called ‘bounds’ that matches up with the points identified in the marker variables. Then we can use the map.fitBounds notation to connect the map to these markers.

After that, we need to add an option to our ‘overlay’ variable to make it interactive. We add this line between the options for ‘opacity’ and ‘attribution’:

var overlay = L.imageOverlay.rotated("http://www.westjerseyhistory.org/maps/hopkinsmaps/Hopkins1877Hdnfld30.jpg", topleft, topright, bottomleft, {
    opacity: 0.75,
    interactive: true
    attribution: "&copy; <a href='http://www.westjerseyhistory.org/maps/hopkinsmaps/Hopkins1877Hdnfld30.shtml'>West Jersey History Project</a>"
}).addTo(map);

Lastly, we need to add some notations so that the image is re-positioned every time we move a marker:

function repositionImage() {
overlay.reposition(marker1.getLatLng(), marker2.getLatLng(), marker3.getLatLng());
};

marker1.on('drag dragend', repositionImage, function (e) {
document.getElementById('latitude').value = marker.getLatLng().lat;
document.getElementById('longitude').value = marker.getLatLng().lng;
});
marker2.on('drag dragend', repositionImage);
marker3.on('drag dragend', repositionImage);

This lets us overlay an image and move it around until it matches up with the basemap, but any time we refresh the page, the map will return to the coordinates specified in the HTML file. We can read the coordinates for the corners of the map by adding an interactive popup to display the coordinates of any location we click on the map:

var popup = L.popup();

function onMapClick(e) {
popup
.setLatLng(e.latlng)
.setContent("You clicked the map at " + e.latlng.toString())
.openOn(map);
}

map.on('click', onMapClick);

By adding this to the HTML file, we can move the image so that it lines up with our map, then click on each corner to reveal the appropriate coordinates.

Once we have the desired coordinates, we can edit our HTML file to match the map at the top of the page so that the overlay remains in place.

There is another option for overlaying an image of an archival map onto our basemap, but it requires turning the image of the map into a tile layer. You’ll remember that our basemap is a tile layer, that changes resolution as we zoom in or out. Turning an image into a tile layer usually requires the use of proprietary software and, like our basemaps, Leaflet likes tile layers to be hosted online. Turning an image of an archival map into a tile layer usually means relying on other online services, which may disappear or decide to impose a fee on their services. If these considerations are off-putting, you can always rely on the above method to overlay images.

You can create a new HTML file for this second method, add the usual header and footer to format your HTML page.

This is the first time we are two vector layers to to our map, so it’s a good time to talk about formatting layers. Most of the variables we add to our maps start with a ‘var’ tag and end with ‘.addTo(map)’ but we can also create a set of variables, then add them separate layers to our map. Take a look at this notation:

<script>

var streetmap = L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png'),
    haddonfield = L.tileLayer('http://mapwarper.net/maps/tile/19483/{z}/{x}/{y}.png');
 
var map = L.map('map', {
 center: [39.910320, -75.028256], 
 zoom: 14, 
 layers: [streetmap, haddonfield]
 });
 
</script>

This creates a variable called ‘streetmap’ that calls up the usual OpenStreetMap tile layer AND a variable called ‘haddonfield’ that calls up another tile layer created from the West Jersey Historical Project. Then we create a variable called ‘map’, we add some options to specify the coordinates and default zoom, and an option to add the layers we created above. Once we add the usual header and footers to open and close the HTML codes and specify the source of the Leaflet library, this script gives us a map with to tile layers:

This method relies less on writing JavaScript and more on creating a tile layer. Turning an image into a tile layer is relatively easy, thanks to Map Warper, an online app designed by Tim Water that will geo-rectify and warp images of archival maps so that they can be added as tile layers. You can browse through the maps already uploaded and see that once you click on a map, the ‘Export’ tab provides a url for the map’s georecrectified tile layer:

mapwarper

If we wanted to overlay an archival map of Greece as a layer, we could add the above url to our HTML file. If Map Warper does not already contain the map we want to add, you can create a map, upload a map from your own collection and rectify the map by adding control points that correspond with OpenStreetMap:

recrify

The New York Public Library operates its own version of Map Warper to georectify and share digitized maps from the NYPL collection, but this version does not allow you to upload your own maps. It does, however, provide a useful tutorial that can guide you through the work of georectifying a map.

Both versions of Map Warper provide a wonderful repository of georectified maps that can be easily incorporated as tile layers into a Leaflet map. The catch is that if these servers disappear or cease to offer their services for free, those layers will disappear from your own map.