> > #2 – Indoor Mapping with Google Maps API and GeoJSON

#2 – Indoor Mapping with Google Maps API and GeoJSON

This post describes the works I have done during the first week of “coding period” of my Google Summer of Code 2017 project.

This week I have created a proof of concept web application which demonstrate custom indoor mapping using Google Maps API. I have used the GeoJSON format for encoding the various geographic data which represent different features such as ‘room’, ‘corridor’, ‘stairs’, etc.

Following are the main tasks involved in this process:

  1. Choosing a Building and Mapping Features
  2. Exporting Data to GeoJSON Format
  3. Importing GeoJSON to Google Maps

1.  Choosing a Building and Mapping Features

Here we choose our target building and then add indoor features to it. Every indoor elements such as “rooms” and “doors” must be mapped to one of the three geometry primitives – Point, LineString and Polygon or their “multipart” counterparts – MultiPoint, MultiLineString and MultiPolygon.

The geometry primitives, multipart geometries and the corresponding GeoJSON representation are depicted in following figures:

Primitive Geometries - geojson
Primitive Geometries [from Wikipedia ]

Multipart Geometries - Geojson
Multipart Geometries [from Wikipedia ]
I used the software called JOSM Editor (Java OpenStreetMap Editor) for the mapping purpose. It is available for free here.

In JOSM Editor, first we have to download the map data which encapsulate our building. For this, go to: File -> Download from OSM

Then draw a rectangle around the the building and click the “Download” button.

josm editor-download mapDoing so will open the selected area in the editor without the base map. Whatever drawing we perform in the editor is automatically geo-referenced. The only feature we have now in our editor is the Building. By selecting the building we can see the tag(s) attached to the building (building=yes).

Tags are utmost important as they define the properties of the feature. We can use the “Draw nodes” tool to draw the indoor elements. I have mapped the following features in the map:

  • Room
  • Corridor
  • Door
  • Stair

I have used polygons to represent rooms and corridors and points to represent doors and stairs.

I have used the Simple Indoor Tagging Schema for tagging features. The tags used for each of these features are tabulated below:

Feature Tags Example
Building building=name_of_building
levels=no_of_floors_available
max_level=highest_floor/level
min_level=lowest_floor/level
ref=unique_identifier
building=paying guest
levels=2
max_level=1
min_level=0
ref=1
Room indoor=room
level=the_floor_level_no
ref=unique_identifier
indoor=room
level=0
ref=3
Corridor amenity=facility_available
entrance=has_entrance_or_not
indoor=corridor
level=the_floor_level_no
ref=unique_identifier
amenity=bicycle_parking
entrance=yes
indoor=corridor
level=0
ref=2
Door door=yes_or_no
level=the_floor_level_no
ref=unique_identifier
door=yes
level=0
ref=8
Stair indoor=room_or_area
level=from_level_to_level
ref=unique_identifier
stairs=yes
indoor=room
level=0-1
ref=26
stairs=yes

It is to be noted that all features in level 0 (floor 0) are tagged with level=0, and all features in level 1 with level=1. This is important as we use it in the GUI to switch between levels.

After mapping all the features in level 0, our map now look like this:

Now, we have to map the level 1. But, before start mapping, we have to hide the level 0 features to avoid confusion. Use the “Filters” to hide the level 0 features:

2. Exporting Data to GeoJSON Format

After mapping every features, save the file to GeoJSON file format.

Note: Drawing a closed structure (a room, for instance) is not sufficient for JOSM Editor to recognize it as a polygon. That means the created GeoJSON file will contain LineString instead of Polygon. But adding a buidling=name, or amenity=police tag will convert the closed line string to polygon. Later, we may delete those unwanted tags manually from GeoJSON. I am not quite sure if there is another standard way to solve this problem.

3. Importing GeoJSON to Google Maps

I have used the Google Map API’s Data Layer to import our GeoJSON data to the map.

The code to import the GeoJSON is as simple as this:

map.data.loadGeoJson('pg.json');

This will import the raw data (pg.json) as a layer to the map. We can further improve the look and feel of the features by adding styles as described here.

3.1 Adding Layer Switch Buttons to Map

So far we have just imported all of the data to the map, now to turn it into an indoor map, we have to show only the features of a single layer at any time. Obviously, we have to add button controls so that we can switch between the levels. Following is the function I created to generate a single button control for a level:

function LevelControl(controlDiv, map, targetLayerNo) {
  //clear current style
  map.data.setStyle({});
  // Set CSS for the control border.
  var controlUI = document.createElement('div');
  controlUI.style.backgroundColor = '#fff';
  controlUI.style.border = '2px solid #fff';
  controlUI.style.borderRadius = '3px';
  controlUI.style.boxShadow = '0 2px 6px rgba(0,0,0,.3)';
  controlUI.style.cursor = 'pointer';
  controlUI.style.marginRight = '10px';
  controlUI.style.textAlign = 'center';
  controlUI.title = 'Click to view Level ' + targetLayerNo;
  controlDiv.appendChild(controlUI);

  // Set CSS for the control interior.
  var controlText = document.createElement('div');
  controlText.style.color = 'rgb(25,25,25)';
  controlText.style.fontFamily = 'Roboto,Arial,sans-serif';
  controlText.style.fontSize = '16px';
  controlText.style.padding = '7px';
  controlText.innerHTML = targetLayerNo.toString();
  controlUI.appendChild(controlText);


  // Setup the click event listeners to change the level on map
  controlUI.addEventListener('click', function() {
    // this is the custom control in which the properties are appended
    var infoBar = document.getElementById('infoBar');
    // clear current contents
    infoBar.innerHTML = "";
    map.data.setStyle(function(feature) {
        // get level
        var level = feature.getProperty('level');
        var color = 'green';
        // all features of the input layer is made visible,
        // others hidden.
        var visibility = level == targetLayerNo ? true : false;
        return {
          icon: '/images/door.png',
          fillColor: color,
          strokeColor: color,
          strokeWeight: 1,
          visible: visibility
        };
    });
  });
}

In the initMap() function, we create two controls for level zero and level one respectively, and then add them to map.

// create a custom control for first layer switch button
var layerOneControlDiv = document.createElement('div');
var layerOneControl = new LevelControl(layerOneControlDiv, map, 0);

// custom control for second layer
var layerTwoControlDiv = document.createElement('div');
var layerTwoControl = new LevelControl(layerTwoControlDiv, map, 1);
// place the custom controls in map
map.controls[google.maps.ControlPosition.RIGHT_BOTTOM].push(layerOneControlDiv);
map.controls[google.maps.ControlPosition.RIGHT_BOTTOM].push(layerTwoControlDiv);

3.2 Adding Feature Info Bar to Map

I have created yet another control to display the tags attached to a feature whenever we click on it. The following function will create this control:

// to create a custom control for showing feature properties
function InfoControl(controlDiv, map) {
  // Set CSS for the control border.
  var controlUI = document.createElement('div');
  controlUI.style.backgroundColor = '#fff';
  controlUI.style.border = '2px solid #fff';
  controlUI.style.borderRadius = '3px';
  controlUI.style.boxShadow = '0 2px 6px rgba(0,0,0,.3)';
  controlUI.style.cursor = 'pointer';
  controlUI.style.marginBottom = '20px';
  controlUI.style.width = '200px';  
  controlDiv.appendChild(controlUI);

  // Set CSS for the control interior.
  var controlText = document.createElement('div');
  controlText.style.color = 'rgb(25,25,25)';
  controlText.style.fontFamily = 'Roboto,Arial,sans-serif';
  controlText.style.fontSize = '16px';
  controlText.style.padding = '7px';
  controlText.id = 'infoBar';
  controlUI.appendChild(controlText);
}

As before, we have to add this control to map:

// custom control for showing feature properties/tags
var infoBarControlDiv = document.createElement('div');
var infoBarControl = new InfoControl(infoBarControlDiv, map);
// place the custom controls in map
map.controls[google.maps.ControlPosition.BOTTOM_CENTER].push(infoBarControlDiv);

Now the real challenge is to update our Info Bar dynamically on click of a feature. To do this, I have captured the onClick event of all features and did some simple coding to highlight the clicked feature and most importantly, get it’s tags and add them to Info Bar:

// event handlers for click function on features
map.data.addListener('click', function(event) {
  // this is the custom control in which the properties are appended
  var infoBar = document.getElementById('infoBar');
  // clear the current contents
  infoBar.innerHTML = "";
  // clear highlight from previously clicked feature
  map.data.revertStyle();
  // highlight the clicked feature
  map.data.overrideStyle(event.feature,
    {strokeWeight: 2, strokeColor: 'yellow', fillColor: 'yellow'});

  // loop through the properties of the feature
  event.feature.forEachProperty(function(value, name) {
    //append it to the infoBar
    infoBar.insertAdjacentHTML('afterbegin', "<strong>" + name + ": </strong>" + value + "<br>");
  })
});

That’s it!

Our simple indoor map is up and running:

The complete code is available in my github repository here.

The following two tabs change content below.

Vipin Raj

Vipin Raj is a software developer specialized in PostgreSQL Database and Data Modeling, the man behind technobytz and an IoT and Security enthusiast. Having 3+ years of experience in the IT industry, he is currently pursuing his masters in computer science and information security. He spend his free time writing blog posts with the intension of sharing his knowledge to the tech community.

One thought on “#2 – Indoor Mapping with Google Maps API and GeoJSON

Leave a Reply

Your email address will not be published. Required fields are marked *