Implementing OpenStreetMap

Google recently announced a change in their Maps API usage quota that will inevitably move some current users from the previously free tier into a paid tier. I thought this might perhaps drive more developers to switch from Google Maps to OpenStreetMap if only for budget reasons, so hopefully the following guide would help get things started.

OpenStreetMap implementation is actually just as simple as Google Maps, especially when coupled with Javascript libraries focused on building maps. In the following example, I used Leaflet. As far as tiles are concerned, I used tiles by Stamen. While I personally like Stamen’s tiles a lot, there are many tiles available across the web with different styles, and some might fit your particular niche or style better; check out this link for a few others. Finally, for the data, I used some data from Lava’s WW2DB project.

To get things started, I created a div that will eventually contain the map. Then, I linked to the .css stylesheet and the .js package files:

<div id="mapdiv" style="width: 400px; height: 400px;"></div>
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.3.1/dist/leaflet.css" integrity="sha512-Rksm5RenBEKSKFjgI3a41vrjkw4EVPlJ3+OiI65vTjIdo9brlAacEuKOiQ5OFh7cOI1bkDwLqdLw3Zg0cRJAAQ==" crossorigin=""/>
<script src="https://unpkg.com/leaflet@1.3.1/dist/leaflet.js" integrity="sha512-/Nsx9X4HebavoBvEBuyp3I7od5tA0UzAxs+j83KgC8PU0kgB4XiK4Lfe4y4cgBtaRJQEIFCW+oC506aPT2L1zw==" crossorigin=""></script>

Then, a snippet of PHP code fetched necessary data from the WW2DB backend database, and used the data to build a Javascript array. The resulting Javascript array that I built looked something like this:

var rawDataArray = [
	{ "name": "This is marker #1", "lat": "10", "lng": "10" },
	{ "name": "This is marker #2", "lat": "20", "lng": "20" },
	{ "name": "This is marker #3", "lat": "30", "lng": "30" },
	...
];

Now comes the main part. You will see that it is not too complex, even if have little experience with Google Maps, OpenStreetMap, or Leaflet.

// Initialize the map (parameters: initial latitude, initial longitude, zoom level)
var mymap = L.map('mapdiv').setView([0, 0], 13);

// Specify tiles
L.tileLayer('https://stamen-tiles-{s}.a.ssl.fastly.net/terrain/{z}/{x}/{y}.{ext}', {
	attribution: 'Tiles <a href="http://stamen.com">Stamen Design</a>, <a href="http://creativecommons.org/licenses/by/3.0">CC BY 3.0</a> — Data <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>',
	subdomains: 'abcd',
	minZoom: 0,
	maxZoom: 18,
	ext: 'png'
}).addTo(mymap);

// Loops through the raw data, place a point on the map, and build an array of the points
var circleArray = [];
for (var i = 0; i < rawDataArray.length; i++) {
	var rawData = rawDataArray[i];
	circle = new L.circle([rawData["lat"], rawData["lng"]], {
		color: 'red',
		fillColor: '#f03',
		fillOpacity: 0.5,
		radius: 50
	}).addTo(mymap).bindPopup(L.popup({maxHeight:200}).setContent(rawData["name"]));
	circleArray.push(circle);
}

// Adjust the map view based on the array of points, so the map won't appear to be zoomed overly in or out
var group = new L.featureGroup(circleArray);
mymap.fitBounds(group.getBounds());

Here's an example I implemented for showing WW2-era facilities found on the island of Taiwan:


While this worked for 99% of maps found on WW2DB, I discovered a small problem with Leaflet in which the .fitBounds() method did not handle things well when a single data set contains points on both sides of the International Date Line. To be fair, this is not truly a Leaflet problem; this issue is generally a headache across all mapping programs, although this is something I did not notice with Google Maps before. Here is an example demonstrating said problem involving points located in the Territory of Alaska:

I developed a work-around for this. Recall I used a PHP snippet to fetch data from the backend and to build the Javascript array for Leaflet to use. I modified the PHP code so that as I looped through the data, I would also keep counts of points with really low (I used 120 or less) or really high (I used 120 or more) longitude values. With this two counts, I could then have the PHP determine whether I would bring some points to the other side of the International Date Line by adding or subtracting 360 degrees to their longitude values. At this time, I made that determination by evaluating the number of points that would remain/would be moved. For the case of Alaska, with this logic applied, the PHP code would decide that all data points with longitude value less than 120 will have 360 added to it; for example, (54.359349,-159.66807) will become (54.359349,200.33193). After applying this work-around, the Alaska map becomes this:

Google Maps with input events

This example code updates a set of input fields with latitude-longitude values in response to mouse clicks on an instance of Google Maps. It can also redraw map markers upon typing in new coordinates.


<html>
  <head>
    <style type="text/css">
      html { height: 100% }
      body { height: 100%; margin: 10; padding: 10 }
      #map_canvas { height: 100% }
    </style>
    <script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?sensor=true"></script>
    <script type="text/javascript">
      var map;
      var currMarker = new google.maps.Marker();

      function initialize() {
        var myLatLng = new google.maps.LatLng(0, 0);
        var mapOptions = {
          center: myLatLng,
          zoom: 1,
          mapTypeId: google.maps.MapTypeId.ROADMAP
        };
        map = new google.maps.Map(document.getElementById("map_canvas"),
            mapOptions);

        google.maps.event.addListener(map, 'click', function(event) {
          placeMarker(event.latLng);
        });
      }

      function placeMarker(location) {
        currMarker.setMap(null);
        var marker = new google.maps.Marker({
            position: location,
            map: map
        });    
        currMarker=marker;

        document.getElementById("lat").value=location.lat();
        document.getElementById("lng").value=location.lng();
      }

      function updateMarker() {
        currMarker.setMap(null);
        var lat = document.getElementById("lat").value;
        var lng = document.getElementById("lng").value;
        var newLatLng = new google.maps.LatLng(lat, lng);
        var marker = new google.maps.Marker({
            position: newLatLng,
            map: map
        });    
        currMarker=marker;
      }
    </script>
  </head>
  <body onload="initialize()">
  <p>
    <div id="map_canvas" style="width:400px; height:400px"></div>
    <div id="coordinates">
      lat: <input id="lat" type="text" onchange="updateMarker()" value="0" />
      lng: <input id="lng" type="text" onchange="updateMarker()" value="0" />
    </div>
  </p>
  </body>
</html>

Below is the code in action:


lat:
lng: