From ead5446d9c99778918feb485c5f5eb310b30ffda Mon Sep 17 00:00:00 2001 From: Kailas Krivanka Date: Sat, 10 Dec 2016 14:51:22 -0800 Subject: [PATCH 1/6] Added paths to map Added MapHandler class to manage drawing polyline paths to google map. The handler can handle a variable number of paths with variable points, all of which are draggable. --- mobility-track-android/app/app.iml | 2 + mobility-track-android/app/build.gradle | 2 + .../ucsc/mobility_track/MainActivity.java | 6 + .../ucsc/mobility_track/MapHandler.java | 104 ++++++++++++++++++ 4 files changed, 114 insertions(+) create mode 100644 mobility-track-android/app/src/main/java/ogr/scorelab/ucsc/mobility_track/MapHandler.java diff --git a/mobility-track-android/app/app.iml b/mobility-track-android/app/app.iml index 0a7e21a..21cd6e0 100644 --- a/mobility-track-android/app/app.iml +++ b/mobility-track-android/app/app.iml @@ -111,10 +111,12 @@ + + diff --git a/mobility-track-android/app/build.gradle b/mobility-track-android/app/build.gradle index a85b351..09eedb7 100644 --- a/mobility-track-android/app/build.gradle +++ b/mobility-track-android/app/build.gradle @@ -24,4 +24,6 @@ dependencies { compile 'com.android.support:appcompat-v7:23.1.1' compile 'com.android.support:design:23.1.1' compile 'com.google.android.gms:play-services-maps:10.0.1' + // https://mvnrepository.com/artifact/org.slf4j/slf4j-simple + compile group: 'org.slf4j', name: 'slf4j-simple', version: '1.6.1' } diff --git a/mobility-track-android/app/src/main/java/ogr/scorelab/ucsc/mobility_track/MainActivity.java b/mobility-track-android/app/src/main/java/ogr/scorelab/ucsc/mobility_track/MainActivity.java index 6828d5d..6aed9f1 100644 --- a/mobility-track-android/app/src/main/java/ogr/scorelab/ucsc/mobility_track/MainActivity.java +++ b/mobility-track-android/app/src/main/java/ogr/scorelab/ucsc/mobility_track/MainActivity.java @@ -21,6 +21,8 @@ import com.google.android.gms.maps.MapFragment; import com.google.android.gms.maps.OnMapReadyCallback; import com.google.android.gms.maps.UiSettings; +import com.google.android.gms.maps.model.LatLng; +import com.google.android.gms.maps.model.MarkerOptions; import org.json.JSONArray; import org.json.JSONException; @@ -208,5 +210,9 @@ public void onMapReady(GoogleMap googleMap) { return; } googleMap.setMyLocationEnabled(true); + + + MapHandler handler = new MapHandler(googleMap); + handler.drawPath(); } } diff --git a/mobility-track-android/app/src/main/java/ogr/scorelab/ucsc/mobility_track/MapHandler.java b/mobility-track-android/app/src/main/java/ogr/scorelab/ucsc/mobility_track/MapHandler.java new file mode 100644 index 0000000..4107593 --- /dev/null +++ b/mobility-track-android/app/src/main/java/ogr/scorelab/ucsc/mobility_track/MapHandler.java @@ -0,0 +1,104 @@ +package ogr.scorelab.ucsc.mobility_track; + +import android.view.View; + +import com.google.android.gms.maps.GoogleMap; +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 java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Created by Kailas on 12/10/2016. + */ + +class MapHandler implements GoogleMap.OnMarkerDragListener{ + + private GoogleMap map; + + private HashMap paths = new HashMap<>(); + + MapHandler(GoogleMap map) { + this.map = map; + map.setOnMarkerDragListener(this); + } + + void drawPath(LatLng... points){ + if(points.length <= 1){ + return; + } + + PolylineOptions ops = new PolylineOptions(); + ops.add(points); + Polyline line = map.addPolyline(ops); + paths.put(line.getId(),line); + + for (LatLng pos: + points) { + map.addMarker(new MarkerOptions().position(pos).draggable(true)).setTag(new MarkerTag(line.getId(),pos)); + } + + } + + //Draw path with default locations + void drawPath(){ + drawPath(new LatLng(38,-122),new LatLng(37,-122)); + } + + @Override + public void onMarkerDragStart(Marker marker) { + + } + + @Override + public void onMarkerDrag(Marker marker) { + MarkerTag tag = ((MarkerTag) marker.getTag()); + Polyline line = paths.get(tag.getLineID()); + + List points = line.getPoints(); + int index = points.indexOf(tag.getPos()); + points.set(index,marker.getPosition()); + tag.updatePos(marker.getPosition()); + + line.setPoints(points); + } + + @Override + public void onMarkerDragEnd(Marker marker) { + + } + + private void showTrackers(){ + + } + + //Class set as tag to marker to aid in identification + private class MarkerTag{ + + private LatLng pos; + private String lineID; + + MarkerTag(String lineID, LatLng pos) { + this.lineID = lineID; + this.pos = pos; + } + + void updatePos(LatLng newPos){ + this.pos = newPos; + } + + LatLng getPos() { + return pos; + } + + String getLineID() { + return lineID; + } + } +} From 9c173e31039cea3ec3e8fac0215f40ba9ce3bab6 Mon Sep 17 00:00:00 2001 From: Kailas Krivanka Date: Sat, 10 Dec 2016 21:15:44 -0800 Subject: [PATCH 2/6] Added detection of nearby tracker locations to path --- mobility-track-android/app/app.iml | 2 - mobility-track-android/app/build.gradle | 2 - .../ucsc/mobility_track/MapHandler.java | 427 +++++++++++++++++- 3 files changed, 425 insertions(+), 6 deletions(-) diff --git a/mobility-track-android/app/app.iml b/mobility-track-android/app/app.iml index 21cd6e0..0a7e21a 100644 --- a/mobility-track-android/app/app.iml +++ b/mobility-track-android/app/app.iml @@ -111,12 +111,10 @@ - - diff --git a/mobility-track-android/app/build.gradle b/mobility-track-android/app/build.gradle index 09eedb7..a85b351 100644 --- a/mobility-track-android/app/build.gradle +++ b/mobility-track-android/app/build.gradle @@ -24,6 +24,4 @@ dependencies { compile 'com.android.support:appcompat-v7:23.1.1' compile 'com.android.support:design:23.1.1' compile 'com.google.android.gms:play-services-maps:10.0.1' - // https://mvnrepository.com/artifact/org.slf4j/slf4j-simple - compile group: 'org.slf4j', name: 'slf4j-simple', version: '1.6.1' } diff --git a/mobility-track-android/app/src/main/java/ogr/scorelab/ucsc/mobility_track/MapHandler.java b/mobility-track-android/app/src/main/java/ogr/scorelab/ucsc/mobility_track/MapHandler.java index 4107593..01134c1 100644 --- a/mobility-track-android/app/src/main/java/ogr/scorelab/ucsc/mobility_track/MapHandler.java +++ b/mobility-track-android/app/src/main/java/ogr/scorelab/ucsc/mobility_track/MapHandler.java @@ -1,11 +1,14 @@ package ogr.scorelab.ucsc.mobility_track; +import android.util.Log; import android.view.View; import com.google.android.gms.maps.GoogleMap; import com.google.android.gms.maps.model.LatLng; +import com.google.android.gms.maps.model.LatLngBounds; import com.google.android.gms.maps.model.Marker; import com.google.android.gms.maps.model.MarkerOptions; +import com.google.android.gms.maps.model.PolygonOptions; import com.google.android.gms.maps.model.Polyline; import com.google.android.gms.maps.model.PolylineOptions; @@ -24,6 +27,8 @@ class MapHandler implements GoogleMap.OnMarkerDragListener{ private HashMap paths = new HashMap<>(); + private final double SEARCH_RADIUS_IN_M = 1000; + MapHandler(GoogleMap map) { this.map = map; map.setOnMarkerDragListener(this); @@ -44,11 +49,13 @@ void drawPath(LatLng... points){ map.addMarker(new MarkerOptions().position(pos).draggable(true)).setTag(new MarkerTag(line.getId(),pos)); } + showTrackers(line.getPoints()); + } //Draw path with default locations void drawPath(){ - drawPath(new LatLng(38,-122),new LatLng(37,-122)); + drawPath(new LatLng(37.2,-122),new LatLng(37,-122)); } @Override @@ -71,11 +78,43 @@ public void onMarkerDrag(Marker marker) { @Override public void onMarkerDragEnd(Marker marker) { + showTrackers(paths.get(((MarkerTag) marker.getTag()).getLineID()).getPoints()); + } + + private void showTrackers(List points){ + + List boxes = new RouteBoxer().getRouteBoxes(points,SEARCH_RADIUS_IN_M); + + for (LatLngBounds box : + boxes) { + + LatLng northEast = box.northeast; + LatLng southWest = box.southwest; + + map.addPolygon(new PolygonOptions().add(new LatLng(northEast.latitude,northEast.longitude),new LatLng(northEast.latitude,southWest.longitude),new LatLng(southWest.latitude,southWest.longitude),new LatLng(southWest.latitude,northEast.longitude))); + } + + + LatLng[] trackers = generateTrackers(); + + for (int i = 0; i < trackers.length; i++) { + for (LatLngBounds box + :boxes) { + if(box.contains(trackers[i])){ + map.addMarker(new MarkerOptions().position(trackers[i])); + } + } + } } - private void showTrackers(){ + private LatLng[] generateTrackers(){ + LatLng[] trackers = new LatLng[100]; + for(int i = 0; i < trackers.length; i++){ + trackers[i] = new LatLng((Math.random()/2)+37,(Math.random()/2)-122); + } + return trackers; } //Class set as tag to marker to aid in identification @@ -101,4 +140,388 @@ String getLineID() { return lineID; } } + + + private class RouteBoxer{ + double R = 6371; + ArrayList vertLines = new ArrayList<>(); + ArrayList horLines = new ArrayList<>(); + ArrayList boxesX = new ArrayList<>(); + ArrayList boxesY = new ArrayList<>(); + boolean[][] grid; + + private List getRouteBoxes(List points,double radius){ + ArrayList boxes = new ArrayList<>(); + + LatLngBounds.Builder bigBoundsBuilder = new LatLngBounds.Builder(); + + + for (LatLng point : + points) { + bigBoundsBuilder.include(point); + } + + LatLngBounds bigBounds = bigBoundsBuilder.build(); + LatLng center = bigBounds.getCenter(); + + vertLines.add(center.latitude); + vertLines.add(rhumbDestinationPoint(0,radius,center).latitude); + for(int i = 2;vertLines.get(i-2) < bigBounds.northeast.latitude;i++){ + vertLines.add(rhumbDestinationPoint(0,radius*i,center).latitude); + } + + for(int i = 1;vertLines.get(1) > bigBounds.southwest.latitude;i++){ + vertLines.add(0,rhumbDestinationPoint(180,radius*i,center).latitude); + } + + horLines.add(center.longitude); + horLines.add(rhumbDestinationPoint(90,radius,center).longitude); + for(int i = 2;horLines.get(i-2) < bigBounds.northeast.longitude;i++){ + horLines.add(rhumbDestinationPoint(90,radius*i,center).longitude); + } + + for(int i = 1;horLines.get(1) > bigBounds.southwest.longitude;i++){ + horLines.add(0,rhumbDestinationPoint(270,radius*i,center).longitude); + } + + grid = new boolean[horLines.size()][vertLines.size()]; + + findCellsInPath(points); + mergeIntersectingCells(); +// +// for (int i = 1; i < horLines.size(); i++) { +// for (int j = 1; j < vertLines.size(); j++) { +// Log.e("Debug","I: "+i+" size: "+horLines.size()); +// Log.e("Debug","J: "+i+" size: "+vertLines.size()); +// LatLngBounds box = getCellBounds(new int[]{i-1,j-1}); +// boxesX.add(box); +// boxesY.add(box); +// } +// } + + return boxesX; + } + + private void findCellsInPath(List points){ + // Find the cell where the path begins + int[] hintXY = this.getCellCordsOfPoint(points.get(0)); + + // Mark that cell and it's neighbours for inclusion in the boxes + this.markCell(hintXY); + + // Work through each vertex on the path identifying which grid cell it is in + for (int i = 1; i < points.size(); i++) { + // Use the known cell of the previous vertex to help find the cell of this vertex + int[] gridXY = this.getGridCoordsFromHint(points.get(i), points.get(i-1), hintXY); + + if (gridXY[0] == hintXY[0] && gridXY[1] == hintXY[1]) { + // This vertex is in the same cell as the previous vertex + // The cell will already have been marked for inclusion in the boxes + continue; + + } else if ((Math.abs(hintXY[0] - gridXY[0]) == 1 && hintXY[1] == gridXY[1]) || + (hintXY[0] == gridXY[0] && Math.abs(hintXY[1] - gridXY[1]) == 1)) { + // This vertex is in a cell that shares an edge with the previous cell + // Mark this cell and it's neighbours for inclusion in the boxes + this.markCell(gridXY); + + } else { + // This vertex is in a cell that does not share an edge with the previous + // cell. This means that the path passes through other cells between + // this vertex and the previous vertex, and we must determine which cells + // it passes through + this.getGridIntersects(points.get(i - 1), points.get(i), hintXY, gridXY); + } + + // Use this cell to find and compare with the next one + hintXY = gridXY; + } + } + + private void getGridIntersects(LatLng start,LatLng end,int[] startXY,int[] endXY) { + int i; + LatLng edgePoint; + int[] edgeXY; + double brng = rhumbBearingTo(start,end); // Step 1. + + + LatLng hint = start; + int[] hintXY = startXY; + + // Handle a line segment that travels south first + if (end.latitude > start.latitude) { + // Iterate over the east to west grid lines between the start and end cells + for (i = startXY[1] + 1; i <= endXY[1]; i++) { + // Find the latlng of the point where the path segment intersects with + // this grid line (Step 2 & 3) + edgePoint = this.getGridIntersect(start, brng, this.vertLines.get(i)); + + // Find the cell containing this intersect point (Step 4) + edgeXY = this.getGridCoordsFromHint(edgePoint, hint, hintXY); + + // Mark every cell the path has crossed between this grid and the start, + // or the previous east to west grid line it crossed (Step 5) + this.fillInGridSquares(hintXY[0], edgeXY[0], i - 1); + + // Use the point where it crossed this grid line as the reference for the + // next iteration + hint = edgePoint; + hintXY = edgeXY; + } + + // Mark every cell the path has crossed between the last east to west grid + // line it crossed and the end (Step 5) + this.fillInGridSquares(hintXY[0], endXY[0], i - 1); + + } else { + // Iterate over the east to west grid lines between the start and end cells + for (i = startXY[1]; i > endXY[1]; i--) { + // Find the latlng of the point where the path segment intersects with + // this grid line (Step 2 & 3) + edgePoint = this.getGridIntersect(start, brng, this.vertLines.get(i)); + + // Find the cell containing this intersect point (Step 4) + edgeXY = this.getGridCoordsFromHint(edgePoint, hint, hintXY); + + // Mark every cell the path has crossed between this grid and the start, + // or the previous east to west grid line it crossed (Step 5) + this.fillInGridSquares(hintXY[0], edgeXY[0], i); + + // Use the point where it crossed this grid line as the reference for the + // next iteration + hint = edgePoint; + hintXY = edgeXY; + } + + // Mark every cell the path has crossed between the last east to west grid + // line it crossed and the end (Step 5) + this.fillInGridSquares(hintXY[0], endXY[0], i); + + } + } + + private void mergeIntersectingCells() { + int x, y; + LatLngBounds box; + + // The box we are currently expanding with new cells + LatLngBounds currentBox = null; + +// // Traverse the grid a row at a time +// for (y = 0; y < this.grid[0].length; y++) { +// for (x = 0; x < this.grid.length; x++) { +// +// if (this.grid[x][y]) { +// // This cell is marked for inclusion. If the previous cell in this +// // row was also marked for inclusion, merge this cell into it's box. +// // Otherwise start a new box. +// box = this.getCellBounds(new int[]{x, y}); +// if (currentBox != null) { +// currentBox.including(box.northeast); +// } else { +// currentBox = box; +// } +// +// } else { +// // This cell is not marked for inclusion. If the previous cell was +// // marked for inclusion, merge it's box with a box that spans the same +// // columns from the row below if possible. +// this.mergeBoxesY(currentBox); +// currentBox = null; +// } +// } +// // If the last cell was marked for inclusion, merge it's box with a matching +// // box from the row below if possible. +// this.mergeBoxesY(currentBox); +// currentBox = null; +// } + + // Traverse the grid a column at a time + Log.e("Debug","Grid: "+grid.length+" rows, "+grid[0].length+" columns"); + Log.e("Debug","Vert: "+vertLines.size()+" Hor: "+horLines.size()); + for (x = 0; x < this.grid.length-1; x++) { + for (y = 0; y < this.grid[0].length-1; y++) { + if (this.grid[x][y]) { + + // This cell is marked for inclusion. If the previous cell in this + // column was also marked for inclusion, merge this cell into it's box. + // Otherwise start a new box. +// if (currentBox != null) { +// Log.e("Debug","X: "+x+" Y: "+y); + +// box = this.getCellBounds(new int[]{x, y}); +// currentBox.including(box.northeast); +// } else { +// currentBox = this.getCellBounds(new int[]{x, y}); +// } + + currentBox = this.getCellBounds(new int[]{x, y}); + boxesX.add(currentBox); + + } else { + // This cell is not marked for inclusion. If the previous cell was + // marked for inclusion, merge it's box with a box that spans the same + // rows from the column to the left if possible. +// this.mergeBoxesX(currentBox); +// currentBox = null; + + } + } + // If the last cell was marked for inclusion, merge it's box with a matching + // box from the column to the left if possible. +// this.mergeBoxesX(currentBox); +// currentBox = null; + } + } + + +/** + * Search for an existing box in an adjacent row to the given box that spans the + * same set of columns and if one is found merge the given box into it. If one + * is not found, append this box to the list of existing boxes. + * + * @param {LatLngBounds} The box to merge + */ + private void mergeBoxesX(LatLngBounds box) { + if (box != null) { + for (LatLngBounds currentBox : + boxesX) { + if (box.northeast.longitude == box.southwest.longitude && + currentBox.southwest.latitude == box.southwest.latitude && + currentBox.northeast.latitude == box.northeast.latitude) { + currentBox.including(box.northeast); + return; + } + } + this.boxesX.add(box); + } + }; + +/** + * Search for an existing box in an adjacent column to the given box that spans + * the same set of rows and if one is found merge the given box into it. If one + * is not found, append this box to the list of existing boxes. + * + * @param {LatLngBounds} The box to merge + */ + private void mergeBoxesY(LatLngBounds box) { + if (box != null) { + for (LatLngBounds currentBox : + boxesY) { + if (currentBox.northeast.latitude == box.southwest.latitude && + currentBox.southwest.longitude == box.southwest.longitude && + currentBox.northeast.longitude == box.northeast.longitude) { + currentBox.including(box.northeast); + return; + } + } + this.boxesY.add(box); + } + } + + private LatLngBounds getCellBounds(int[] cell) { + return new LatLngBounds(new LatLng(vertLines.get(cell[1]), this.horLines.get(cell[0])), + new LatLng(this.vertLines.get(cell[1] + 1), this.horLines.get(cell[0] + 1))); + }; + + private LatLng getGridIntersect(LatLng start, double brng, double gridLineLat) { + double d = this.R * ((toRad(gridLineLat) - toRad(start.latitude) / Math.cos(toRad(brng)))); + return rhumbDestinationPoint(brng,d,start); + } + + private void fillInGridSquares(int startx,int endx,int y) { + int x; + if (startx < endx) { + for (x = startx; x <= endx; x++) { + this.markCell(new int[]{x, y}); + } + } else { + for (x = startx; x >= endx; x--) { + this.markCell(new int[]{x, y}); + } + } + } + + private void markCell(int[] cell) { + int x = cell[0]; + int y = cell[1]; + this.grid[x - 1][y - 1] = true; + this.grid[x][y - 1] = true; + this.grid[x + 1][y - 1] = true; + this.grid[x - 1][y] = true; + this.grid[x][y] = true; + this.grid[x + 1][y] = true; + this.grid[x - 1][y + 1] = true; + this.grid[x][y + 1] = true; + this.grid[x + 1][y + 1] = true; + }; + + private int[] getCellCordsOfPoint(LatLng point){ + int x = 0; + int y = 0; + while (this.horLines.get(x) < point.longitude) {x++;} + while (this.vertLines.get(y) < point.latitude) {y++;} + return (new int[]{x, y}); + } + + private int[] getGridCoordsFromHint(LatLng point,LatLng hintlatlng,int[] hint) { + int x, y; + if (point.longitude > hintlatlng.longitude) { + for (x = hint[0]; this.horLines.get(x + 1) < point.longitude; x++) {} + } else { + for (x = hint[0]; this.horLines.get(x) > point.longitude; x--) {} + } + + if (point.latitude > hintlatlng.latitude) { + for (y = hint[1]; this.vertLines.get(y + 1) < point.latitude; y++) {} + } else { + for (y = hint[1]; this.vertLines.get(y) > point.latitude; y--) {} + } + + return (new int[]{x, y}); + }; + + private LatLng rhumbDestinationPoint(double brng, double dist, LatLng pos) { + double d = dist/6378137; + double lat1 = toRad(pos.latitude), lon1 = toRad(pos.longitude); + brng = toRad(brng); + + double dLat = d*Math.cos(brng); + + if (Math.abs(dLat) < 1e-10) dLat = 0; + + double lat2 = lat1 + dLat; + double dPhi = Math.log(Math.tan(lat2/2+Math.PI/4)/Math.tan(lat1/2+Math.PI/4)); + double q = (dPhi!=0) ? dLat/dPhi : Math.cos(lat1); + double dLon = d*Math.sin(brng)/q; + + if (Math.abs(lat2) > Math.PI/2) lat2 = lat2>0 ? Math.PI-lat2 : -Math.PI-lat2; + + double lon2 = (lon1+dLon+3*Math.PI)%(2*Math.PI) - Math.PI; + + return new LatLng(toDeg(lat2), toDeg(lon2)); + } + + private double rhumbBearingTo(LatLng start, LatLng dest) { + double dLon = toRad(dest.longitude - start.longitude); + double dPhi = Math.log(Math.tan(toRad(dest.latitude) / 2 + Math.PI / 4) / Math.tan(toRad(start.latitude) / 2 + Math.PI / 4)); + if (Math.abs(dLon) > Math.PI) { + dLon = dLon > 0 ? -(2 * Math.PI - dLon) : (2 * Math.PI + dLon); + } + return toBrng(Math.atan2(dLon, dPhi)); + } + + + private double toRad(double value) { + return value * Math.PI / 180; + } + + private double toDeg(double value) { + return value * 180 / Math.PI; + } + + private double toBrng(double value) { + return (toDeg(value) + 360) % 360; + } + } } From 69b861c56fe2cbece15e271446a9255d18a077c4 Mon Sep 17 00:00:00 2001 From: Kailas Krivanka Date: Tue, 13 Dec 2016 21:53:46 -0800 Subject: [PATCH 3/6] Added map-handler Added script to be integrated with map that uses RouteBoxer to plot markers along an editable PolyLine. --- mobility-track-web/test/RouteBoxer.js | 584 +++++++++++++++++++++++++ mobility-track-web/test/map-handler.js | 79 ++++ 2 files changed, 663 insertions(+) create mode 100644 mobility-track-web/test/RouteBoxer.js create mode 100644 mobility-track-web/test/map-handler.js diff --git a/mobility-track-web/test/RouteBoxer.js b/mobility-track-web/test/RouteBoxer.js new file mode 100644 index 0000000..4f4d109 --- /dev/null +++ b/mobility-track-web/test/RouteBoxer.js @@ -0,0 +1,584 @@ +/** + * @name RouteBoxer + * @version 1.0 + * @copyright (c) 2010 Google Inc. + * @author Thor Mitchell + * + * @fileoverview The RouteBoxer class takes a path, such as the Polyline for a + * route generated by a Directions request, and generates a set of LatLngBounds + * objects that are guaranteed to contain every point within a given distance + * of that route. These LatLngBounds objects can then be used to generate + * requests to spatial search services that support bounds filtering (such as + * the Google Maps Data API) in order to implement search along a route. + *

+ * RouteBoxer overlays a grid of the specified size on the route, identifies + * every grid cell that the route passes through, and generates a set of bounds + * that cover all of these cells, and their nearest neighbours. Consequently + * the bounds returned will extend up to ~3x the specified distance from the + * route in places. + */ + +/* + * 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. + */ + +/** + * Creates a new RouteBoxer + * + * @constructor + */ +function RouteBoxer() { + this.R = 6371; // earth's mean radius in km +} + +/** + * Generates boxes for a given route and distance + * + * @param {google.maps.LatLng[] | google.maps.Polyline} path The path along + * which to create boxes. The path object can be either an Array of + * google.maps.LatLng objects or a Maps API v2 or Maps API v3 + * google.maps.Polyline object. + * @param {Number} range The distance in kms around the route that the generated + * boxes must cover. + * @return {google.maps.LatLngBounds[]} An array of boxes that covers the whole + * path. + */ +RouteBoxer.prototype.box = function (path, range) { + // Two dimensional array representing the cells in the grid overlaid on the path + this.grid_ = null; + + // Array that holds the latitude coordinate of each vertical grid line + this.latGrid_ = []; + + // Array that holds the longitude coordinate of each horizontal grid line + this.lngGrid_ = []; + + // Array of bounds that cover the whole route formed by merging cells that + // the route intersects first horizontally, and then vertically + this.boxesX_ = []; + + // Array of bounds that cover the whole route formed by merging cells that + // the route intersects first vertically, and then horizontally + this.boxesY_ = []; + + // The array of LatLngs representing the vertices of the path + var vertices = null; + + // If necessary convert the path into an array of LatLng objects + if (path instanceof Array) { + // already an arry of LatLngs (eg. v3 overview_path) + vertices = path; + } else if (path instanceof google.maps.Polyline) { + if (path.getPath) { + // v3 Maps API Polyline object + vertices = new Array(path.getPath().getLength()); + for (var i = 0; i < vertices.length; i++) { + vertices[i] = path.getPath().getAt(i); + } + } else { + // v2 Maps API Polyline object + vertices = new Array(path.getVertexCount()); + for (var j = 0; j < vertices.length; j++) { + vertices[j] = path.getVertex(j); + } + } + } + + // Build the grid that is overlaid on the route + this.buildGrid_(vertices, range); + + // Identify the grid cells that the route intersects + this.findIntersectingCells_(vertices); + + // Merge adjacent intersected grid cells (and their neighbours) into two sets + // of bounds, both of which cover them completely + this.mergeIntersectingCells_(); + + // Return the set of merged bounds that has the fewest elements + return (this.boxesX_.length <= this.boxesY_.length ? + this.boxesX_ : + this.boxesY_); +}; + +/** + * Generates boxes for a given route and distance + * + * @param {LatLng[]} vertices The vertices of the path over which to lay the grid + * @param {Number} range The spacing of the grid cells. + */ +RouteBoxer.prototype.buildGrid_ = function (vertices, range) { + + // Create a LatLngBounds object that contains the whole path + var routeBounds = new google.maps.LatLngBounds(); + for (var i = 0; i < vertices.length; i++) { + routeBounds.extend(vertices[i]); + } + + // Find the center of the bounding box of the path + var routeBoundsCenter = routeBounds.getCenter(); + + // Starting from the center define grid lines outwards vertically until they + // extend beyond the edge of the bounding box by more than one cell + this.latGrid_.push(routeBoundsCenter.lat()); + + // Add lines from the center out to the north + this.latGrid_.push(routeBoundsCenter.rhumbDestinationPoint(0, range).lat()); + for (i = 2; this.latGrid_[i - 2] < routeBounds.getNorthEast().lat(); i++) { + this.latGrid_.push(routeBoundsCenter.rhumbDestinationPoint(0, range * i).lat()); + } + + // Add lines from the center out to the south + for (i = 1; this.latGrid_[1] > routeBounds.getSouthWest().lat(); i++) { + this.latGrid_.unshift(routeBoundsCenter.rhumbDestinationPoint(180, range * i).lat()); + } + + // Starting from the center define grid lines outwards horizontally until they + // extend beyond the edge of the bounding box by more than one cell + this.lngGrid_.push(routeBoundsCenter.lng()); + + // Add lines from the center out to the east + this.lngGrid_.push(routeBoundsCenter.rhumbDestinationPoint(90, range).lng()); + for (i = 2; this.lngGrid_[i - 2] < routeBounds.getNorthEast().lng(); i++) { + this.lngGrid_.push(routeBoundsCenter.rhumbDestinationPoint(90, range * i).lng()); + } + + // Add lines from the center out to the west + for (i = 1; this.lngGrid_[1] > routeBounds.getSouthWest().lng(); i++) { + this.lngGrid_.unshift(routeBoundsCenter.rhumbDestinationPoint(270, range * i).lng()); + } + + // Create a two dimensional array representing this grid + this.grid_ = new Array(this.lngGrid_.length); + for (i = 0; i < this.grid_.length; i++) { + this.grid_[i] = new Array(this.latGrid_.length); + } +}; + +/** + * Find all of the cells in the overlaid grid that the path intersects + * + * @param {LatLng[]} vertices The vertices of the path + */ +RouteBoxer.prototype.findIntersectingCells_ = function (vertices) { + // Find the cell where the path begins + var hintXY = this.getCellCoords_(vertices[0]); + + // Mark that cell and it's neighbours for inclusion in the boxes + this.markCell_(hintXY); + + // Work through each vertex on the path identifying which grid cell it is in + for (var i = 1; i < vertices.length; i++) { + // Use the known cell of the previous vertex to help find the cell of this vertex + var gridXY = this.getGridCoordsFromHint_(vertices[i], vertices[i - 1], hintXY); + + if (gridXY[0] === hintXY[0] && gridXY[1] === hintXY[1]) { + // This vertex is in the same cell as the previous vertex + // The cell will already have been marked for inclusion in the boxes + continue; + + } else if ((Math.abs(hintXY[0] - gridXY[0]) === 1 && hintXY[1] === gridXY[1]) || + (hintXY[0] === gridXY[0] && Math.abs(hintXY[1] - gridXY[1]) === 1)) { + // This vertex is in a cell that shares an edge with the previous cell + // Mark this cell and it's neighbours for inclusion in the boxes + this.markCell_(gridXY); + + } else { + // This vertex is in a cell that does not share an edge with the previous + // cell. This means that the path passes through other cells between + // this vertex and the previous vertex, and we must determine which cells + // it passes through + this.getGridIntersects_(vertices[i - 1], vertices[i], hintXY, gridXY); + } + + // Use this cell to find and compare with the next one + hintXY = gridXY; + } +}; + +/** + * Find the cell a path vertex is in by brute force iteration over the grid + * + * @param {LatLng[]} latlng The latlng of the vertex + * @return {Number[][]} The cell coordinates of this vertex in the grid + */ +RouteBoxer.prototype.getCellCoords_ = function (latlng) { + for (var x = 0; this.lngGrid_[x] < latlng.lng(); x++) {} + for (var y = 0; this.latGrid_[y] < latlng.lat(); y++) {} + return ([x - 1, y - 1]); +}; + +/** + * Find the cell a path vertex is in based on the known location of a nearby + * vertex. This saves searching the whole grid when working through vertices + * on the polyline that are likely to be in close proximity to each other. + * + * @param {LatLng[]} latlng The latlng of the vertex to locate in the grid + * @param {LatLng[]} hintlatlng The latlng of the vertex with a known location + * @param {Number[]} hint The cell containing the vertex with a known location + * @return {Number[]} The cell coordinates of the vertex to locate in the grid + */ +RouteBoxer.prototype.getGridCoordsFromHint_ = function (latlng, hintlatlng, hint) { + var x, y; + if (latlng.lng() > hintlatlng.lng()) { + for (x = hint[0]; this.lngGrid_[x + 1] < latlng.lng(); x++) {} + } else { + for (x = hint[0]; this.lngGrid_[x] > latlng.lng(); x--) {} + } + + if (latlng.lat() > hintlatlng.lat()) { + for (y = hint[1]; this.latGrid_[y + 1] < latlng.lat(); y++) {} + } else { + for (y = hint[1]; this.latGrid_[y] > latlng.lat(); y--) {} + } + + return ([x, y]); +}; + + +/** + * Identify the grid squares that a path segment between two vertices + * intersects with by: + * 1. Finding the bearing between the start and end of the segment + * 2. Using the delta between the lat of the start and the lat of each + * latGrid boundary to find the distance to each latGrid boundary + * 3. Finding the lng of the intersection of the line with each latGrid + * boundary using the distance to the intersection and bearing of the line + * 4. Determining the x-coord on the grid of the point of intersection + * 5. Filling in all squares between the x-coord of the previous intersection + * (or start) and the current one (or end) at the current y coordinate, + * which is known for the grid line being intersected + * + * @param {LatLng} start The latlng of the vertex at the start of the segment + * @param {LatLng} end The latlng of the vertex at the end of the segment + * @param {Number[]} startXY The cell containing the start vertex + * @param {Number[]} endXY The cell containing the vend vertex + */ +RouteBoxer.prototype.getGridIntersects_ = function (start, end, startXY, endXY) { + var edgePoint, edgeXY, i; + var brng = start.rhumbBearingTo(end); // Step 1. + + var hint = start; + var hintXY = startXY; + + // Handle a line segment that travels south first + if (end.lat() > start.lat()) { + // Iterate over the east to west grid lines between the start and end cells + for (i = startXY[1] + 1; i <= endXY[1]; i++) { + // Find the latlng of the point where the path segment intersects with + // this grid line (Step 2 & 3) + edgePoint = this.getGridIntersect_(start, brng, this.latGrid_[i]); + + // Find the cell containing this intersect point (Step 4) + edgeXY = this.getGridCoordsFromHint_(edgePoint, hint, hintXY); + + // Mark every cell the path has crossed between this grid and the start, + // or the previous east to west grid line it crossed (Step 5) + this.fillInGridSquares_(hintXY[0], edgeXY[0], i - 1); + + // Use the point where it crossed this grid line as the reference for the + // next iteration + hint = edgePoint; + hintXY = edgeXY; + } + + // Mark every cell the path has crossed between the last east to west grid + // line it crossed and the end (Step 5) + this.fillInGridSquares_(hintXY[0], endXY[0], i - 1); + + } else { + // Iterate over the east to west grid lines between the start and end cells + for (i = startXY[1]; i > endXY[1]; i--) { + // Find the latlng of the point where the path segment intersects with + // this grid line (Step 2 & 3) + edgePoint = this.getGridIntersect_(start, brng, this.latGrid_[i]); + + // Find the cell containing this intersect point (Step 4) + edgeXY = this.getGridCoordsFromHint_(edgePoint, hint, hintXY); + + // Mark every cell the path has crossed between this grid and the start, + // or the previous east to west grid line it crossed (Step 5) + this.fillInGridSquares_(hintXY[0], edgeXY[0], i); + + // Use the point where it crossed this grid line as the reference for the + // next iteration + hint = edgePoint; + hintXY = edgeXY; + } + + // Mark every cell the path has crossed between the last east to west grid + // line it crossed and the end (Step 5) + this.fillInGridSquares_(hintXY[0], endXY[0], i); + + } +}; + +/** + * Find the latlng at which a path segment intersects with a given + * line of latitude + * + * @param {LatLng} start The vertex at the start of the path segment + * @param {Number} brng The bearing of the line from start to end + * @param {Number} gridLineLat The latitude of the grid line being intersected + * @return {LatLng} The latlng of the point where the path segment intersects + * the grid line + */ +RouteBoxer.prototype.getGridIntersect_ = function (start, brng, gridLineLat) { + var d = this.R * ((gridLineLat.toRad() - start.lat().toRad()) / Math.cos(brng.toRad())); + return start.rhumbDestinationPoint(brng, d); +}; + +/** + * Mark all cells in a given row of the grid that lie between two columns + * for inclusion in the boxes + * + * @param {Number} startx The first column to include + * @param {Number} endx The last column to include + * @param {Number} y The row of the cells to include + */ +RouteBoxer.prototype.fillInGridSquares_ = function (startx, endx, y) { + var x; + if (startx < endx) { + for (x = startx; x <= endx; x++) { + this.markCell_([x, y]); + } + } else { + for (x = startx; x >= endx; x--) { + this.markCell_([x, y]); + } + } +}; + +/** + * Mark a cell and the 8 immediate neighbours for inclusion in the boxes + * + * @param {Number[]} square The cell to mark + */ +RouteBoxer.prototype.markCell_ = function (cell) { + var x = cell[0]; + var y = cell[1]; + if(x > 0){ + + this.grid_[x - 1][y] = 1; + this.grid_[x - 1][y + 1] = 1; +} +if(y > 0){ + this.grid_[x][y - 1] = 1; + this.grid_[x + 1][y - 1] = 1; +} +if(x>0&&y>0){ + this.grid_[x - 1][y - 1] = 1; +} + this.grid_[x][y] = 1; + this.grid_[x + 1][y] = 1; + this.grid_[x][y + 1] = 1; + this.grid_[x + 1][y + 1] = 1; +}; + +/** + * Create two sets of bounding boxes, both of which cover all of the cells that + * have been marked for inclusion. + * + * The first set is created by combining adjacent cells in the same column into + * a set of vertical rectangular boxes, and then combining boxes of the same + * height that are adjacent horizontally. + * + * The second set is created by combining adjacent cells in the same row into + * a set of horizontal rectangular boxes, and then combining boxes of the same + * width that are adjacent vertically. + * + */ +RouteBoxer.prototype.mergeIntersectingCells_ = function () { + var x, y, box; + + // The box we are currently expanding with new cells + var currentBox = null; + + // Traverse the grid a row at a time + for (y = 0; y < this.grid_[0].length; y++) { + for (x = 0; x < this.grid_.length; x++) { + + if (this.grid_[x][y]) { + // This cell is marked for inclusion. If the previous cell in this + // row was also marked for inclusion, merge this cell into it's box. + // Otherwise start a new box. + box = this.getCellBounds_([x, y]); + if (currentBox) { + currentBox.extend(box.getNorthEast()); + } else { + currentBox = box; + } + + } else { + // This cell is not marked for inclusion. If the previous cell was + // marked for inclusion, merge it's box with a box that spans the same + // columns from the row below if possible. + this.mergeBoxesY_(currentBox); + currentBox = null; + } + } + // If the last cell was marked for inclusion, merge it's box with a matching + // box from the row below if possible. + this.mergeBoxesY_(currentBox); + currentBox = null; + } + + // Traverse the grid a column at a time + for (x = 0; x < this.grid_.length; x++) { + for (y = 0; y < this.grid_[0].length; y++) { + if (this.grid_[x][y]) { + + // This cell is marked for inclusion. If the previous cell in this + // column was also marked for inclusion, merge this cell into it's box. + // Otherwise start a new box. + if (currentBox) { + box = this.getCellBounds_([x, y]); + currentBox.extend(box.getNorthEast()); + } else { + currentBox = this.getCellBounds_([x, y]); + } + + } else { + // This cell is not marked for inclusion. If the previous cell was + // marked for inclusion, merge it's box with a box that spans the same + // rows from the column to the left if possible. + this.mergeBoxesX_(currentBox); + currentBox = null; + + } + } + // If the last cell was marked for inclusion, merge it's box with a matching + // box from the column to the left if possible. + this.mergeBoxesX_(currentBox); + currentBox = null; + } +}; + +/** + * Search for an existing box in an adjacent row to the given box that spans the + * same set of columns and if one is found merge the given box into it. If one + * is not found, append this box to the list of existing boxes. + * + * @param {LatLngBounds} The box to merge + */ +RouteBoxer.prototype.mergeBoxesX_ = function (box) { + if (box !== null) { + for (var i = 0; i < this.boxesX_.length; i++) { + if (this.boxesX_[i].getNorthEast().lng() === box.getSouthWest().lng() && + this.boxesX_[i].getSouthWest().lat() === box.getSouthWest().lat() && + this.boxesX_[i].getNorthEast().lat() === box.getNorthEast().lat()) { + this.boxesX_[i].extend(box.getNorthEast()); + return; + } + } + this.boxesX_.push(box); + } +}; + +/** + * Search for an existing box in an adjacent column to the given box that spans + * the same set of rows and if one is found merge the given box into it. If one + * is not found, append this box to the list of existing boxes. + * + * @param {LatLngBounds} The box to merge + */ +RouteBoxer.prototype.mergeBoxesY_ = function (box) { + if (box !== null) { + for (var i = 0; i < this.boxesY_.length; i++) { + if (this.boxesY_[i].getNorthEast().lat() === box.getSouthWest().lat() && + this.boxesY_[i].getSouthWest().lng() === box.getSouthWest().lng() && + this.boxesY_[i].getNorthEast().lng() === box.getNorthEast().lng()) { + this.boxesY_[i].extend(box.getNorthEast()); + return; + } + } + this.boxesY_.push(box); + } +}; + +/** + * Obtain the LatLng of the origin of a cell on the grid + * + * @param {Number[]} cell The cell to lookup. + * @return {LatLng} The latlng of the origin of the cell. + */ +RouteBoxer.prototype.getCellBounds_ = function (cell) { + return new google.maps.LatLngBounds( + new google.maps.LatLng(this.latGrid_[cell[1]], this.lngGrid_[cell[0]]), + new google.maps.LatLng(this.latGrid_[cell[1] + 1], this.lngGrid_[cell[0] + 1])); +}; + +/* Based on the Latitude/longitude spherical geodesy formulae & scripts + at http://www.movable-type.co.uk/scripts/latlong.html + (c) Chris Veness 2002-2010 +*/ +google.maps.LatLng.prototype.rhumbDestinationPoint = function (brng, dist) { + var R = 6371; // earth's mean radius in km + var d = parseFloat(dist) / R; // d = angular distance covered on earth's surface + var lat1 = this.lat().toRad(), lon1 = this.lng().toRad(); + brng = brng.toRad(); + + var lat2 = lat1 + d * Math.cos(brng); + var dLat = lat2 - lat1; + var dPhi = Math.log(Math.tan(lat2 / 2 + Math.PI / 4) / Math.tan(lat1 / 2 + Math.PI / 4)); + var q = (Math.abs(dLat) > 1e-10) ? dLat / dPhi : Math.cos(lat1); + var dLon = d * Math.sin(brng) / q; + // check for going past the pole + if (Math.abs(lat2) > Math.PI / 2) { + lat2 = lat2 > 0 ? Math.PI - lat2 : - (Math.PI - lat2); + } + var lon2 = (lon1 + dLon + Math.PI) % (2 * Math.PI) - Math.PI; + + if (isNaN(lat2) || isNaN(lon2)) { + return null; + } + return new google.maps.LatLng(lat2.toDeg(), lon2.toDeg()); +}; + +google.maps.LatLng.prototype.rhumbBearingTo = function (dest) { + var dLon = (dest.lng() - this.lng()).toRad(); + var dPhi = Math.log(Math.tan(dest.lat().toRad() / 2 + Math.PI / 4) / Math.tan(this.lat().toRad() / 2 + Math.PI / 4)); + if (Math.abs(dLon) > Math.PI) { + dLon = dLon > 0 ? -(2 * Math.PI - dLon) : (2 * Math.PI + dLon); + } + return Math.atan2(dLon, dPhi).toBrng(); +}; + +/** + * Extend the Number object to convert degrees to radians + * + * @return {Number} Bearing in radians + * @ignore + */ +Number.prototype.toRad = function () { + return this * Math.PI / 180; +}; + +/** + * Extend the Number object to convert radians to degrees + * + * @return {Number} Bearing in degrees + * @ignore + */ +Number.prototype.toDeg = function () { + return this * 180 / Math.PI; +}; + +/** + * Normalize a heading in degrees to between 0 and +360 + * + * @return {Number} Return + * @ignore + */ +Number.prototype.toBrng = function () { + return (this.toDeg() + 360) % 360; +}; diff --git a/mobility-track-web/test/map-handler.js b/mobility-track-web/test/map-handler.js new file mode 100644 index 0000000..3448282 --- /dev/null +++ b/mobility-track-web/test/map-handler.js @@ -0,0 +1,79 @@ +var map =document.getElementById('map'), g,boxer,paths=[],markers = [],trackers = getTrackerLocations(); + +function initMap() { + g = google.maps; + map = document.getElementById('map'); + drawPath(); +} + +initMap(); + +function drawPath() { + var points = []; + for (point of arguments) { + if(point.hasOwnProperty("lat")&&point.hasOwnProperty("lng")){ + points.push(point); + } + } + + if(points.length <= 1){ + //Default path + points = [{"lat":37.5,"lng":-122},{"lat":37,"lng":-122}]; + } + + var newPath = new g.Polyline({ + path: points, + strokeWeight: 5, + strokeOpacity: 1.0, + strokeColor: '#0C7FDD', + editable:true, + draggable:true, + }); + + newPath.setMap(map); + + paths.push(newPath); + + g.event.addListener(newPath.getPath(),'insert_at',function() { + updateMarkers(paths.indexOf(newPath)); + }); + g.event.addListener(newPath.getPath(),'remove_at',function() { + updateMarkers(paths.indexOf(newPath)); + }); + g.event.addListener(newPath,'dragend',function() { + updateMarkers(paths.indexOf(newPath)); + }); + g.event.addListener(newPath.getPath(),'set_at',function() { + updateMarkers(paths.indexOf(newPath)); + }); +} + +function updateMarkers(pathIndex) { + + var boxer = new RouteBoxer(); + var boxes = boxer.box(paths[pathIndex],1); + + //Remove all markers + for(marker of markers){ + marker.setMap(null); + } + markers.length = 0; + + for (box of boxes) { + for (tracker of trackers) { + if(box.contains(tracker)){ + var marker = new g.Marker({position:tracker,map:map,animation: g.Animation.DROP}); + markers.push(marker); + } + } + } +} + +function getTrackerLocations() { + var trackers = []; + //replace with query to tracker DB + for (var i = 0; i < 500; i++) { + trackers.push({"lat":(Math.random())+37,"lng":(Math.random())-122}); + } + return trackers; +} From 289c58e85eac2dce78fd8202e17a89a51de97f11 Mon Sep 17 00:00:00 2001 From: Kailas Krivanka Date: Tue, 13 Dec 2016 21:59:03 -0800 Subject: [PATCH 4/6] Revert "Added detection of nearby tracker locations to path" This reverts commit 9c173e31039cea3ec3e8fac0215f40ba9ce3bab6. --- mobility-track-android/app/app.iml | 2 + mobility-track-android/app/build.gradle | 2 + .../ucsc/mobility_track/MapHandler.java | 427 +----------------- 3 files changed, 6 insertions(+), 425 deletions(-) diff --git a/mobility-track-android/app/app.iml b/mobility-track-android/app/app.iml index 0a7e21a..21cd6e0 100644 --- a/mobility-track-android/app/app.iml +++ b/mobility-track-android/app/app.iml @@ -111,10 +111,12 @@ + + diff --git a/mobility-track-android/app/build.gradle b/mobility-track-android/app/build.gradle index a85b351..09eedb7 100644 --- a/mobility-track-android/app/build.gradle +++ b/mobility-track-android/app/build.gradle @@ -24,4 +24,6 @@ dependencies { compile 'com.android.support:appcompat-v7:23.1.1' compile 'com.android.support:design:23.1.1' compile 'com.google.android.gms:play-services-maps:10.0.1' + // https://mvnrepository.com/artifact/org.slf4j/slf4j-simple + compile group: 'org.slf4j', name: 'slf4j-simple', version: '1.6.1' } diff --git a/mobility-track-android/app/src/main/java/ogr/scorelab/ucsc/mobility_track/MapHandler.java b/mobility-track-android/app/src/main/java/ogr/scorelab/ucsc/mobility_track/MapHandler.java index 01134c1..4107593 100644 --- a/mobility-track-android/app/src/main/java/ogr/scorelab/ucsc/mobility_track/MapHandler.java +++ b/mobility-track-android/app/src/main/java/ogr/scorelab/ucsc/mobility_track/MapHandler.java @@ -1,14 +1,11 @@ package ogr.scorelab.ucsc.mobility_track; -import android.util.Log; import android.view.View; import com.google.android.gms.maps.GoogleMap; import com.google.android.gms.maps.model.LatLng; -import com.google.android.gms.maps.model.LatLngBounds; import com.google.android.gms.maps.model.Marker; import com.google.android.gms.maps.model.MarkerOptions; -import com.google.android.gms.maps.model.PolygonOptions; import com.google.android.gms.maps.model.Polyline; import com.google.android.gms.maps.model.PolylineOptions; @@ -27,8 +24,6 @@ class MapHandler implements GoogleMap.OnMarkerDragListener{ private HashMap paths = new HashMap<>(); - private final double SEARCH_RADIUS_IN_M = 1000; - MapHandler(GoogleMap map) { this.map = map; map.setOnMarkerDragListener(this); @@ -49,13 +44,11 @@ void drawPath(LatLng... points){ map.addMarker(new MarkerOptions().position(pos).draggable(true)).setTag(new MarkerTag(line.getId(),pos)); } - showTrackers(line.getPoints()); - } //Draw path with default locations void drawPath(){ - drawPath(new LatLng(37.2,-122),new LatLng(37,-122)); + drawPath(new LatLng(38,-122),new LatLng(37,-122)); } @Override @@ -78,43 +71,11 @@ public void onMarkerDrag(Marker marker) { @Override public void onMarkerDragEnd(Marker marker) { - showTrackers(paths.get(((MarkerTag) marker.getTag()).getLineID()).getPoints()); - } - - private void showTrackers(List points){ - - List boxes = new RouteBoxer().getRouteBoxes(points,SEARCH_RADIUS_IN_M); - - for (LatLngBounds box : - boxes) { - - LatLng northEast = box.northeast; - LatLng southWest = box.southwest; - - map.addPolygon(new PolygonOptions().add(new LatLng(northEast.latitude,northEast.longitude),new LatLng(northEast.latitude,southWest.longitude),new LatLng(southWest.latitude,southWest.longitude),new LatLng(southWest.latitude,northEast.longitude))); - } - - - LatLng[] trackers = generateTrackers(); - - for (int i = 0; i < trackers.length; i++) { - for (LatLngBounds box - :boxes) { - if(box.contains(trackers[i])){ - map.addMarker(new MarkerOptions().position(trackers[i])); - } - } - } } - private LatLng[] generateTrackers(){ - LatLng[] trackers = new LatLng[100]; + private void showTrackers(){ - for(int i = 0; i < trackers.length; i++){ - trackers[i] = new LatLng((Math.random()/2)+37,(Math.random()/2)-122); - } - return trackers; } //Class set as tag to marker to aid in identification @@ -140,388 +101,4 @@ String getLineID() { return lineID; } } - - - private class RouteBoxer{ - double R = 6371; - ArrayList vertLines = new ArrayList<>(); - ArrayList horLines = new ArrayList<>(); - ArrayList boxesX = new ArrayList<>(); - ArrayList boxesY = new ArrayList<>(); - boolean[][] grid; - - private List getRouteBoxes(List points,double radius){ - ArrayList boxes = new ArrayList<>(); - - LatLngBounds.Builder bigBoundsBuilder = new LatLngBounds.Builder(); - - - for (LatLng point : - points) { - bigBoundsBuilder.include(point); - } - - LatLngBounds bigBounds = bigBoundsBuilder.build(); - LatLng center = bigBounds.getCenter(); - - vertLines.add(center.latitude); - vertLines.add(rhumbDestinationPoint(0,radius,center).latitude); - for(int i = 2;vertLines.get(i-2) < bigBounds.northeast.latitude;i++){ - vertLines.add(rhumbDestinationPoint(0,radius*i,center).latitude); - } - - for(int i = 1;vertLines.get(1) > bigBounds.southwest.latitude;i++){ - vertLines.add(0,rhumbDestinationPoint(180,radius*i,center).latitude); - } - - horLines.add(center.longitude); - horLines.add(rhumbDestinationPoint(90,radius,center).longitude); - for(int i = 2;horLines.get(i-2) < bigBounds.northeast.longitude;i++){ - horLines.add(rhumbDestinationPoint(90,radius*i,center).longitude); - } - - for(int i = 1;horLines.get(1) > bigBounds.southwest.longitude;i++){ - horLines.add(0,rhumbDestinationPoint(270,radius*i,center).longitude); - } - - grid = new boolean[horLines.size()][vertLines.size()]; - - findCellsInPath(points); - mergeIntersectingCells(); -// -// for (int i = 1; i < horLines.size(); i++) { -// for (int j = 1; j < vertLines.size(); j++) { -// Log.e("Debug","I: "+i+" size: "+horLines.size()); -// Log.e("Debug","J: "+i+" size: "+vertLines.size()); -// LatLngBounds box = getCellBounds(new int[]{i-1,j-1}); -// boxesX.add(box); -// boxesY.add(box); -// } -// } - - return boxesX; - } - - private void findCellsInPath(List points){ - // Find the cell where the path begins - int[] hintXY = this.getCellCordsOfPoint(points.get(0)); - - // Mark that cell and it's neighbours for inclusion in the boxes - this.markCell(hintXY); - - // Work through each vertex on the path identifying which grid cell it is in - for (int i = 1; i < points.size(); i++) { - // Use the known cell of the previous vertex to help find the cell of this vertex - int[] gridXY = this.getGridCoordsFromHint(points.get(i), points.get(i-1), hintXY); - - if (gridXY[0] == hintXY[0] && gridXY[1] == hintXY[1]) { - // This vertex is in the same cell as the previous vertex - // The cell will already have been marked for inclusion in the boxes - continue; - - } else if ((Math.abs(hintXY[0] - gridXY[0]) == 1 && hintXY[1] == gridXY[1]) || - (hintXY[0] == gridXY[0] && Math.abs(hintXY[1] - gridXY[1]) == 1)) { - // This vertex is in a cell that shares an edge with the previous cell - // Mark this cell and it's neighbours for inclusion in the boxes - this.markCell(gridXY); - - } else { - // This vertex is in a cell that does not share an edge with the previous - // cell. This means that the path passes through other cells between - // this vertex and the previous vertex, and we must determine which cells - // it passes through - this.getGridIntersects(points.get(i - 1), points.get(i), hintXY, gridXY); - } - - // Use this cell to find and compare with the next one - hintXY = gridXY; - } - } - - private void getGridIntersects(LatLng start,LatLng end,int[] startXY,int[] endXY) { - int i; - LatLng edgePoint; - int[] edgeXY; - double brng = rhumbBearingTo(start,end); // Step 1. - - - LatLng hint = start; - int[] hintXY = startXY; - - // Handle a line segment that travels south first - if (end.latitude > start.latitude) { - // Iterate over the east to west grid lines between the start and end cells - for (i = startXY[1] + 1; i <= endXY[1]; i++) { - // Find the latlng of the point where the path segment intersects with - // this grid line (Step 2 & 3) - edgePoint = this.getGridIntersect(start, brng, this.vertLines.get(i)); - - // Find the cell containing this intersect point (Step 4) - edgeXY = this.getGridCoordsFromHint(edgePoint, hint, hintXY); - - // Mark every cell the path has crossed between this grid and the start, - // or the previous east to west grid line it crossed (Step 5) - this.fillInGridSquares(hintXY[0], edgeXY[0], i - 1); - - // Use the point where it crossed this grid line as the reference for the - // next iteration - hint = edgePoint; - hintXY = edgeXY; - } - - // Mark every cell the path has crossed between the last east to west grid - // line it crossed and the end (Step 5) - this.fillInGridSquares(hintXY[0], endXY[0], i - 1); - - } else { - // Iterate over the east to west grid lines between the start and end cells - for (i = startXY[1]; i > endXY[1]; i--) { - // Find the latlng of the point where the path segment intersects with - // this grid line (Step 2 & 3) - edgePoint = this.getGridIntersect(start, brng, this.vertLines.get(i)); - - // Find the cell containing this intersect point (Step 4) - edgeXY = this.getGridCoordsFromHint(edgePoint, hint, hintXY); - - // Mark every cell the path has crossed between this grid and the start, - // or the previous east to west grid line it crossed (Step 5) - this.fillInGridSquares(hintXY[0], edgeXY[0], i); - - // Use the point where it crossed this grid line as the reference for the - // next iteration - hint = edgePoint; - hintXY = edgeXY; - } - - // Mark every cell the path has crossed between the last east to west grid - // line it crossed and the end (Step 5) - this.fillInGridSquares(hintXY[0], endXY[0], i); - - } - } - - private void mergeIntersectingCells() { - int x, y; - LatLngBounds box; - - // The box we are currently expanding with new cells - LatLngBounds currentBox = null; - -// // Traverse the grid a row at a time -// for (y = 0; y < this.grid[0].length; y++) { -// for (x = 0; x < this.grid.length; x++) { -// -// if (this.grid[x][y]) { -// // This cell is marked for inclusion. If the previous cell in this -// // row was also marked for inclusion, merge this cell into it's box. -// // Otherwise start a new box. -// box = this.getCellBounds(new int[]{x, y}); -// if (currentBox != null) { -// currentBox.including(box.northeast); -// } else { -// currentBox = box; -// } -// -// } else { -// // This cell is not marked for inclusion. If the previous cell was -// // marked for inclusion, merge it's box with a box that spans the same -// // columns from the row below if possible. -// this.mergeBoxesY(currentBox); -// currentBox = null; -// } -// } -// // If the last cell was marked for inclusion, merge it's box with a matching -// // box from the row below if possible. -// this.mergeBoxesY(currentBox); -// currentBox = null; -// } - - // Traverse the grid a column at a time - Log.e("Debug","Grid: "+grid.length+" rows, "+grid[0].length+" columns"); - Log.e("Debug","Vert: "+vertLines.size()+" Hor: "+horLines.size()); - for (x = 0; x < this.grid.length-1; x++) { - for (y = 0; y < this.grid[0].length-1; y++) { - if (this.grid[x][y]) { - - // This cell is marked for inclusion. If the previous cell in this - // column was also marked for inclusion, merge this cell into it's box. - // Otherwise start a new box. -// if (currentBox != null) { -// Log.e("Debug","X: "+x+" Y: "+y); - -// box = this.getCellBounds(new int[]{x, y}); -// currentBox.including(box.northeast); -// } else { -// currentBox = this.getCellBounds(new int[]{x, y}); -// } - - currentBox = this.getCellBounds(new int[]{x, y}); - boxesX.add(currentBox); - - } else { - // This cell is not marked for inclusion. If the previous cell was - // marked for inclusion, merge it's box with a box that spans the same - // rows from the column to the left if possible. -// this.mergeBoxesX(currentBox); -// currentBox = null; - - } - } - // If the last cell was marked for inclusion, merge it's box with a matching - // box from the column to the left if possible. -// this.mergeBoxesX(currentBox); -// currentBox = null; - } - } - - -/** - * Search for an existing box in an adjacent row to the given box that spans the - * same set of columns and if one is found merge the given box into it. If one - * is not found, append this box to the list of existing boxes. - * - * @param {LatLngBounds} The box to merge - */ - private void mergeBoxesX(LatLngBounds box) { - if (box != null) { - for (LatLngBounds currentBox : - boxesX) { - if (box.northeast.longitude == box.southwest.longitude && - currentBox.southwest.latitude == box.southwest.latitude && - currentBox.northeast.latitude == box.northeast.latitude) { - currentBox.including(box.northeast); - return; - } - } - this.boxesX.add(box); - } - }; - -/** - * Search for an existing box in an adjacent column to the given box that spans - * the same set of rows and if one is found merge the given box into it. If one - * is not found, append this box to the list of existing boxes. - * - * @param {LatLngBounds} The box to merge - */ - private void mergeBoxesY(LatLngBounds box) { - if (box != null) { - for (LatLngBounds currentBox : - boxesY) { - if (currentBox.northeast.latitude == box.southwest.latitude && - currentBox.southwest.longitude == box.southwest.longitude && - currentBox.northeast.longitude == box.northeast.longitude) { - currentBox.including(box.northeast); - return; - } - } - this.boxesY.add(box); - } - } - - private LatLngBounds getCellBounds(int[] cell) { - return new LatLngBounds(new LatLng(vertLines.get(cell[1]), this.horLines.get(cell[0])), - new LatLng(this.vertLines.get(cell[1] + 1), this.horLines.get(cell[0] + 1))); - }; - - private LatLng getGridIntersect(LatLng start, double brng, double gridLineLat) { - double d = this.R * ((toRad(gridLineLat) - toRad(start.latitude) / Math.cos(toRad(brng)))); - return rhumbDestinationPoint(brng,d,start); - } - - private void fillInGridSquares(int startx,int endx,int y) { - int x; - if (startx < endx) { - for (x = startx; x <= endx; x++) { - this.markCell(new int[]{x, y}); - } - } else { - for (x = startx; x >= endx; x--) { - this.markCell(new int[]{x, y}); - } - } - } - - private void markCell(int[] cell) { - int x = cell[0]; - int y = cell[1]; - this.grid[x - 1][y - 1] = true; - this.grid[x][y - 1] = true; - this.grid[x + 1][y - 1] = true; - this.grid[x - 1][y] = true; - this.grid[x][y] = true; - this.grid[x + 1][y] = true; - this.grid[x - 1][y + 1] = true; - this.grid[x][y + 1] = true; - this.grid[x + 1][y + 1] = true; - }; - - private int[] getCellCordsOfPoint(LatLng point){ - int x = 0; - int y = 0; - while (this.horLines.get(x) < point.longitude) {x++;} - while (this.vertLines.get(y) < point.latitude) {y++;} - return (new int[]{x, y}); - } - - private int[] getGridCoordsFromHint(LatLng point,LatLng hintlatlng,int[] hint) { - int x, y; - if (point.longitude > hintlatlng.longitude) { - for (x = hint[0]; this.horLines.get(x + 1) < point.longitude; x++) {} - } else { - for (x = hint[0]; this.horLines.get(x) > point.longitude; x--) {} - } - - if (point.latitude > hintlatlng.latitude) { - for (y = hint[1]; this.vertLines.get(y + 1) < point.latitude; y++) {} - } else { - for (y = hint[1]; this.vertLines.get(y) > point.latitude; y--) {} - } - - return (new int[]{x, y}); - }; - - private LatLng rhumbDestinationPoint(double brng, double dist, LatLng pos) { - double d = dist/6378137; - double lat1 = toRad(pos.latitude), lon1 = toRad(pos.longitude); - brng = toRad(brng); - - double dLat = d*Math.cos(brng); - - if (Math.abs(dLat) < 1e-10) dLat = 0; - - double lat2 = lat1 + dLat; - double dPhi = Math.log(Math.tan(lat2/2+Math.PI/4)/Math.tan(lat1/2+Math.PI/4)); - double q = (dPhi!=0) ? dLat/dPhi : Math.cos(lat1); - double dLon = d*Math.sin(brng)/q; - - if (Math.abs(lat2) > Math.PI/2) lat2 = lat2>0 ? Math.PI-lat2 : -Math.PI-lat2; - - double lon2 = (lon1+dLon+3*Math.PI)%(2*Math.PI) - Math.PI; - - return new LatLng(toDeg(lat2), toDeg(lon2)); - } - - private double rhumbBearingTo(LatLng start, LatLng dest) { - double dLon = toRad(dest.longitude - start.longitude); - double dPhi = Math.log(Math.tan(toRad(dest.latitude) / 2 + Math.PI / 4) / Math.tan(toRad(start.latitude) / 2 + Math.PI / 4)); - if (Math.abs(dLon) > Math.PI) { - dLon = dLon > 0 ? -(2 * Math.PI - dLon) : (2 * Math.PI + dLon); - } - return toBrng(Math.atan2(dLon, dPhi)); - } - - - private double toRad(double value) { - return value * Math.PI / 180; - } - - private double toDeg(double value) { - return value * 180 / Math.PI; - } - - private double toBrng(double value) { - return (toDeg(value) + 360) % 360; - } - } } From 690dc3ea700798402a724dc303710740532ccc80 Mon Sep 17 00:00:00 2001 From: Kailas Krivanka Date: Tue, 13 Dec 2016 21:59:09 -0800 Subject: [PATCH 5/6] Revert "Added paths to map" This reverts commit ead5446d9c99778918feb485c5f5eb310b30ffda. --- mobility-track-android/app/app.iml | 2 - mobility-track-android/app/build.gradle | 2 - .../ucsc/mobility_track/MainActivity.java | 6 - .../ucsc/mobility_track/MapHandler.java | 104 ------------------ 4 files changed, 114 deletions(-) delete mode 100644 mobility-track-android/app/src/main/java/ogr/scorelab/ucsc/mobility_track/MapHandler.java diff --git a/mobility-track-android/app/app.iml b/mobility-track-android/app/app.iml index 21cd6e0..0a7e21a 100644 --- a/mobility-track-android/app/app.iml +++ b/mobility-track-android/app/app.iml @@ -111,12 +111,10 @@ - - diff --git a/mobility-track-android/app/build.gradle b/mobility-track-android/app/build.gradle index 09eedb7..a85b351 100644 --- a/mobility-track-android/app/build.gradle +++ b/mobility-track-android/app/build.gradle @@ -24,6 +24,4 @@ dependencies { compile 'com.android.support:appcompat-v7:23.1.1' compile 'com.android.support:design:23.1.1' compile 'com.google.android.gms:play-services-maps:10.0.1' - // https://mvnrepository.com/artifact/org.slf4j/slf4j-simple - compile group: 'org.slf4j', name: 'slf4j-simple', version: '1.6.1' } diff --git a/mobility-track-android/app/src/main/java/ogr/scorelab/ucsc/mobility_track/MainActivity.java b/mobility-track-android/app/src/main/java/ogr/scorelab/ucsc/mobility_track/MainActivity.java index 6aed9f1..6828d5d 100644 --- a/mobility-track-android/app/src/main/java/ogr/scorelab/ucsc/mobility_track/MainActivity.java +++ b/mobility-track-android/app/src/main/java/ogr/scorelab/ucsc/mobility_track/MainActivity.java @@ -21,8 +21,6 @@ import com.google.android.gms.maps.MapFragment; import com.google.android.gms.maps.OnMapReadyCallback; import com.google.android.gms.maps.UiSettings; -import com.google.android.gms.maps.model.LatLng; -import com.google.android.gms.maps.model.MarkerOptions; import org.json.JSONArray; import org.json.JSONException; @@ -210,9 +208,5 @@ public void onMapReady(GoogleMap googleMap) { return; } googleMap.setMyLocationEnabled(true); - - - MapHandler handler = new MapHandler(googleMap); - handler.drawPath(); } } diff --git a/mobility-track-android/app/src/main/java/ogr/scorelab/ucsc/mobility_track/MapHandler.java b/mobility-track-android/app/src/main/java/ogr/scorelab/ucsc/mobility_track/MapHandler.java deleted file mode 100644 index 4107593..0000000 --- a/mobility-track-android/app/src/main/java/ogr/scorelab/ucsc/mobility_track/MapHandler.java +++ /dev/null @@ -1,104 +0,0 @@ -package ogr.scorelab.ucsc.mobility_track; - -import android.view.View; - -import com.google.android.gms.maps.GoogleMap; -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 java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * Created by Kailas on 12/10/2016. - */ - -class MapHandler implements GoogleMap.OnMarkerDragListener{ - - private GoogleMap map; - - private HashMap paths = new HashMap<>(); - - MapHandler(GoogleMap map) { - this.map = map; - map.setOnMarkerDragListener(this); - } - - void drawPath(LatLng... points){ - if(points.length <= 1){ - return; - } - - PolylineOptions ops = new PolylineOptions(); - ops.add(points); - Polyline line = map.addPolyline(ops); - paths.put(line.getId(),line); - - for (LatLng pos: - points) { - map.addMarker(new MarkerOptions().position(pos).draggable(true)).setTag(new MarkerTag(line.getId(),pos)); - } - - } - - //Draw path with default locations - void drawPath(){ - drawPath(new LatLng(38,-122),new LatLng(37,-122)); - } - - @Override - public void onMarkerDragStart(Marker marker) { - - } - - @Override - public void onMarkerDrag(Marker marker) { - MarkerTag tag = ((MarkerTag) marker.getTag()); - Polyline line = paths.get(tag.getLineID()); - - List points = line.getPoints(); - int index = points.indexOf(tag.getPos()); - points.set(index,marker.getPosition()); - tag.updatePos(marker.getPosition()); - - line.setPoints(points); - } - - @Override - public void onMarkerDragEnd(Marker marker) { - - } - - private void showTrackers(){ - - } - - //Class set as tag to marker to aid in identification - private class MarkerTag{ - - private LatLng pos; - private String lineID; - - MarkerTag(String lineID, LatLng pos) { - this.lineID = lineID; - this.pos = pos; - } - - void updatePos(LatLng newPos){ - this.pos = newPos; - } - - LatLng getPos() { - return pos; - } - - String getLineID() { - return lineID; - } - } -} From a004781860dabc12264009b970d841861218f193 Mon Sep 17 00:00:00 2001 From: Kailas Krivanka Date: Wed, 14 Dec 2016 07:22:18 -0800 Subject: [PATCH 6/6] Added test html in seperate folder --- .../test/{ => trackers_along_path}/RouteBoxer.js | 0 .../test/{ => trackers_along_path}/map-handler.js | 9 +++++---- .../test/trackers_along_path/map.html | 15 +++++++++++++++ 3 files changed, 20 insertions(+), 4 deletions(-) rename mobility-track-web/test/{ => trackers_along_path}/RouteBoxer.js (100%) rename mobility-track-web/test/{ => trackers_along_path}/map-handler.js (90%) create mode 100644 mobility-track-web/test/trackers_along_path/map.html diff --git a/mobility-track-web/test/RouteBoxer.js b/mobility-track-web/test/trackers_along_path/RouteBoxer.js similarity index 100% rename from mobility-track-web/test/RouteBoxer.js rename to mobility-track-web/test/trackers_along_path/RouteBoxer.js diff --git a/mobility-track-web/test/map-handler.js b/mobility-track-web/test/trackers_along_path/map-handler.js similarity index 90% rename from mobility-track-web/test/map-handler.js rename to mobility-track-web/test/trackers_along_path/map-handler.js index 3448282..850985e 100644 --- a/mobility-track-web/test/map-handler.js +++ b/mobility-track-web/test/trackers_along_path/map-handler.js @@ -1,13 +1,14 @@ -var map =document.getElementById('map'), g,boxer,paths=[],markers = [],trackers = getTrackerLocations(); +var map, g,boxer,paths=[],markers = [],trackers = getTrackerLocations(); function initMap() { g = google.maps; - map = document.getElementById('map'); + map = new g.Map(document.getElementById('map'),{ + zoom:8, + center:{lat:37,lng:-122}, + }); drawPath(); } -initMap(); - function drawPath() { var points = []; for (point of arguments) { diff --git a/mobility-track-web/test/trackers_along_path/map.html b/mobility-track-web/test/trackers_along_path/map.html new file mode 100644 index 0000000..4584a38 --- /dev/null +++ b/mobility-track-web/test/trackers_along_path/map.html @@ -0,0 +1,15 @@ + + + + + + +
+ +
+ + + + + +