Spatial IoT : Micro Map API Server : with embedded Tomcat, Spring, embedded H2GIS, GeoJSON on Raspberry PI

Introduction

Just for fun I’ve been trying to create a tiny GeoJSON server with smallest footprint possible. The tiny server should be able to run efficiently even from a Raspberry PI. I have a dream to expand this toy project into a full blown open source spatial project eventually.

Design Goal & Constraint

I wanted to use embedded server modules like Tomcat and H2 to make this server as lean and efficient as possible.

Developer Sandbox

  • Operating OS: Windows 7 64
  • Build Tool: Maven
  • Server Container : Embedded Tomcat 8
  • RDBMS : H2
  • Spatial Engine : H2GIS
  • Framework : Spring

Setup Build Environment

We would build this tutorial on an external USB drive with a drive letter F. Maven build tool is used with JDK 1.8. Let us first set JAVA and Maven environment. Open Windows command prompt. Assuming JDK location is at F:\jdk1.8.0_25 set JAVA_HOME. Adjust your java location in the script below if it is different.

 

F:\>SET JAVA_HOME=F:\jdk1.8.0_25
F:\>SET PATH=%JAVA_HOME%\bin;%PATH%

10

 

 

 

Check Java by running ‘java -version’ and check JAVA_HOME by typing ‘echo %JAVA_HOME%’. You should get a console out something similar to screen shot displayed below

12

 

 

 

Set environment variables for Maven.

F:\>SET MAVEN_HOME=F:\apache-maven-3.2.5
F:\>SET PATH=%MAVEN_HOME%\BIN;%PATH%

20

 

 

 

Check Maven installation by typing up ‘mvn -version’ and you should get a console out as shown below.

26

 

Create Project

 

Make a folder named “micro-map-server” to hold all project related artifacts. This folder will be the root of our Maven pom.xml based project.

 F:\> mkdir micro-map-server

30

 

 

 

Start a maven project. Running the following Maven command would generate some basic folder structure.

mvn archetyp:generate -DgroupId=com.sortedset.blog.micromapservice -DartifactId=micro-map-service -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false

 

A few folders and file artifacts will be created as a start. We would edit the generated pom.xml to add more libraries dependencies and other plugins.

E:\MICRO-MAP-SERVICE
│   pom.xml
│
└───src
    ├───main
    │   └───java
    │       └───com
    │           └───sortedset
    │               └───blog
    │                   └───micromapservice
    │                           App.java

 

pom.xml

Edit the pom.xml file to hold all JAR dependencies. In this project we will be using embedded Tomcat 8, Spring MVC with Rest controller, and embedded spatial database H2GIS. As such pom.xml dependency area shows how to reference the mentioned JAR libraries. We would also want to run this project from command line as JAR. A Maven plugin is being configured to make executable jar. Please read section where a plugin named “maven-jar-plugin” defined.

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.sortedset.blog.micromapservice</groupId>
  <artifactId>micro-map-service</artifactId>
  <packaging>jar</packaging>
  <version>1.0</version>
  <name>micro-map-service</name>
  <url>http://maven.apache.org</url>
  	<build>
		<plugins>
			<!-- To make executable jar -->
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-jar-plugin</artifactId>
				<version>2.5</version>
				<configuration>
					<!-- Configures the created archive -->
					<archive>
						<!-- Configures the content of the created manifest -->
						<manifest>
							<!-- Adds the classpath to the created manifest -->
							<addClasspath>true</addClasspath>
							<!--
								Specifies that all dependencies of our application are found
								from the lib directory.
							-->
							<classpathPrefix>lib/</classpathPrefix>
							<!-- Configures the main class of the application -->
							<mainClass>com.sortedset.blog.micromapservice.Main</mainClass>
						</manifest>
					</archive>
				</configuration>
			</plugin>

			<!-- to Copy jar to lib-->
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-dependency-plugin</artifactId>
				<version>2.1</version>
				<executions>
					<execution>
						<id>copy</id>
						<phase>install</phase>
						<goals>
							<goal>copy-dependencies</goal>
						</goals>
						<configuration>
							<outputDirectory>
								${project.build.directory}/lib
							</outputDirectory>
						</configuration>
					</execution>
				</executions>
			</plugin>

			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<configuration>
					<source>1.7</source>
					<target>1.7</target>
				</configuration>
			</plugin>

		</plugins>
	</build>

	<dependencies>
		<dependency>
		  <groupId>junit</groupId>
		  <artifactId>junit</artifactId>
		  <version>3.8.1</version>
		  <scope>test</scope>
		</dependency>

		<!-- Log4j -->
		<dependency>
			<groupId>log4j</groupId>
			<artifactId>log4j</artifactId>
			<version>1.2.17</version>
		</dependency>

		<!-- Tomcat 8 Embedded -->
		<!-- http://mvnrepository.com/artifact/org.apache.tomcat.embed -->
		<dependency>
			<groupId>org.apache.tomcat.embed</groupId>
			<artifactId>tomcat-embed-core</artifactId>
			<version>8.0.15</version>
		</dependency>
		<dependency>
			<groupId>org.apache.tomcat.embed</groupId>
			<artifactId>tomcat-embed-logging-juli</artifactId>
			<version>8.0.15</version>
		</dependency>
		<dependency>
			<groupId>org.apache.tomcat.embed</groupId>
			<artifactId>tomcat-embed-jasper</artifactId>
			<version>8.0.15</version>
		</dependency>
		<dependency>
			<groupId>org.apache.tomcat.embed</groupId>
			<artifactId>tomcat-embed-logging-log4j</artifactId>
			<version>8.0.15</version>
		</dependency>
		<dependency>
			<groupId>org.apache.tomcat.embed</groupId>
			<artifactId>tomcat-embed-websocket</artifactId>
			<version>8.0.15</version>
		</dependency>
		<dependency>
			<groupId>org.apache.tomcat.embed</groupId>
			<artifactId>tomcat-embed-el</artifactId>
			<version>8.0.15</version>
		</dependency>

		<!-- Spring -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context</artifactId>
			<version>${org.springframework-version}</version>
		</dependency>

		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-webmvc</artifactId>
			<version>${org.springframework-version}</version>
		</dependency>
        <!-- Jackson -->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>${jackson.databind-version}</version>
        </dependency>
		<!-- JavaEE APIs -->
		<dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.0.1</version>
        </dependency>

		<!-- h2gis -->
		<dependency>
			<groupId>org.orbisgis</groupId>
			<artifactId>h2spatial-ext</artifactId>
			<version>1.1.0</version>
		</dependency>

	</dependencies>

	<properties>
		<java-version>1.7</java-version>
		<org.springframework-version>4.1.4.RELEASE</org.springframework-version>
		<jackson.databind-version>2.2.3</jackson.databind-version>
	</properties>
</project>

 

Edit and Add Code in Eclipse Luna

Start Eclipse Luna. Click “File > Import > Existing Maven Project” and select maven project root folder “F:\>micro-map-service” to import project.

main.java

Enable embedded Tomcat. Create a java class named “main.java”. In this class we would define web application’s root folder location which will be named ‘webcontent’. The folder needs to be created right under the project root folder. A port number of 20577 will be added too. Finally an web application context will be created named ‘/micro-map-server’. This is the starter class that will get called when we will start our Java project. This class will start the embedded Tomcat instance.

 


package com.sortedset.blog.micromapservice;

import java.io.File;

import org.apache.catalina.startup.Tomcat;
import org.apache.log4j.Logger;

public class Main
{
private static Logger LOGGER = Logger.getLogger(Main.class);

    public static void main( String[] args ) throws Exception
    {

		String webappDirLocation = "webcontent/";
        Tomcat tomcat = new Tomcat();

        String webPort = System.getenv("PORT");
        if(webPort == null || webPort.isEmpty()) {
            webPort = "20577";
        }

        tomcat.setPort(Integer.valueOf(webPort));

		// Add web application
        tomcat.addWebapp("/micromapserver", new File(webappDirLocation).getAbsolutePath());
        LOGGER.info("configuring app with basedir: " + new File(webappDirLocation).getAbsolutePath());

        tomcat.start();
        tomcat.getServer().await();

    }
}

 

Enable Spring

AppInitializer.java

This Spring related class will register “DispatcherServlet” servlet and also add a mapping to find Spring related applications from url as “/spring/*”. This means that in order to access Spring related servlets we would need to postfix our url with /spring. This class also define a package location for Spring container to find Spring configuration class (i.e. AppConfig.java in this tutorial).


package com.sortedset.blog.micromapservice.spring;

import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.ContextLoaderListener;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration;

public class AppInitializer implements WebApplicationInitializer {

    private static final String CONFIG_LOCATION = "com.sortedset.blog.micromapservice.spring";
    private static final String MAPPING_URL = "/spring/*";

    public void onStartup(ServletContext servletContext) throws ServletException {
        WebApplicationContext context = getContext();
        servletContext.addListener(new ContextLoaderListener(context));
        ServletRegistration.Dynamic dispatcher = servletContext.addServlet("DispatcherServlet", new DispatcherServlet(context));
        dispatcher.setLoadOnStartup(1);
        dispatcher.addMapping(MAPPING_URL);
    }

    private AnnotationConfigWebApplicationContext getContext() {
        AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
        context.setConfigLocation(CONFIG_LOCATION);
        return context;
    }

}

AppConfig.java

This Spring class will configure and scan package for Spring components. In this tutorial we are searching package “com.sortedset.blog.micromapservice.spring”.

 


package com.sortedset.blog.micromapservice.spring;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan(basePackages = "com.sortedset.blog.micromapservice.spring")
class AppConfig {
}

 

WebMvcConfigurerAdapterAppConfig.java

This Spring related class will enable Spring MVC capabilities for the project. Which is a prerequisite for Spring REST.

 


package com.sortedset.blog.micromapservice.spring;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

@EnableWebMvc
@Configuration
class WebMvcConfig extends WebMvcConfigurerAdapter {
}

 

RestGisController.java

This Spring related class enables RESTful services using “@RestController” annotation. @RequestMapping annotation will configure the request to follow a specific route. In this tutorial route is “/countries/border” which will respond to a http “GET” method from client. The return type is string which will be encoded in JSON format and specially we will use GeoJSON format to send response back to client.

 

package com.sortedset.blog.micromapservice.spring;

import org.apache.log4j.Logger;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class RestGisController {

    private static Logger LOGGER = Logger.getLogger(RestGisController.class);

    @RequestMapping(value= "/countries/border", method = RequestMethod.GET)
    public String countries(@RequestParam(value="Name", defaultValue="United States") String name) {
        LOGGER.info("Request parameter : " + name);
        RestGisResource r =  new RestGisResource();
        try {
            String result = r.getCountryBorder(name);
            if(result.isEmpty()){
                return "{\"servermsg\" : \"Query for country = "+ name + " came out empty \"}" ;
            }else {
                LOGGER.info(result);
                return result;
            }
        } catch (Exception e) {
            System.out.println(e.getMessage());
            return "{\"servermsg\" : \"Server Error\"}";
        }
    }
}
 

 

RestGisResource.java

This class will actually make the database call to embedded H2GIS database. For simplicity we are not following any proper Data Access Object pattern or any ORM like Hibernate. We would use straight JDBC call to file based embedded database table and fetch the result as GeoJSON format. A simple spatial table will be used. Detail description on how to load this spatial table will be shown on later sections of this tutorial.

 


package com.sortedset.blog.micromapservice.spring;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;

public class RestGisResource {

    public String getCountryBorder(String countryName) throws Exception {

        Class.forName("org.h2.Driver");
        Connection conn = DriverManager.getConnection("jdbc:h2:file:data/myspatialdb");
        Statement stat = conn.createStatement();

        StringBuilder sb = new StringBuilder();

        ResultSet rs;
        rs = stat.executeQuery("select ST_AsGeoJSON(the_geom) , name  from world_borders where name='"+countryName+"'");
        while (rs.next()) {
            //Append to make a proper GeoJSON JSON string.
            sb.append(" { \"type\": \"FeatureCollection\", \"features\": [{ \"type\": \"Feature\",\"properties\": {\"name\": \""+ countryName +"\"},\"geometry\": ");
            sb.append(rs.getString(1) );
            sb.append("}]}");
        }
        rs.close();
        stat.close();
        conn.close();

        return sb.toString();
    }
}

 

Prepare Database with Spatial Data

H2GIS

Install H2GIS JARs

Configure embedded H2GIS. Maven pom file has been prepared with all required dependencies to embed H2 with H2GIS extensions. Running “mvn install” command should collect and put all the required JAR files under <project root>\target\lib folder. Open a command prompt and type up the following commands.

  • Open Windows command prompt.
  • Change drive letter to the USB stick. In this tutorial the drive letter is “F:”
 F:
  • Change directory to project root location. In this tutorial it is F:\micro-map-service
 cd micro-map-service
  • Run maven command “mvn install” to compile source code and copy JAR files from Maven repositories to project lib folder.
 mvn install
  • Now there should be a bunch of JAR files in location “F:\micro-map-service\target\lib”.
  • Change directory to JAR location.
 cd target\lib

Connect to H2GIS

  • Run the following command to connect to H2GIS. H2 has a nice web browser console that should spawn up after a successful connection to the database. Notice that we are running the command from the project root location (i.e. F:\micro-map-service) assuming that a Maven install command that we ran in previous step has put rquired JAR files in “F:\micro-map-service\target\lib”

 

java -cp target/lib/cts-1.3.3.jar;target/lib/h2-1.3.176.jar;target/lib/h2drivers-1.1.0.jar;target/lib/h2network-1.1.0.jar;target/lib/h2spatial-1.1.0.jar;target/lib/h2spatial-api-1.1.0.jar;target/lib/h2spatial-ext-1.1.0.jar;target/lib/jackson-core-2.2.3.jar;target/lib/java-network-analyzer-0.1.6.jar;target/lib/jdelaunay-0.5.2.jar;target/lib/jgrapht-core-0.9.0.jar;target/lib/jts-1.13.jar;target/lib/log4j-1.2.17.jar;target/lib/slf4j-api-1.6.0.jar;target/lib/spatial-utilities-1.1.0.jar org.h2.tools.Console -url jdbc:h2:file:data/myspatialdb

50

 

  • A successful execution would bring up H2 Admin console on our default browser.

H2 Admin console

 

  • To learn more about how to connect and how where H2 keeps it database file please read H2 faq at http://www.h2database.com/html/faq.html

Spatially Enable H2

  • In H2GIS web console type up the following SQL commands to enable H2 with spatial functions. The script creates SPATIAL_REF_SYS table and GEOMETRY_COLUMNS.
CREATE ALIAS IF NOT EXISTS SPATIAL_INIT FOR
 "org.h2gis.h2spatialext.CreateSpatialExtension.initSpatialExtension";
 CALL SPATIAL_INIT();

 

Add Shape Spatial Data

  • Download free spatial data from http://thematicmapping.org/downloads/world_borders.php.
  • Download TM_WORLD_BORDERS_SIMPL-0.3.zip shape file and unzip to F:\spatial_data folder.
  • Add world_borders.shp ESRI shape file from local repository. Use the following function in web console to import a shape file. A successful execution will produce a table called world_borders and should be viewable on the left panel on H2’s web console.

 

CALL SHPRead(‘F:\\spatial_data\TM_WORLD_BORDERS_SIMPL-0.3.shp’, ‘world_borders’);

 

 

Enable spatial extension.

 

 

  • Test spatial table and GeoJSON by executing the following SQL statement at H2GIS web console.

 

select ST_AsGeoJSON(the_geom) from world_borders limit 5

 

Web Content

For map display we will use Google Map api and overlay our GeoJSON country border on top of Google base map. An index.html file will be added to /webcontent folder. The index.html code is pretty self explanatory. Google Map api call “loadGeoJson” will be used to feth REST data from H2GIS.


<!DOCTYPE html >
<html>
<head>
<title>Micro Map Service Sample</title>
<!-- only force Internet Explorer 10 standards for testing on local machine -->
<meta http-equiv="X-UA-Compatible" content="IE=10" />

<style type="text/css">
html {
    height: 100%
}

body {
    height: 100%;
    margin: 0;
    padding: 0
}

#map-canvas {
    height: 500px;
    width: 800px;
    margin-left: auto;
    margin-right: auto;
    margin-top: 40px;
    background-color: #b0e0e6;
}
</style>

<!-- Map -->
<script type="text/javascript"
    src="https://maps.googleapis.com/maps/api/js?key=[ YOUR GOOGLE MAP API KEY GOES HERE ]">

</script>
<script type="text/javascript">
    var map = null;
    function initialize() {
        var querystring = window.location.search;
        var splittedString = querystring.split('=')
        var countryName = splittedString[1];

        var mapOptions = {
            center : new google.maps.LatLng(29.879382, -95.588725),
            zoom : 2
        };
        map = new google.maps.Map(document.getElementById("map-canvas"),
                mapOptions);
        map.data.loadGeoJson("/micromapserver/spring/countries/border?Name="
                + countryName);

        map.data.setStyle({
            fillColor : 'blue',
            strokeWeight : .7
        });
    }

    google.maps.event.addDomListener(window, 'load', initialize);
</script>
</head>
<body>
    <div id="map-canvas"></div>

    <script>
      (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
      (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
      m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
      })(window,document,'script','//www.google-analytics.com/analytics.js','ga');

      ga('create', 'UA-58868102-1', 'auto');
      ga('send', 'pageview');

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

 

Test

  • Let us compile and install the application into a executable jar.
  • Open a command console.
  • Change directory to to project root location. In this tutorial it is “F:\micro-map-service\
  • Execute the following maven code

 

mvn clean install
  • A successful execution should create a jar named micro-map-service-1.0.jar under “F:\micro-map-service\target\”
  • Start app by running java.
 F:\micro-map-service>java -jar target\micro-map-service-1.0.jar
  • The application should be ready at port 20577 and we should see something similar as shown here.

 

GeoJSON Data

 

 

 

Map

 

 

Deploy to Raspberry PI

Deployment to Raspberry PI with Raspbain should be fairly straight forward. Raspbian now a days comes bundled with Oracle JDK 1.8. We will just need to copy the executable JAR, H2GIS database file, JAR files, and webcontent to the target Raspberry PI to deploy the application.

Use WinSCP or something similar to connect to Raspberry PI through an SFTP connection. Create a folder at Raspberry PI that will serve as the root location. For this tutorial I’ve created folder named “micro-map-service” under /usr/bin/ folder.

The following directories were copied

  • ./webcontent
  • ./data
  • ./target
  • ./target/lib

Copy all contents from /target/lib folder but only copy the executable jar micro-map-service-1.0.jar from /target folder.

Now use putty or something similar from your laptop to Raspberry PI and execute the executable jar as shown below.

 java -jar target/micro-map-service-1.0jar

This will start the application and you can test it from a browser running from your laptop.

 

Live Test App :

I’ve installed a live app for you to play with at the following address.

To view a map type the following url

http://java.sortedset.com/micromapserver/?Name=Canada

http://java.sortedset.com/micromapserver/?Name=Brazil

 

To view GeoJSON issue a rest call as shown below.

http://java.sortedset.com/micromapserver/spring/countries/border?Name=Canada

 

GitHub Code Repository:

The whole project is available at GitHub if someone likes to download and deploy the application using maven.

https://github.com/iyusuf/micro-map-service

 

 

Resources:

Add a Comment

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