package org.androad.sys.ors.rs.yournavigation; import java.util.ArrayList; import java.util.StringTokenizer; import org.osmdroid.util.BoundingBoxE6; import org.osmdroid.util.GeoPoint; import org.androad.R; import org.androad.adt.other.GraphicsPoint; import org.androad.sys.ors.adt.Error; import org.androad.sys.ors.adt.rs.Route; import org.androad.sys.ors.adt.rs.RouteInstruction; import org.androad.sys.ors.exceptions.ORSException; import org.androad.util.constants.Constants; import org.androad.util.constants.TimeConstants; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; import android.content.Context; import android.graphics.Point; import android.util.FloatMath; import android.util.Log; public class YourNavigationRSParser extends DefaultHandler implements TimeConstants, Constants { // ==================================== // Constants // ==================================== // ==================================== // Fields // ==================================== private final ArrayList<Error> mErrors = new ArrayList<Error>(); private int mErrorNumber = 0; private Context context; private Route mRoute; private ArrayList<GeoPoint> mPolyline; private double maxLat = 0; private double maxLon = 0; private double minLat = 0; private double minLon = 0; private boolean inKml = false; private boolean inDocument = false; private boolean inName = false; private boolean inOpen = false; private boolean inDistance = false; private boolean inDescription = false; private boolean inFolder = false; private boolean inVisibility = false; private boolean inPlacemark = false; private boolean inLineString = false; private boolean inTessellate = false; private boolean inCoordinates = false; private RouteInstruction mTmpRouteInstruction; // =========================================================== // Constructors // =========================================================== public YourNavigationRSParser(Context ctx) { this.context = ctx; } // =========================================================== // Getter & Setter // =========================================================== public ArrayList<Error> getErrors(){ return this.mErrors; } public Route getRoute() throws ORSException{ if(this.mErrors != null && this.mErrors.size() > 0) { throw new ORSException(this.mErrors); } return this.mRoute; } // ==================================== // Methods from Superclasses // ==================================== @Override public void startDocument() throws SAXException { this.mRoute = new Route(); this.mRoute.setRouteInstructions(new ArrayList<RouteInstruction>()); this.mPolyline = new ArrayList<GeoPoint>(); this.mRoute.setPolyLine(this.mPolyline); super.startDocument(); } @Override public void startElement(final String uri, final String localName, final String name, final Attributes attributes) throws SAXException { this.sb.setLength(0); if(localName.equals("kml")){ this.inKml = true; } else if(localName.equals("Document")){ this.inDocument = true; } else if(localName.equals("name")){ this.inName = true; } else if(localName.equals("open")){ this.inOpen = true; } else if(localName.equals("distance")){ this.inDistance = true; } else if(localName.equals("description")){ this.inDescription = true; } else if(localName.equals("Folder")){ this.inFolder = true; } else if(localName.equals("visibility")){ this.inVisibility = true; } else if(localName.equals("Placemark")){ this.inPlacemark = true; } else if(localName.equals("LineString")){ this.inLineString = true; } else if(localName.equals("tessellate")){ this.inTessellate = true; } else if(localName.equals("coordinates")){ this.inCoordinates = true; } else { Log.w(DEBUGTAG, "Unexpected tag: '" + name + "'"); } super.startElement(uri, localName, name, attributes); } protected StringBuilder sb = new StringBuilder(); private int mLastFirstMotherPolylineIndex = 0; @Override public void characters(final char[] chars, final int start, final int length) throws SAXException { this.sb.append(chars, start, length); super.characters(chars, start, length); } @Override public void endElement(final String uri, final String localName, final String name) throws SAXException { if(localName.equals("kml")){ this.inKml = false; } else if(localName.equals("Document")){ this.inDocument = false; } else if(localName.equals("name")){ this.inName = false; } else if(localName.equals("open")){ this.inOpen = false; } else if(localName.equals("distance")){ this.inDistance = false; this.mRoute.setDistanceMeters((int)(1000 * Float.parseFloat(this.sb.toString()))); } else if(localName.equals("description")){ this.inDescription = false; } else if(localName.equals("Folder")){ this.inFolder = false; } else if(localName.equals("visibility")){ this.inVisibility = false; } else if(localName.equals("Placemark")){ this.inPlacemark = false; } else if(localName.equals("LineString")){ this.inLineString = false; } else if(localName.equals("tessellate")){ this.inTessellate = false; } else if(localName.equals("coordinates")){ this.inCoordinates = false; final String coords = this.sb.toString(); final StringTokenizer st1 = new StringTokenizer(coords, "\n"); while (st1.hasMoreTokens()){ final String point = st1.nextToken(); final StringTokenizer st2 = new StringTokenizer(point, ","); if (!st2.hasMoreTokens()) continue; final double b; final double a; try { b = Double.parseDouble(st2.nextToken()); a = Double.parseDouble(st2.nextToken()); } catch (Exception e) { continue; } if (maxLat == 0 || a > maxLat) { maxLat = a; } if (maxLon == 0 || b > maxLon) { maxLon = b; } if (minLat == 0 || a < minLat) { minLat = a; } if (minLon == 0 || b < minLon) { minLon = b; } final GeoPoint gp = new GeoPoint((int) (a * 1E6), (int) (b * 1E6)); this.mPolyline.add(gp); } final Point vIn = new Point(), vOut = new Point(); /* Needed to calculate the angles. */ final int polyLineLenght = this.mPolyline.size(); String mRouteInstructionDescr = null; int curIndexInPolyLine = 0; int turnAngle = 0; for (final GeoPoint gp : this.mPolyline) { if (curIndexInPolyLine == 0) { this.mTmpRouteInstruction = new RouteInstruction(); this.mRoute.getRouteInstructions().add(this.mTmpRouteInstruction); this.mTmpRouteInstruction.setDescriptionHtml(context.getString(R.string.begin_at)); } else if (curIndexInPolyLine >= polyLineLenght - 1) { this.mTmpRouteInstruction.setDescriptionHtml(context.getString(R.string.arrived_at)); } else { final GeoPoint lastgp = this.mPolyline.get(curIndexInPolyLine - 1); final GeoPoint nextgp = this.mPolyline.get(curIndexInPolyLine + 1); /* To calculate the angle, we need the line "into" that turnpoint. */ vIn.x = lastgp.getLongitudeE6() - gp.getLongitudeE6(); vIn.y = lastgp.getLatitudeE6() - gp.getLatitudeE6(); /* And the line 'out of' that turnpoint. */ vOut.x = nextgp.getLongitudeE6() - gp.getLongitudeE6(); vOut.y = nextgp.getLatitudeE6() - gp.getLatitudeE6(); /* Formula: angle = acos[(x * y) / (|x| * |y|)] */ if(GraphicsPoint.crossProduct(vIn, vOut) > 0) { turnAngle = - 180 + (int)Math.toDegrees(Math.acos(GraphicsPoint.dotProduct(vIn, vOut) / (FloatMath.sqrt(vIn.x * vIn.x + vIn.y * vIn.y) * FloatMath.sqrt(vOut.x * vOut.x + vOut.y * vOut.y)))); } else { turnAngle = 180 - (int)Math.toDegrees(Math.acos(GraphicsPoint.dotProduct(vIn, vOut) / (FloatMath.sqrt(vIn.x * vIn.x + vIn.y * vIn.y) * FloatMath.sqrt(vOut.x * vOut.x + vOut.y * vOut.y)))); } if(turnAngle > 60) { mRouteInstructionDescr = context.getString(R.string.turn_left_90); } else if(turnAngle > 35) { mRouteInstructionDescr = context.getString(R.string.turn_left_45); } else if(turnAngle > 15) { mRouteInstructionDescr = context.getString(R.string.turn_left_25); } else if(turnAngle < -60) { mRouteInstructionDescr = context.getString(R.string.turn_right_90); } else if(turnAngle < -35) { mRouteInstructionDescr = context.getString(R.string.turn_right_45); } else if(turnAngle < -15) { mRouteInstructionDescr = context.getString(R.string.turn_right_25); } else { mRouteInstructionDescr = null; } if (mRouteInstructionDescr != null) { final int distance = gp.distanceTo(this.mTmpRouteInstruction.getPartialPolyLine().get(0)); this.mTmpRouteInstruction.setLengthMeters(distance); this.mTmpRouteInstruction.setDescriptionHtml(mRouteInstructionDescr); this.mTmpRouteInstruction = new RouteInstruction(); this.mRoute.getRouteInstructions().add(this.mTmpRouteInstruction); } } this.mTmpRouteInstruction.getPartialPolyLine().add(gp); // If this was the first element, we will determine its position in the OverallPolyline if(this.mTmpRouteInstruction.getPartialPolyLine().size() == 1) { this.mLastFirstMotherPolylineIndex = this.mRoute.findInPolyLine(gp, this.mLastFirstMotherPolylineIndex); this.mTmpRouteInstruction.setFirstMotherPolylineIndex(this.mLastFirstMotherPolylineIndex); } curIndexInPolyLine++; } } else { Log.w(DEBUGTAG, "Unexpected end-tag: '" + name + "'"); } // Reset the stringbuffer this.sb.setLength(0); super.endElement(uri, localName, name); } @Override public void endDocument() throws SAXException { if(this.mErrors == null || this.mErrors.size() == 0){ final GeoPoint gp = this.mTmpRouteInstruction.getPartialPolyLine().get(this.mTmpRouteInstruction.getPartialPolyLine().size() - 1); int distance = gp.distanceTo(this.mTmpRouteInstruction.getPartialPolyLine().get(0)); this.mTmpRouteInstruction.setLengthMeters(distance); distance = gp.distanceTo(this.mPolyline.get(0)); this.mRoute.setDistanceMeters(distance); this.mRoute.setStart(this.mPolyline.get(0)); this.mRoute.setDestination(this.mPolyline.get(this.mPolyline.size() - 1)); this.mRoute.setStartInstruction(this.mRoute.getRouteInstructions().remove(0)); this.mRoute.setBoundingBoxE6(new BoundingBoxE6(maxLat, maxLon, minLat, minLon)); } super.endDocument(); } }