From 969562c03f3ae3a6fa96f8e15a5e7a0675b32eff Mon Sep 17 00:00:00 2001 From: Vitaly Litvak Date: Tue, 14 Jul 2015 00:26:15 +0300 Subject: [PATCH] For #193 - added basic support for 'snap to roads' function --- .../client/controller/ArchiveController.java | 4 +- .../org/traccar/web/client/i18n/Messages.java | 2 + .../traccar/web/client/model/DataService.java | 2 +- .../web/client/model/DataServiceAsync.java | 2 +- .../traccar/web/client/view/ArchiveView.java | 12 +++- .../web/client/view/ArchiveView.ui.xml | 2 + .../web/server/model/DataServiceImpl.java | 68 ++++++++++++++++++- .../web/server/model/ExportServlet.java | 14 ++-- .../web/client/i18n/Messages.properties | 1 + .../web/client/i18n/Messages_ru.properties | 1 + 10 files changed, 93 insertions(+), 15 deletions(-) diff --git a/src/main/java/org/traccar/web/client/controller/ArchiveController.java b/src/main/java/org/traccar/web/client/controller/ArchiveController.java index 016e279e..029f1fe9 100644 --- a/src/main/java/org/traccar/web/client/controller/ArchiveController.java +++ b/src/main/java/org/traccar/web/client/controller/ArchiveController.java @@ -74,9 +74,9 @@ public void onSelected(Position position) { } @Override - public void onLoad(final Device device, Date from, Date to, boolean filter, final ArchiveStyle style) { + public void onLoad(final Device device, Date from, Date to, boolean filter, boolean snapToRoads, final ArchiveStyle style) { if (device != null && from != null && to != null) { - Application.getDataService().getPositions(device, from, to, filter, new BaseAsyncCallback>(i18n) { + Application.getDataService().getPositions(device, from, to, filter, snapToRoads, new BaseAsyncCallback>(i18n) { @Override public void onSuccess(List result) { archiveHandler.onClear(device); diff --git a/src/main/java/org/traccar/web/client/i18n/Messages.java b/src/main/java/org/traccar/web/client/i18n/Messages.java index 9b7324d2..74254e2d 100644 --- a/src/main/java/org/traccar/web/client/i18n/Messages.java +++ b/src/main/java/org/traccar/web/client/i18n/Messages.java @@ -391,4 +391,6 @@ String defaultNotificationTemplate(@Select DeviceEventType type, String overlay(); String overlayType(@Select UserSettings.OverlayType type); + + String snapToRoads(); } diff --git a/src/main/java/org/traccar/web/client/model/DataService.java b/src/main/java/org/traccar/web/client/model/DataService.java index a184d66d..8849abc7 100644 --- a/src/main/java/org/traccar/web/client/model/DataService.java +++ b/src/main/java/org/traccar/web/client/model/DataService.java @@ -58,7 +58,7 @@ public interface DataService extends RemoteService { void saveDeviceShare(Device device, Map share); - List getPositions(Device device, Date from, Date to, boolean filter); + List getPositions(Device device, Date from, Date to, boolean filter, boolean snapToRoads); List getLatestPositions(); diff --git a/src/main/java/org/traccar/web/client/model/DataServiceAsync.java b/src/main/java/org/traccar/web/client/model/DataServiceAsync.java index 8c1c69fe..1c73c217 100644 --- a/src/main/java/org/traccar/web/client/model/DataServiceAsync.java +++ b/src/main/java/org/traccar/web/client/model/DataServiceAsync.java @@ -53,7 +53,7 @@ public interface DataServiceAsync { void getLatestPositions(AsyncCallback> callback); - void getPositions(Device device, Date from, Date to, boolean filter, AsyncCallback> callback); + void getPositions(Device device, Date from, Date to, boolean filter, boolean snapToRoads, AsyncCallback> callback); void updateApplicationSettings(ApplicationSettings applicationSettings, AsyncCallback callback); diff --git a/src/main/java/org/traccar/web/client/view/ArchiveView.java b/src/main/java/org/traccar/web/client/view/ArchiveView.java index 045fb937..83ba1d2c 100644 --- a/src/main/java/org/traccar/web/client/view/ArchiveView.java +++ b/src/main/java/org/traccar/web/client/view/ArchiveView.java @@ -65,7 +65,7 @@ interface ArchiveViewUiBinder extends UiBinder { public interface ArchiveHandler { void onSelected(Position position); - void onLoad(Device device, Date from, Date to, boolean filter, ArchiveStyle style); + void onLoad(Device device, Date from, Date to, boolean filter, boolean snapToRoads, ArchiveStyle style); void onFilterSettings(); void onClear(Device device); void onChangeArchiveMarkerType(PositionIconType newMarkerType); @@ -100,6 +100,9 @@ public ContentPanel getView() { @UiField CheckBox disableFilter; + @UiField + CheckBox snapToRoads; + @UiField(provided = true) TextButton styleButtonTrackColor; @@ -235,6 +238,7 @@ public void onLoadClicked(SelectEvent event) { getCombineDate(fromDate, fromTime), getCombineDate(toDate, toTime), !disableFilter.getValue(), + snapToRoads.getValue(), new ArchiveStyle(style) ); } @@ -260,7 +264,8 @@ public void onCSVClicked(SelectionEvent event) { "?deviceId=" + (deviceCombo.getValue() == null ? null : deviceCombo.getValue().getId()) + "&from=" + jsonTimeFormat.format(getCombineDate(fromDate, fromTime)).replaceFirst("\\+", "%2B") + "&to=" + jsonTimeFormat.format(getCombineDate(toDate, toTime)).replaceFirst("\\+", "%2B") + - "&filter=" + !disableFilter.getValue(), + "&filter=" + !disableFilter.getValue() + + "&snapToRoads=" + snapToRoads.getValue(), "_blank", null); } } @@ -276,7 +281,8 @@ public void onGPXClicked(SelectionEvent event) { "?deviceId=" + (deviceCombo.getValue() == null ? null : deviceCombo.getValue().getId()) + "&from=" + jsonTimeFormat.format(getCombineDate(fromDate, fromTime)).replaceFirst("\\+", "%2B") + "&to=" + jsonTimeFormat.format(getCombineDate(toDate, toTime)).replaceFirst("\\+", "%2B") + - "&filter=" + !disableFilter.getValue(), + "&filter=" + !disableFilter.getValue() + + "&snapToRoads=" + snapToRoads.getValue(), "_blank", null); } } diff --git a/src/main/java/org/traccar/web/client/view/ArchiveView.ui.xml b/src/main/java/org/traccar/web/client/view/ArchiveView.ui.xml index 10510f4f..8f81ce21 100644 --- a/src/main/java/org/traccar/web/client/view/ArchiveView.ui.xml +++ b/src/main/java/org/traccar/web/client/view/ArchiveView.ui.xml @@ -43,6 +43,8 @@ + + diff --git a/src/main/java/org/traccar/web/server/model/DataServiceImpl.java b/src/main/java/org/traccar/web/server/model/DataServiceImpl.java index 9d1616f8..5996dc62 100644 --- a/src/main/java/org/traccar/web/server/model/DataServiceImpl.java +++ b/src/main/java/org/traccar/web/server/model/DataServiceImpl.java @@ -16,6 +16,10 @@ package org.traccar.web.server.model; import java.io.*; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLConnection; +import java.text.NumberFormat; import java.util.*; import java.util.List; @@ -29,9 +33,14 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; +import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.google.gson.stream.JsonReader; import com.google.gwt.user.server.rpc.RemoteServiceServlet; import com.google.inject.persist.Transactional; +import org.apache.commons.io.IOUtils; import org.hibernate.proxy.HibernateProxy; import org.traccar.web.client.model.DataService; import org.traccar.web.client.model.EventService; @@ -575,7 +584,7 @@ public Device removeDevice(Device device) { @Transactional @RequireUser @Override - public List getPositions(Device device, Date from, Date to, boolean filter) { + public List getPositions(Device device, Date from, Date to, boolean filter, boolean snapToRoads) { EntityManager entityManager = getSessionEntityManager(); UserSettings filters = getSessionUser().getUserSettings(); @@ -626,7 +635,62 @@ public List getPositions(Device device, Date from, Date to, boolean fi } if (add) positions.add(queryResult.get(i)); } - return new ArrayList(positions); + + positions = new ArrayList(positions); + if (snapToRoads && !queryResult.isEmpty()) { + NumberFormat lonLatFormat = NumberFormat.getNumberInstance(Locale.US); + lonLatFormat.setMinimumFractionDigits(6); + lonLatFormat.setMaximumFractionDigits(6); + + StringBuilder matchURL = new StringBuilder("http://router.project-osrm.org/match?geometry=false"); + for (Position position : positions) { + matchURL.append("&loc=").append(lonLatFormat.format(position.getLatitude())) + .append(',').append(lonLatFormat.format(position.getLongitude())) + .append("&t=").append(position.getTime().getTime() / 1000); + } + + InputStream is = null; + URLConnection conn = null; + try { + URL url = new URL(matchURL.toString()); + conn = url.openConnection(); + conn.setRequestProperty("User-Agent", "Traccar Web UI Mod (java)"); + is = conn.getInputStream(); + + JsonReader reader = new JsonReader(new InputStreamReader(is, "UTF-8")); + Gson gson = new Gson(); + reader.beginObject(); + + String name = reader.nextName(); + if (name.equals("matchings")) { + reader.beginArray(); + while (reader.hasNext()) { + JsonObject matching = gson.fromJson(reader, JsonObject.class); + JsonArray indices = matching.getAsJsonArray("indices"); + JsonArray matched_points = matching.getAsJsonArray("matched_points"); + for (int i = 0; i < indices.size(); i++) { + JsonArray latLon = matched_points.get(i).getAsJsonArray(); + int index = indices.get(i).getAsInt(); + Position snapped = new Position(positions.get(index)); + snapped.setLatitude(latLon.get(0).getAsDouble()); + snapped.setLongitude(latLon.get(1).getAsDouble()); + positions.set(index, snapped); + } + } + reader.endArray(); + } + reader.endObject(); + reader.close(); + } catch (MalformedURLException mfe) { + log("Incorrect URL", mfe); + } catch (IOException ioex) { + log("I/O error", ioex); + } finally { + IOUtils.closeQuietly(is); + } + } + + return positions; } @RequireUser diff --git a/src/main/java/org/traccar/web/server/model/ExportServlet.java b/src/main/java/org/traccar/web/server/model/ExportServlet.java index 596fe5c6..a2665c1b 100644 --- a/src/main/java/org/traccar/web/server/model/ExportServlet.java +++ b/src/main/java/org/traccar/web/server/model/ExportServlet.java @@ -68,14 +68,16 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws Se Date to = requestDateFormat.parse(req.getParameter("to")); String strFilter = req.getParameter("filter"); boolean filter = strFilter != null && strFilter.equalsIgnoreCase("true"); + String strSnapToRoads = req.getParameter("snapToRoads"); + boolean snapToRoads = strSnapToRoads != null && strSnapToRoads.equalsIgnoreCase("true"); Device device = entityManager.get().find(Device.class, deviceId); checkAccess(device); if (exportType.equals("csv")) { - csv(resp, device, from, to, filter); + csv(resp, device, from, to, filter, snapToRoads); } else if (exportType.equals("gpx")) { - gpx(resp, device, from, to, filter); + gpx(resp, device, from, to, filter, snapToRoads); } else { throw new ServletException("Unsupported export type: " + exportType); } @@ -98,7 +100,7 @@ void checkAccess(Device device) { } } - void csv(HttpServletResponse response, Device device, Date from, Date to, boolean filter) throws IOException { + void csv(HttpServletResponse response, Device device, Date from, Date to, boolean filter, boolean snapToRoads) throws IOException { response.setContentType("text/csv;charset=UTF-8"); response.setHeader("Content-Disposition", "attachment; filename=traccar-positions.csv"); @@ -108,7 +110,7 @@ void csv(HttpServletResponse response, Device device, Date from, Date to, boolea writer.println(line(SEPARATOR, "time", "valid", "latitude", "longitude", "altitude", "speed", "distance", "course", "power", "address", "other")); - for (Position p : dataService.getPositions(device, from, to, filter)) { + for (Position p : dataService.getPositions(device, from, to, filter, snapToRoads)) { writer.println(line(SEPARATOR, p.getTime(), p.getValid(), p.getLatitude(), p.getLongitude(), p.getAltitude(), p.getSpeed(), p.getDistance(), p.getCourse(), p.getPower(), p.getAddress(), p.getOther())); } } @@ -125,7 +127,7 @@ private static String line(char SEPARATOR, Object... s) { return result.toString(); } - void gpx(HttpServletResponse response, Device device, Date from, Date to, boolean filter) throws IOException, XMLStreamException { + void gpx(HttpServletResponse response, Device device, Date from, Date to, boolean filter, boolean snapToRoads) throws IOException, XMLStreamException { response.setContentType("application/gpx+xml;charset=UTF-8"); response.setHeader("Content-Disposition", "attachment; filename=traccar-positions.gpx"); @@ -169,7 +171,7 @@ void gpx(HttpServletResponse response, Device device, Date from, Date to, boolea xsw.writeCharacters("Traccar archive"); xsw.writeEndElement(); xsw.writeStartElement("trkseg"); - for (Position p : dataService.getPositions(device, from, to, filter)) { + for (Position p : dataService.getPositions(device, from, to, filter, snapToRoads)) { xsw.writeStartElement("trkpt"); xsw.writeAttribute("lat", p.getLatitude().toString()); xsw.writeAttribute("lon", p.getLongitude().toString()); diff --git a/src/main/resources/org/traccar/web/client/i18n/Messages.properties b/src/main/resources/org/traccar/web/client/i18n/Messages.properties index 1db2d77c..8d2416fd 100644 --- a/src/main/resources/org/traccar/web/client/i18n/Messages.properties +++ b/src/main/resources/org/traccar/web/client/i18n/Messages.properties @@ -30,6 +30,7 @@ defaultHashImplementation = Default password hash archive = Archive from = From to = To +snapToRoads = Snap to roads load = Load exportToCSV = Export to CSV exportToGPX = Export to GPX diff --git a/src/main/resources/org/traccar/web/client/i18n/Messages_ru.properties b/src/main/resources/org/traccar/web/client/i18n/Messages_ru.properties index b4e5684a..508e61cd 100644 --- a/src/main/resources/org/traccar/web/client/i18n/Messages_ru.properties +++ b/src/main/resources/org/traccar/web/client/i18n/Messages_ru.properties @@ -30,6 +30,7 @@ defaultHashImplementation = Хэш-функция паролей archive=Архив from=С to=По +snapToRoads = Привязка к дорогам errNoResults = В выбранном периоде нет записей errFillFields = Все поля должны быть заполнены errNoDeviceNameOrId = Пожалуйста введите имя и уникальный код