В этой статье показано, как создать географическую карту с использованием Java EE 8 MVC и Leafleft. Моя цель — заменить эту статичную скучную карту чем-то более сексуальным . Для этого я использовал новые Java EE MVC и Leaflet.
Новая платформа MVC на основе действий в Java EE будет доступна в Java EE 8. Базовую информацию об Ozark (справочную реализацию MVC) можно получить из этой статьи . Leaflet — это библиотека JavaScript с открытым исходным кодом для интерактивных карт.
Для разработки приложений я использую Glassfish 4.1.1 из Java EE SDK update 2. Для запуска приложения в работе я использовал Tomcat 7.
Архитектура
Приложение разделено (как требует шаблон проектирования MVC) на три уровня:
-
Контроллер — он получает запросы от веб-браузера, получает данные о качестве воздуха и заполняет их в Model.
-
Модель является контейнером для наших данных. Он может извлекать данные в виде строки Geo JSON.
-
Просмотр — это стандартная страница JSP. Он получает данные из модели и выводит данные в браузер.
заявка
Java EE MVC основан на JAX-RS. Итак, создать приложение просто с помощью аннотации:
@ApplicationPath("ozark")
public class GettingStartedApplication extends Application
{
}
контроллер
Данные о качестве воздуха (в ближайшее время AQ), которые я хочу отобразить на карте, находятся в этом файле JSON , который обновляется каждый час. Вы можете использовать онлайн- просмотрщик JSON, чтобы узнать его структуру. Здесь есть легенда (индекс качества воздуха) и информация об измерительных станциях и индексе качества воздуха.
Роль контроллера — читать и анализировать этот JSON и заполнять необходимую информацию в MapModel, которая вводится 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;
}
}
Последняя строка метода map () перенаправляет обработку в наш View .
модель
Модель представлена тремя классами:
-
AirQualityLegendItem представляет один элемент легенды. Он содержит значение индекса AQ, его цвета и текстовое описание.
-
AirQualityMeasuringStation представляет собой станцию измерения качества воздуха и содержит уникальный код, имя, положение WGS-84 и значение индекса AQ.
-
MapModel — сама модель. Содержит список станций и легенды.
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
}
Класс модели карты аннотируется как @RequestScoped и имеет имя для 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 = <c:out value="${mapModel.geoJson}" escapeXml="false"/> ;</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;
}
}
Важный метод getGeoJson () будет вызываться из View, и он возвращает GEO JSON как String . Для каждой станции создается функция с этой структурой:
{
"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]}
}
Посмотреть
Наконец, mapView.jsp отображается. Это не весь его контент, а только важнейшие части.
В головной части Leafleft JavaScript и CSS включены:
<%@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>
Затем нам нужно вставить карту div на страницу:
<div id="map" style="height: 800px; position: relative; padding: 0px; margin: 0 auto 0 auto;"></div>
И последнее, но не менее важное: нам нужен код JavaScript для инициализации карты. Важнейшая часть этой строки:
var geojson = <c: out value = «$ {mapModel.geoJson}» escapeXml = «false» />;
… как вызывается метод MapModel getGeoJson () . Затем обрабатывается GEO JSON. Для каждого созданного объекта очки передаются в круги с соответствующими цветами.
<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 © <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>
Резюме
Создать простое приложение с будущими инфраструктурами Java MVC очень просто. Я был очень приятно удивлен скоростью разработки.
Бегущий на Glassfish
Glassfish 4.1.1 содержит все необходимые технологии, а не Ozark. Я использую инструменты Eclipse для разработки (не Maven), поэтому я помещаю только в файлы / WebContent / WEB-INF / lib файлы ozark-1.0.0-m02.jar и javax.mvc-api-1.0-edr2.jar .
Вы можете найти полный исходный код проекта Eclipse для Glassfish здесь .
Работает на Apache Tomcat 7
Здесь ситуация намного сложнее. Нам нужно скачать реализацию CDI WELD ( weld-servlet.jar ), JAX-RS API Jersey и включить в приложение с помощью jstl-1.2_1.jar и javax.json.jar, как показано на рисунке выше.
jersey-cdi * .jar ‘позаимствованы’ у Glassfish 4.1.1.
Заставь CDI работать
Создайте 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>
Получить Джерси на работу
В web.xml добавьте этот контент:
<?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>
На Tomcat путь к приложению: http: // хост: порт / OzarkMapApp / rest / map