package rectangledbmi.com.pittsburghrealtimetracker.handlers; import android.content.Context; import android.location.Location; import android.net.http.HttpResponseCache; import android.os.AsyncTask; import android.util.Log; import com.google.android.gms.maps.GoogleMap; import com.google.android.gms.maps.model.BitmapDescriptorFactory; import com.google.android.gms.maps.model.LatLng; import com.google.android.gms.maps.model.Marker; import com.google.android.gms.maps.model.MarkerOptions; import com.google.android.gms.maps.model.Polyline; import com.google.android.gms.maps.model.PolylineOptions; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlPullParserFactory; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import java.util.concurrent.ConcurrentMap; import rectangledbmi.com.pittsburghrealtimetracker.R; import rectangledbmi.com.pittsburghrealtimetracker.handlers.containers.RequestLineContainer; import rectangledbmi.com.pittsburghrealtimetracker.world.LineInfo; import rectangledbmi.com.pittsburghrealtimetracker.world.TransitStopCollection; /** * This is the way to add the polylines if it's not present on the map * <p/> * REQUIRES PortAuthorityAPI class to get the Port Authority URLs * <p/> * TODO: to get the bus stops... change linkedList<LinkedList<LatLng>> to be a custom wrapper * * @author Jeremy Jao */ public class RequestLine extends AsyncTask<Void, Void, RequestLineContainer> { /** * The Google Map */ private GoogleMap mMap; /** * the Map that contains the Polylines by bus route string */ private ConcurrentMap<String, List<Polyline>> patterns; /** * The route that was selected */ private String selectedRoute; private float zoom, zoomVisibility; TransitStopCollection transitStopCollection; /** * The route color */ private int color; private Context context; //TODO: selectedRoute and color have to go out in order to add the polylines to the map... public RequestLine(GoogleMap mMap, ConcurrentMap<String, List<Polyline>> patterns, String selectedRoute, int color, float zoomLevel, float zoomVisibility, TransitStopCollection stopMap, Context context ) { this.mMap = mMap; this.patterns = patterns; this.selectedRoute = selectedRoute; this.color = color; this.zoom = zoomLevel; this.transitStopCollection = stopMap; this.zoomVisibility = zoomVisibility; this.context = context; } /** * Pull parser that gets the XML from the background * * @param voids nothing * @return the results to put the PolyLine on the map as a list */ @Override protected RequestLineContainer doInBackground(Void... voids) { RequestLineContainer points; XmlPullParserFactory pullParserFactory; try { points = null; checkSD(); pullParserFactory = XmlPullParserFactory.newInstance(); XmlPullParser parser = pullParserFactory.newPullParser(); HttpResponseCache cache = HttpResponseCache.getInstalled(); if(cache != null) { Log.d("cache_info_lines", selectedRoute + ": " + Integer.toString(cache.getHitCount())); } else { Log.d("cache_info", selectedRoute + ": " + "cache is empty"); } InputStream in = new FileInputStream(context.getFilesDir() + "/lineinfo/" + selectedRoute + ".xml"); parser.setInput(in, null); // get the list... points = parseXML(parser); } catch (XmlPullParserException e) { e.printStackTrace(); return null; } catch (IOException e) { e.printStackTrace(); return null; } return points; } private void checkSD() { File sd = new File(context.getFilesDir() + "/lineinfo"); if(!sd.exists()) { sd.mkdirs(); } File routeFile = new File(sd.getName() + "/" + selectedRoute + ".xml"); if(!routeFile.exists()) { InputSave save = new InputSave(context); save.saveFile(selectedRoute); } } /** * This is the pull parser method to get the polyline points for the specific route * * @param parser the XMLPullParser that opens the URL * @return the list of points for the Polyline to be added * @throws XmlPullParserException * @throws IOException */ private RequestLineContainer parseXML(XmlPullParser parser) throws XmlPullParserException, IOException { LinkedList<LatLng> points = new LinkedList<>(); ArrayList<LinkedList<LatLng>> allPoints = new ArrayList<>(); ArrayList<LineInfo> busStopInfos = new ArrayList<>(); int eventType = parser.getEventType(); double tempLat = 0.0; double tempLong = 0.0; int seq = 1; int tempSeq = 0; String rtdir = null; String stpnm = null; String stpid = null; boolean isBusStop = false; while (eventType != XmlPullParser.END_DOCUMENT) { String name; try { switch (eventType) { case (XmlPullParser.START_TAG): { name = parser.getName(); switch (name) { case "rtdir": points = new LinkedList<>(); allPoints.add(points); rtdir = parser.nextText(); seq = 1; break; case "lat": tempLat = Double.parseDouble(parser.nextText()); break; case "lon": tempLong = Double.parseDouble(parser.nextText()); break; case "seq": tempSeq = Integer.parseInt(parser.nextText()); break; case "typ": if ("S".equals(parser.nextText())) { isBusStop = true; } break; case "stpid": stpid = parser.nextText(); break; case "stpnm": stpnm = parser.nextText(); break; } break; } case (XmlPullParser.END_TAG): { name = parser.getName(); if ("pt".equals(name)) { seq = addPoints(points, tempLat, tempLong, seq, tempSeq); if (isBusStop) { busStopInfos.add(new LineInfo(stpid, stpnm, rtdir, tempLat, tempLong)); isBusStop = false; stpid = null; stpnm = null; } } break; } } } catch (NullPointerException e) { Log.e("requestline_error", e.getMessage()); } eventType = parser.next(); } connectPoints(allPoints); return new RequestLineContainer(allPoints, busStopInfos); } private void connectPoints(ArrayList<LinkedList<LatLng>> allPoints) { boolean[] firstconnect = new boolean[allPoints.size()]; boolean[] lastconnect = new boolean[allPoints.size()]; int firstindex = 0; for (LinkedList<LatLng> firstPoint : allPoints) { LatLng tempLatLng = new LatLng(0, 0); float min = Float.MAX_VALUE; int lastindex = 0; int templastindex = Integer.MAX_VALUE; if (!firstconnect[firstindex]) { for (LinkedList<LatLng> endPoints : allPoints) { if (!lastconnect[lastindex] && !equals(firstPoint, endPoints)) { float tempdistance = distanceBtwn(firstPoint.getFirst(), endPoints.getLast()); if (tempdistance == (float) 0) { templastindex = lastindex; firstconnect[firstindex] = true; lastconnect[lastindex++] = true; break; } else if (tempdistance < min) { min = tempdistance; tempLatLng = endPoints.getLast(); templastindex = lastindex; } } ++lastindex; } if ((templastindex != Integer.MAX_VALUE && !firstconnect[firstindex] && !lastconnect[templastindex]) && !tempLatLng.equals(new LatLng(0, 0)) && (min != Float.MAX_VALUE) && min <= (float) 700) { firstPoint.add(0, tempLatLng); firstconnect[firstindex] = true; lastconnect[templastindex] = true; } ++firstindex; } } } private boolean equals(LinkedList<LatLng> firstPoint, LinkedList<LatLng> lastPoint) { return (firstPoint.size() == lastPoint.size() && firstPoint.getFirst().equals(lastPoint.getFirst()) && firstPoint.getLast().equals(lastPoint.getLast())); } private float distanceBtwn(LatLng from, LatLng to) { Location A = new Location("A"); A.setLatitude(from.latitude); A.setLongitude(from.longitude); Location B = new Location("B"); B.setLatitude(to.latitude); B.setLongitude(to.longitude); return A.distanceTo(B); } /** * Adds points to the points list (the list of points for the PolyLine). adds if the sequence is correct * and the tempLat or tempLong isn't the initialized value (Africa -> 0.0, 0.0) * * @param points the list of points for the Polyline * @param tempLat the temporary latitude * @param tempLong the temporary longitude * @param seq the sequence counter * @param tempSeq What the XML says its sequence is * @return the sequence incremented if successful. else the same sequence... */ private int addPoints(LinkedList<LatLng> points, double tempLat, double tempLong, int seq, int tempSeq) { if ((tempLat != 0.0 || tempLong != 0.0) && (seq == tempSeq)) { LatLng templatlong = new LatLng(tempLat, tempLong); points.add(templatlong); return seq + 1; } return seq; } /** * Adds the polyline to the map on the UI thread * */ @Override protected void onPostExecute(RequestLineContainer container) { if(mMap != null && container != null) { ArrayList<LinkedList<LatLng>> latLngs = container.getPolylinesInfo(); ArrayList<LineInfo> busStopInfos = container.getBusStopInfos(); if (latLngs != null) { List<Polyline> polylines = new ArrayList<>(latLngs.size()); for (LinkedList<LatLng> points : latLngs) { polylines.add(mMap.addPolyline(new PolylineOptions().addAll(points).color(color).geodesic(true).visible(true))); } patterns.put(selectedRoute, polylines); } if (busStopInfos != null) { for (LineInfo busStopInfo : busStopInfos) { if (!transitStopCollection.addRouteToMarker(busStopInfo.getStpid(), selectedRoute, zoom, zoomVisibility)) { Marker marker = mMap.addMarker(new MarkerOptions(). position(busStopInfo.getLatLng()). alpha(.65f). flat(false). title("(" + busStopInfo.getStpid() + ") " + busStopInfo.getStpnm() + " " + busStopInfo.getRtdir()). snippet(busStopInfo.getRtdir()). draggable(false). icon(BitmapDescriptorFactory.fromResource(R.drawable.bus_stop)). anchor(.5f, .5f) .visible(false) ); transitStopCollection.addMarkerToRoute(marker, busStopInfo.getStpid(), selectedRoute, zoom, zoomVisibility); } } } } } }