diff --git a/src/main/java/org/traccar/web/server/model/GPXParser.java b/src/main/java/org/traccar/web/server/model/GPXParser.java new file mode 100644 index 00000000..d84f0046 --- /dev/null +++ b/src/main/java/org/traccar/web/server/model/GPXParser.java @@ -0,0 +1,130 @@ +/* + * Copyright 2015 Vitaly Litvak (vitavaque@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.web.server.model; + +import org.apache.commons.lang3.StringEscapeUtils; +import org.traccar.web.shared.model.Device; +import org.traccar.web.shared.model.Position; + +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; +import java.io.InputStream; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.*; + +public class GPXParser { + public static class Result { + Position latestPosition; + List positions; + } + + public Result parse(InputStream inputStream, Device device) throws XMLStreamException, ParseException { + Result result = new Result(); + + TimeZone tz = TimeZone.getTimeZone("UTC"); + DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); + DateFormat dateFormatWithMS = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); + + dateFormat.setTimeZone(tz); + dateFormatWithMS.setTimeZone(tz); + + XMLStreamReader xsr = XMLInputFactory.newFactory().createXMLStreamReader(inputStream); + + result.positions = new LinkedList(); + Position position = null; + Stack extensionsElements = new Stack(); + boolean extensionsStarted = false; + + while (xsr.hasNext()) { + xsr.next(); + if (xsr.getEventType() == XMLStreamReader.START_ELEMENT) { + if (xsr.getLocalName().equalsIgnoreCase("trkpt")) { + position = new Position(); + position.setLongitude(Double.parseDouble(xsr.getAttributeValue(null, "lon"))); + position.setLatitude(Double.parseDouble(xsr.getAttributeValue(null, "lat"))); + position.setValid(Boolean.TRUE); + position.setDevice(device); + } else if (xsr.getLocalName().equalsIgnoreCase("time")) { + if (position != null) { + String strTime = xsr.getElementText(); + if (strTime.length() == 20) { + position.setTime(dateFormat.parse(strTime)); + } else { + position.setTime(dateFormatWithMS.parse(strTime)); + } + } + } else if (xsr.getLocalName().equalsIgnoreCase("ele") && position != null) { + position.setAltitude(Double.parseDouble(xsr.getElementText())); + } else if (xsr.getLocalName().equalsIgnoreCase("address") && position != null) { + position.setAddress(StringEscapeUtils.unescapeXml(xsr.getElementText())); + } else if (xsr.getLocalName().equalsIgnoreCase("speed") && position != null) { + position.setSpeed(Double.parseDouble(xsr.getElementText())); + } else if (xsr.getLocalName().equalsIgnoreCase("power") && position != null) { + position.setPower(Double.parseDouble(xsr.getElementText())); + } else if (xsr.getLocalName().equalsIgnoreCase("course") && position != null) { + position.setCourse(Double.parseDouble(xsr.getElementText())); + } else if (xsr.getLocalName().equalsIgnoreCase("other") && position != null) { + position.setOther(StringEscapeUtils.unescapeXml(xsr.getElementText())); + } else if (xsr.getLocalName().equalsIgnoreCase("extensions")) { + extensionsStarted = true; + } else if (position != null && extensionsStarted) { + extensionsElements.push(xsr.getLocalName()); + } + } else if (xsr.getEventType() == XMLStreamReader.END_ELEMENT) { + if (xsr.getLocalName().equalsIgnoreCase("trkpt")) { + if (position.getOther() == null) { + position.setOther("gpx_import"); + } + + if (position.getOther().endsWith("")) { + position.setOther(position.getOther().substring(0, position.getOther().length() - 7)); + } else { + position.setOther("gpx_import" + position.getOther()); + } + position.setOther(position.getOther() + "" + + (result.positions.isEmpty() ? "import_start" : "import") + ""); + + result.positions.add(position); + if (result.latestPosition == null || result.latestPosition.getTime().compareTo(position.getTime()) < 0) { + result.latestPosition = position; + } + position = null; + } else if (xsr.getLocalName().equalsIgnoreCase("extensions")) { + extensionsStarted = false; + } else if (extensionsStarted) { + extensionsElements.pop(); + } + } else if (extensionsStarted && xsr.getEventType() == XMLStreamReader.CHARACTERS && !xsr.getText().trim().isEmpty() && !extensionsElements.empty()) { + String name = ""; + for (int i = 0; i < extensionsElements.size(); i++) { + name += (name.length() > 0 ? "-" : "") + extensionsElements.get(i); + } + position.setOther((position.getOther() == null ? "" : position.getOther()) + + "<" + name + ">" + xsr.getText() + ""); + } + } + + if (result.positions.size() > 1) { + Position last = ((LinkedList) result.positions).getLast(); + last.setOther(last.getOther().replaceFirst("import", "import_end")); + } + + return result; + } +} diff --git a/src/main/java/org/traccar/web/server/model/ImportServlet.java b/src/main/java/org/traccar/web/server/model/ImportServlet.java index 0b8ecef0..ce60d186 100644 --- a/src/main/java/org/traccar/web/server/model/ImportServlet.java +++ b/src/main/java/org/traccar/web/server/model/ImportServlet.java @@ -19,7 +19,6 @@ import org.apache.commons.fileupload.FileItemIterator; import org.apache.commons.fileupload.FileUploadException; import org.apache.commons.fileupload.servlet.ServletFileUpload; -import org.apache.commons.lang3.StringEscapeUtils; import org.traccar.web.shared.model.ApplicationSettings; import org.traccar.web.shared.model.Device; import org.traccar.web.shared.model.Position; @@ -33,9 +32,7 @@ import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import javax.xml.stream.XMLInputFactory; import javax.xml.stream.XMLStreamException; -import javax.xml.stream.XMLStreamReader; import java.io.IOException; import java.io.InputStream; import java.text.DateFormat; @@ -107,80 +104,13 @@ void gpx(Device device, InputStream inputStream, HttpServletResponse response) t try { - XMLStreamReader xsr = XMLInputFactory.newFactory().createXMLStreamReader(inputStream); - - List parsedPositions = new LinkedList(); - Position position = null; - Position latestPosition = null; + GPXParser.Result parsed = new GPXParser().parse(inputStream, device); response.getWriter().println("
");
 
             int imported = 0;
 
-            while (xsr.hasNext()) {
-                xsr.next();
-                if (xsr.getEventType() == XMLStreamReader.START_ELEMENT) {
-
-                    if (xsr.getLocalName().equalsIgnoreCase("trkpt")) {
-                        position = new Position();
-                        position.setLongitude(Double.parseDouble(xsr.getAttributeValue(null, "lon")));
-                        position.setLatitude(Double.parseDouble(xsr.getAttributeValue(null, "lat")));
-                        position.setValid(Boolean.TRUE);
-                        position.setDevice(device);
-                    } else if (xsr.getLocalName().equalsIgnoreCase("time")) {
-                        if (position != null) {
-                            String strTime = xsr.getElementText();
-                            if (strTime.length() == 20) {
-                                position.setTime(dateFormat.parse(strTime));
-                            } else {
-                                position.setTime(dateFormatWithMS.parse(strTime));
-                            }
-                        }
-                    } else if (xsr.getLocalName().equalsIgnoreCase("ele") && position != null) {
-                        position.setAltitude(Double.parseDouble(xsr.getElementText()));
-                    } else if (xsr.getLocalName().equalsIgnoreCase("address") && position != null) {
-                        position.setAddress(StringEscapeUtils.unescapeXml(xsr.getElementText()));
-                    } else if (xsr.getLocalName().equalsIgnoreCase("speed") && position != null) {
-                        position.setSpeed(Double.parseDouble(xsr.getElementText()));
-                    } else if (xsr.getLocalName().equalsIgnoreCase("power") && position != null) {
-                        position.setPower(Double.parseDouble(xsr.getElementText()));
-                    } else if (xsr.getLocalName().equalsIgnoreCase("course") && position != null) {
-                        position.setCourse(Double.parseDouble(xsr.getElementText()));
-                    } else if (xsr.getLocalName().equalsIgnoreCase("other") && position != null) {
-                        position.setOther(StringEscapeUtils.unescapeXml(xsr.getElementText()));
-                    } else if (!xsr.getLocalName().equalsIgnoreCase("extensions") && position != null) {
-                        position.setOther((position.getOther() == null ? "" : position.getOther()) +
-                        "<" + xsr.getLocalName() + ">" + xsr.getElementText() + "");
-                    }
-                } else if (xsr.getEventType() == XMLStreamReader.END_ELEMENT &&
-                           xsr.getLocalName().equalsIgnoreCase("trkpt")) {
-
-                    parsedPositions.add(position);
-                    if (latestPosition == null || position.getTime().compareTo(position.getTime()) < 0) {
-                        latestPosition = position;
-                    }
-                    position = null;
-                }
-            }
-
-            for (int i = 0; i < parsedPositions.size(); i++) {
-                position = parsedPositions.get(i);
-                StringBuilder other = new StringBuilder("gpx_import");
-                other.append("");
-                if (i == 0) {
-                    other.append("import_start");
-                } else if (i == parsedPositions.size() - 1) {
-                    other.append("import_end");
-                } else {
-                    other.append("import");
-                }
-                other.append("");
-                if (position.getOther() != null) {
-                    other.append(position.getOther());
-                }
-                other.append("");
-                position.setOther(other.toString());
-
+            for (Position position : parsed.positions) {
                 boolean exist = false;
                 for (Position existing : entityManager.get().createQuery("SELECT p FROM Position p WHERE p.device=:device AND p.time=:time", Position.class)
                         .setParameter("device", device)
@@ -200,11 +130,11 @@ void gpx(Device device, InputStream inputStream, HttpServletResponse response) t
                 }
             }
 
-            if (latestPosition != null && device.getLatestPosition() == null || device.getLatestPosition().getTime().compareTo(latestPosition.getTime()) < 0) {
-                device.setLatestPosition(latestPosition);
+            if (parsed.latestPosition != null && device.getLatestPosition() == null || device.getLatestPosition().getTime().compareTo(parsed.latestPosition.getTime()) < 0) {
+                device.setLatestPosition(parsed.latestPosition);
             }
 
-            response.getWriter().println("Already exist: " + (parsedPositions.size() - imported));
+            response.getWriter().println("Already exist: " + (parsed.positions.size() - imported));
             response.getWriter().println("Imported: " + imported);
 
             response.getWriter().println("
"); diff --git a/src/test/java/org/traccar/web/server/model/GPXParserTest.java b/src/test/java/org/traccar/web/server/model/GPXParserTest.java new file mode 100644 index 00000000..4ff77137 --- /dev/null +++ b/src/test/java/org/traccar/web/server/model/GPXParserTest.java @@ -0,0 +1,104 @@ +/* + * Copyright 2015 Vitaly Litvak (vitavaque@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.web.server.model; + +import org.junit.Test; +import org.traccar.web.shared.model.Position; + +import javax.xml.stream.XMLStreamException; +import java.text.ParseException; + +import static org.junit.Assert.*; + +public class GPXParserTest { + @Test + public void testTraccarOutput() throws XMLStreamException, ParseException { + GPXParser.Result r = new GPXParser().parse(getClass().getResourceAsStream("/org/traccar/web/server/model/test_traccar.gpx"), null); + assertNotNull(r); + assertNotNull(r.positions); + assertEquals(2, r.positions.size()); + + Position p = r.positions.get(0); + assertEquals(-1, p.getLatitude(), 0.01); + assertEquals(-3, p.getLongitude(), 0.01); + assertEquals("ADDR1", p.getAddress()); + assertEquals(5.5, p.getSpeed(), 0.01); + assertEquals(7.7, p.getCourse(), 0.01); + assertEquals(55.5, p.getAltitude(), 0.01); + assertEquals(1420096479000L, p.getTime().getTime()); + assertEquals("gps103acc onimport_start", p.getOther()); + + p = r.positions.get(1); + assertEquals(-2, p.getLatitude(), 0.01); + assertEquals(-4, p.getLongitude(), 0.01); + assertEquals("ADDR2", p.getAddress()); + assertEquals(8.8, p.getSpeed(), 0.01); + assertEquals(10.10, p.getCourse(), 0.01); + assertEquals(77.7, p.getAltitude(), 0.01); + assertEquals(1420102196000L, p.getTime().getTime()); + assertEquals("gps103acc onimport_end", p.getOther()); + + assertNotNull(r.latestPosition); + assertTrue(r.latestPosition == r.positions.get(1)); + } + + @Test + public void testForeign() throws XMLStreamException, ParseException { + GPXParser.Result r = new GPXParser().parse(getClass().getResourceAsStream("/org/traccar/web/server/model/test_foreign.gpx"), null); + assertNotNull(r); + assertNotNull(r.positions); + assertEquals(6, r.positions.size()); + + Object[][] expected = new Object[][] { + { 10.02591601, 11.01236986, 109.43, 1404758632655L }, + { 12.0259547, 13.01249098, 104.76, 1404758644193L }, + { 14.02606332, 15.01255586, 105.04, 1404758649182L }, + { 16.02613562, 17.01263875, 107.89, 1404758652179L }, + { 18.02623771, 19.01269516, 108.71, 1404758656194L }, + { 20.02631864, 21.01279424, 108.77, 1404758660181L } }; + + for (int i = 0; i < r.positions.size(); i++) { + Position p = r.positions.get(i); + assertEquals((Double) expected[i][0], p.getLatitude(), 0.00000000001); + assertEquals((Double) expected[i][1], p.getLongitude(), 0.00000000001); + assertEquals((Double) expected[i][2], p.getAltitude(), 0.0001); + assertEquals(((Long) expected[i][3]).longValue(), p.getTime().getTime()); + assertNull(p.getAddress()); + assertNull(p.getSpeed()); + assertNull(p.getCourse()); + assertEquals("gpx_importimport" + (i == 0 ? "_start" : i == r.positions.size() - 1 ? "_end" : "") + "", p.getOther()); + } + + assertNotNull(r.latestPosition); + assertTrue(r.latestPosition == r.positions.get(5)); + } + + @Test + public void testWithForeignExtensions() throws XMLStreamException, ParseException { + GPXParser.Result r = new GPXParser().parse(getClass().getResourceAsStream("/org/traccar/web/server/model/test_foreign_ext.gpx"), null); + assertEquals(1, r.positions.size()); + Position p = r.positions.get(0); + assertEquals(10.02591601, p.getLatitude(), 0.00000000001); + assertEquals(11.01236986, p.getLongitude(), 0.00000000001); + assertEquals(109.43, p.getAltitude(), 0.0001); + assertEquals(1404758632655L, p.getTime().getTime()); + assertNull(p.getAddress()); + assertNull(p.getCourse()); + assertNull(p.getPower()); + assertNull(p.getSpeed()); + assertEquals("gpx_importPID4SID4XYimport_start", p.getOther()); + } +} diff --git a/src/test/resources/org/traccar/web/server/model/test_foreign.gpx b/src/test/resources/org/traccar/web/server/model/test_foreign.gpx new file mode 100644 index 00000000..40f09186 --- /dev/null +++ b/src/test/resources/org/traccar/web/server/model/test_foreign.gpx @@ -0,0 +1,35 @@ + + + + 1 + + + 109.43 + + + + 104.76 + + + + 105.04 + + + + 107.89 + + + + 108.71 + + + + 108.77 + + + + + \ No newline at end of file diff --git a/src/test/resources/org/traccar/web/server/model/test_foreign_ext.gpx b/src/test/resources/org/traccar/web/server/model/test_foreign_ext.gpx new file mode 100644 index 00000000..69fa96ca --- /dev/null +++ b/src/test/resources/org/traccar/web/server/model/test_foreign_ext.gpx @@ -0,0 +1,24 @@ + + + + 1 + + + 109.43 + + + PID4 + SID4 + + X + Y + + + + + + \ No newline at end of file diff --git a/src/test/resources/org/traccar/web/server/model/test_traccar.gpx b/src/test/resources/org/traccar/web/server/model/test_traccar.gpx new file mode 100644 index 00000000..b7f2b299 --- /dev/null +++ b/src/test/resources/org/traccar/web/server/model/test_traccar.gpx @@ -0,0 +1,40 @@ + + + + + Traccar WEB UI + + + + + TestDevice + Archive records for TestDevice from 2014-12-31T21:01:00.000Z to 2015-02-16T20:15:00.000Z + Traccar archive + + + + 55.5 + + ADDR1 + 5.5 + 7.7 + &lt;info&gt;&lt;protocol&gt;gps103&lt;/protocol&gt;&lt;alarm&gt;acc on&lt;/alarm&gt;&lt;/info&gt; + + + + + 77.7 + + ADDR2 + 8.8 + 10.10 + &lt;info&gt;&lt;protocol&gt;gps103&lt;/protocol&gt;&lt;alarm&gt;acc on&lt;/alarm&gt;&lt;/info&gt; + + + + + \ No newline at end of file