DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Last call! Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • What Do We Know About Tomcat 9.0
  • A Systematic Approach for Java Software Upgrades
  • Java EE 6 Pet Catalog with GlassFish and MySQL
  • Redis-Based Tomcat Session Management

Trending

  • Mastering Advanced Traffic Management in Multi-Cloud Kubernetes: Scaling With Multiple Istio Ingress Gateways
  • AI's Dilemma: When to Retrain and When to Unlearn?
  • Comprehensive Guide to Property-Based Testing in Go: Principles and Implementation
  • Unlocking Data with Language: Real-World Applications of Text-to-SQL Interfaces
  1. DZone
  2. Coding
  3. Java
  4. Java EE 8 MVC + Leaflet Map Demo

Java EE 8 MVC + Leaflet Map Demo

Replace static maps with Java EE 8's new MVC features and Leaflet, an open source JavaScript library for interactive maps.

By 
Lumir Vanek user avatar
Lumir Vanek
·
Nov. 23, 15 · Tutorial
Likes (4)
Comment
Save
Tweet
Share
22.5K Views

Join the DZone community and get the full member experience.

Join For Free

This article shows how to create a geographic map using Java EE 8 MVC and Leafleft. My goal is to replace this static boring map with something more sexy. I used the new Java EE MVC and Leaflet for this stuff.

Java EE's new action-based MVC framework will be available in Java EE 8. You can get basic information about Ozark (MVC reference implementation) from this article. Leaflet is an open-source JavaScript library for interactive maps.

For application development, I use Glassfish 4.1.1 from Java EE SDK update 2. For running the app in production, I used Tomcat 7.

Architecture

Application is divided (as MVC design pattern requires) to three layers:

  • Controller — it gets requests from a web browser, obtains air quality data, and fills it into Model.

  • Model is the container for our data. It's able to retrieve data as a Geo JSON string.

  • View is a standard JSP page. It gets data from Model and renders output to the browser.

Application

Java EE MVC is based on JAX-RS. So creating an application is simple using annotation:

@ApplicationPath("ozark")
public class GettingStartedApplication extends Application 
{
}

Controller

Data about air quality (shortly AQ) that I want to display on map is in this JSON file, updated every hour. You can use an online JSON viewer to discover its structure. There is a legend (air quality index) and information about measuring stations and air quality index stated here.

The role of Controller is to read and parse this JSON and populate required information to MapModel that is injected by CDI.

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;
import java.nio.charset.StandardCharsets;

import javax.inject.Inject;
import javax.json.Json;
import javax.json.JsonArray;
import javax.json.JsonObject;
import javax.json.JsonReader;
import javax.mvc.annotation.Controller;
import javax.ws.rs.GET;
import javax.ws.rs.Path;

/**
 * The MVC Controller. It waits for HTTP GET requests on his Path: 
 * <code>http://host:port/AppName/@ApplicationPath/@Path</code>
 * 
 * @author lvanek
 *
 */
@Controller
@Path("map")
public class MapController 
{
    /**
     * The MVC model
     */
    @Inject
    MapModel mapModel;


    /**
     * Method responds to HTTP GET requests
     * 
     * @return View path
     */
    @GET
    public String map()
    {
    createModel();

    return "/WEB-INF/jsp/mapView.jsp";
    }

    /**
     * Read JSON with Air quality data and populate MVC model
     * 
     * Use:
     * <code>http://jsonviewer.stack.hu/#http://portal.chmi.cz/files/portal/docs/uoco/web_generator/aqindex_eng.json</code><p>
     * for JSON browsing
     * 
     */
    private void createModel()
    {
    try
    {
    URL vvv = new URL("http://vvv.chmi.cz/uoco/aqindex_eng.json"); 
    URLConnection urlConnection = vvv.openConnection();

    try (final BufferedReader reader = new BufferedReader(new InputStreamReader(urlConnection.getInputStream(), StandardCharsets.UTF_8.displayName()))) 
    {
    JsonReader jsonReader = Json.createReader(reader);
    final JsonObject root = (JsonObject) jsonReader.read();

    mapModel.setActualized(root.getString("Actualized"));

    final JsonArray jsonLegend = root.getJsonArray("Legend");

    for (int i = 0; i < jsonLegend.size(); i++)
    {
    JsonObject jsonAqIndex = jsonLegend.getJsonObject(i);

    mapModel.putAirQualiryLegendItem(jsonAqIndex.getInt("Ix"), new AirQualiryLegendItem(jsonAqIndex.getInt("Ix"), 
    jsonAqIndex.getString("Color"), 
    jsonAqIndex.getString("ColorText"), 
    jsonAqIndex.getString("Description")));
    }

    final JsonArray jsonStates = root.getJsonArray("States");

    for (int s = 0; s < jsonStates.size(); s++)
    {
    final JsonObject jsonState = jsonStates.getJsonObject(s);
    final JsonArray jsonRegions = jsonState.getJsonArray("Regions");

    for (int i = 0; i < jsonRegions.size(); i++)
    {
    final JsonObject jsonRegion = jsonRegions.getJsonObject(i);

    final JsonArray jsonStations = jsonRegion.getJsonArray("Stations");
    for (int j = 0; j < jsonStations.size(); j++)
    {
    final JsonObject jsonStation = jsonStations.getJsonObject(j);

    if ((jsonStation.containsKey("Lat")) && (jsonStation.containsKey("Lon"))) // 'Prague center' and 'Prague periphery' not have position and components list
    {
    String wgs84Latitude = jsonStation.getString("Lat");
    String wgs84Longitude = jsonStation.getString("Lon");

    mapModel.addAirQualityMeasuringStation(new AirQualityMeasuringStation(jsonStation.getString("Code"),
    jsonStation.getString("Name"),
    jsonStation.getString("Owner"), 
    jsonStation.getString("Classif"), 
    Float.valueOf(wgs84Latitude), 
    Float.valueOf(wgs84Longitude), 
    jsonStation.getInt("Ix")));
    }
    }
    }
    }
    } 
    }
    catch (Exception e) 
    {
    e.printStackTrace();
    }
    }

    /**
     * Substitute an empty String, when a null value is encountered.
     * 
     * @param source String
     * @return Original string, or empty string, if parameter is null
     */
    static String nvl(String source)
    {
    return (source == null) ? "" : source;
    }
}

Last line of method map() redirects processing to our View.

Model

Model is represented by three classes:Air quality measuring station

  • AirQualityLegendItem represents one Legend item. It contains an AQ index value, its colors, and text description.

  • AirQualityMeasuringStation represents an Air Quality measuring station, and contains unique code, name, WGS-84 position, and AQ index value.

  • MapModel — the model itself. It contains a list of stations and legend.

public class AirQualityLegendItem 
{
private final int index;
private final String color;
private final String colorText;
private final String description;

/**
 * Constructor
 * 
 * @param index Air quality index value
 * @param color Background color
 * @param colorText Text color
 * @param description Index description
 */
public AirQualityLegendItem(int index, String color, String colorText, String description)
{
super();
this.index = index;
this.color = color;
this.colorText = colorText;
this.description = description;
}

// getters deleted for shorting
}


public class AirQualityMeasuringStation
{
private final String code;
private final String name;
private final String owner;
private final String classification;
private final float wgs84Latitude;
private final float wgs84Longitude;
private final int index;

/**
 * Constructor
 * 
 * @param code Station unique code
 * @param name Station name
 * @param owner Station owner
 * @param classification Station classification
 * @param wgs84Latitude WGS-84 latitude
 * @param wgs84Longitude WGS-84 longitude
 * @param index Air quality index value
 */
public AirQualityMeasuringStation(String code, String name, String owner, String classification, float wgs84Latitude, float wgs84Longitude, int index) 
{
super();
this.code = code;
this.name = name;
this.owner = owner;
this.classification = classification;
this.wgs84Latitude = wgs84Latitude;
this.wgs84Longitude = wgs84Longitude;
this.index = index;
} 

  // getters deleted for shorting
}


The map model class is anotated as @RequestScoped, and has a name for CDI.

/**
 * The MVC model. Contains data for view:<p/>
 * <code>/WebContent/WEB-INF/jsp/mapView.jsp</code>
 * 
 * @author lvanek
 *
 */
@Named(value="mapModel")
@RequestScoped
public class MapModel 
{
/**
 *  Air quality indexes, key is index value 
 */
private Map<Integer, AirQualityLegendItem> legend = new HashMap<>(8);

/**
 * Air quality measuring stations
 */
private List<AirQualityMeasuringStation> stations = new ArrayList<>();

/**
 * Date of Air quality index actualization
 */
private String actualized;

public String getActualized() {
return actualized;
}

public void setActualized(String actualized) {
this.actualized = actualized;
}

private static final JsonBuilderFactory bf = Json.createBuilderFactory(null);

public Map<Integer, AirQualityLegendItem> getLegend() {
return legend;
}

public void putAirQualiryLegendItem(int index, AirQualityLegendItem item)
{
legend.put(index, item);
}

public AirQualityLegendItem getAirQualiryLegendItem(int index)
{
return legend.get(index);
}

public void addAirQualityMeasuringStation(AirQualityMeasuringStation station)
{
stations.add(station);
}

/**
 * Create Geo JSON with Air quality data. It's requested in mapView.jsp by:<p/>
 * 
 * <code>var geojson = &lt;c:out value="${mapModel.geoJson}" escapeXml="false"/&gt; ;</code>
 * 
 * @return Geo JSON string
 */
public String getGeoJson()
{
JsonObjectBuilder featureCollection = bf.createObjectBuilder().add("type", "FeatureCollection");

JsonArrayBuilder features = Json.createArrayBuilder();

createFeatures(features);

featureCollection.add("features", features);

JsonObject geoJson = featureCollection.build();

return geoJson.toString();
}

/**
 * Populate given GEO JSON features list with Air quality stations data
 * 
 * @param features GEO JSON features
 */
private void createFeatures(JsonArrayBuilder features)
{
/*
 * Sort stations by Air quality index to have worst ones on top layer
 */
Comparator<AirQualityMeasuringStation> comparator = new Comparator<AirQualityMeasuringStation>()
{
public int compare(AirQualityMeasuringStation a1, AirQualityMeasuringStation a2) 
{
return Integer.compare(a1.getIndex(), a2.getIndex());
}
};


Collections.sort(stations, comparator);

/*
 * Traverse stations and create Geo JSON Features
 */
for(AirQualityMeasuringStation station : stations)
{
String title = station.getCode() + " - " + station.getName();

AirQualityLegendItem legendItem = getAirQualiryLegendItem(station.getIndex());

features.add(createFeature(title,
"#" + legendItem.getColorText(),
"#" + legendItem.getColor(), 
station.getWgs84Latitude(), 
station.getWgs84Longitude(),
station.getClassification(),
legendItem.getDescription()));
}
}

/**
 * Create Geo JSON feature
 * 
 * @param title Title for popup
 * @param color Marker text color
 * @param fillColor Marker background color
 * @param wgs84Latitude WGS-84 Latitude
 * @param wgs84Longitude WGS-84 Longitude
 * @param classification Locality classification
 * @param description Air quality index description
 * @return Geo JSON feature
 */
private JsonObjectBuilder createFeature(String title, String color, String fillColor, float wgs84Latitude, float wgs84Longitude, String classification, String description)
{
JsonObjectBuilder feature = Json.createObjectBuilder()
    .add("type", "Feature");

// Feature properties
JsonObjectBuilder properties = Json.createObjectBuilder()
         .add("popupContent", title)
         .add("description", description)
         .add("classification", classification)
         .add("style", Json.createObjectBuilder()
         .add("weight", 1) 
         .add("color", color)
         .add("fillColor", fillColor)
         .add("fillOpacity", 0.8f)
         .add("opacity", 1));

feature.add("properties", properties);

// Feature geometry
JsonObjectBuilder geometry = Json.createObjectBuilder()
         .add("type", "Point")
         .add("coordinates", Json.createArrayBuilder().add(wgs84Longitude).add(wgs84Latitude));

feature.add("geometry", geometry);

return feature;
}
}

Crucial method getGeoJson() will be called from View, and it returns GEO JSON as String. For each station, a Feature is created with this structure:

{
  "type":"Feature",
 "properties":{
      "popupContent":"LFRTA - Frydlant",
      "description":"Index on this station is not determined",
      "classification":"rural",
      "style":{
        "weight":1,
        "color":"#000000",
        "fillColor":"#CFCFCF",
        "fillOpacity":0.800000011920929,
        "opacity":1}
    },
 "geometry":{"type":"Point",
                "coordinates":[15.0698,50.940650]}
}

View

Finally, mapView.jsp is rendered. This is not all of its content, but only the crucial parts.

In head section, Leafleft JavaScript and CSS are included:

<%@page contentType="text/html" pageEncoding="UTF-8" %>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
...

<head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <link href="./../resources/css/styles.css" rel="stylesheet" type="text/css" />
        <link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.7.5/leaflet.css" />
        <script src="http://cdn.leafletjs.com/leaflet-0.7.5/leaflet.js"></script> 
        <title>Java EE 8 MVC + Leaflet Map demo</title>
 </head>

Then, we need insert map div into page:

 <div id="map" style="height: 800px; position: relative; padding: 0px; margin: 0 auto 0 auto;"></div>


Last but not least, we need some JavaScript code to initialize the map. The crucial part is this line:

var geojson = <c:out value="${mapModel.geoJson}" escapeXml="false"/>;

...which is how MapModel method getGeoJson() called. Then the GEO JSON is processed. For each Feature created, Points are transfered to Circles with appropriate colors.

<script> 
var map = L.map('map').setView([50.0, 15.5], 8);

L.tileLayer('https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?access_token=pk.eyJ1IjoiZmx5aW5nYmlyZCIsImEiOiJjaWd1dXoycWIwYzZ6dmttNWhvdDJlaG5jIn0.I__xI-EzhnkmRI2BB-1SJg', {
maxZoom: 18,
minZoom: 7,
attribution: 'Map data &copy; <a href="http://openstreetmap.org">OpenStreetMap</a> contributors, ' +
'<a href="http://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>, ' +
'Imagery © <a href="http://mapbox.com">Mapbox</a>',
 id: 'xxx', // Use your own Mapbox id !
 accessToken: 'xxx' // Use your own Mapbox accessToken !
}).addTo(map);


var geojson = <c:out value="${mapModel.geoJson}" escapeXml="false"/>;

function onEachFeature(feature, layer) 
{
var popupContent = "<div style='background-color: " + feature.properties.style.fillColor + ";  padding-left: 2px; padding-right: 2px;'><p style='color: " + feature.properties.style.color + ";'>";

if (feature.properties && feature.properties.popupContent) {
popupContent += "<b> " + feature.properties.popupContent + "</b><br/>";
}

popupContent += "Classification: " + feature.properties.classification + 
"<br/>Air quality: <b>" + feature.properties.description + "</b></p></div>";

layer.bindPopup(popupContent);
}

L.geoJson(geojson, {

style: function (feature) {
return feature.properties && feature.properties.style;
},

onEachFeature: onEachFeature,

pointToLayer: function (feature, latlng) 
{
return L.circleMarker(latlng, {
radius: 8,
fillColor: feature.properties.fillColor,
color: feature.properties.color, 
weight: feature.properties.weight,
opacity: feature.properties.opacity,
fillOpacity: feature.properties.fillOpacity
});
}
}).addTo(map);


</script>

Summary

Creating a simple application with upcoming Java MVC frameworks is very straightforward. I was very pleasantly surprised by the speed of development.

Running application

Running on Glassfish

Glassfish 4.1.1 contains all required technologies, instead of Ozark. I use Eclipse tooling for development (not Maven), so I only put into /WebContent/WEB-INF/lib files ozark-1.0.0-m02.jar and javax.mvc-api-1.0-edr2.jar.

You can find the full source code of the Eclipse project for Glassfish here.

Running on Apache Tomcat 7Libraries on Tomcat

Here, the situation is much more difficult. We need to download CDI implementation WELD (weld-servlet.jar), JAX-RS API Jersey, and incorporate into an application with jstl-1.2_1.jar and javax.json.jar as in the above image.

jersey-cdi*.jar's are 'borrowed' from Glassfish 4.1.1.

Get CDI to work

Create WebContent/META-INF/context.xml:

<?xml version="1.0" encoding="UTF-8"?>
<Context reloadable="true">

    <!-- Default set of monitored resources -->
    <WatchedResource>WEB-INF/web.xml</WatchedResource>

   <Resource name="BeanManager"
      auth="Container"
      type="javax.enterprise.inject.spi.BeanManager"
      factory="org.jboss.weld.resources.ManagerObjectFactory"/>
</Context>

Get Jersey to work

On web.xml, add this content:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xmlns="http://java.sun.com/xml/ns/javaee" 
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" 
id="IskoOzarkApp"
version="3.0">

<display-name>OzarkMapApp</display-name>
<welcome-file-list>
    <welcome-file>index.html</welcome-file>
  </welcome-file-list>

<servlet>
<servlet-name>Jersey REST Service</servlet-name>
<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>


<!-- Register JAX-RS Application, if needed. -->
        <init-param>
            <param-name>javax.ws.rs.Application</param-name>
            <param-value>learning.javaee8.mvc.GettingStartedApplication</param-value>
        </init-param>

<!-- Register resources and providers -->
<init-param>
<param-name>jersey.config.server.provider.packages</param-name>
<param-value>idea.isko.mvc.map</param-value>
</init-param>

<!-- Enable Tracing support. -->
        <init-param>
            <param-name>jersey.config.server.tracing</param-name>
            <param-value>ALL</param-value>
        </init-param>

<load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
<servlet-name>Jersey REST Service</servlet-name>
<url-pattern>/rest/*</url-pattern>
</servlet-mapping>

  <resource-env-ref>
  <description>
      Needed to add CDI to Tomcat via Weld 2. It assumes that you have added weld-servlet.jar to WEB-INF/lib and that you
      edited META-INF/context.xml (or the default context.xml) and added a Resource entry for CDI. 

      See http://jsf2.com/using-cdi-and-jsf-2.2-faces-flow-in-tomcat/
    </description>
    <resource-env-ref-name>BeanManager</resource-env-ref-name>
    <resource-env-ref-type>javax.enterprise.inject.spi.BeanManager</resource-env-ref-type>
  </resource-env-ref>

</web-app>

On Tomcat, application path is: http://host:port/OzarkMapApp/rest/map

Java EE Java (programming language) Leaflet (software) Apache Tomcat application

Opinions expressed by DZone contributors are their own.

Related

  • What Do We Know About Tomcat 9.0
  • A Systematic Approach for Java Software Upgrades
  • Java EE 6 Pet Catalog with GlassFish and MySQL
  • Redis-Based Tomcat Session Management

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!