Draw a Geographically Accurate Arc on a Map for Android - java

I would like to draw an arc with a center GPS point and two end GPS points. I can use any mapping software that can be used with android.
So far I have tried Google Maps for android. Mapbox for android. ARCGIS for android. None of them seem to be able to do this simple task.
I'm guessing I need some-sort of overlay?

Unfortunately, I can't really give you a complete answer right now as this question requires heavy math and I'm unable to fully understand the data you have shown in the table. I assume you are trying to have an end result that looks something like this. If that is the case, you have a center point, radius, and the arc starting LatLng and end LatLng (This could also be calculated from the center point with additional math). I'll try and update this answer if I get a chance to write some code handling the math side. As far as I know, none of the map libraries (Google Maps, Mapbox) include this.
Basically, I'd write a method that returns a bunch of latlng points following the arc you wish to draw. From this, i'd draw a line using the addPolyline method.
// Draw line from list of LatLng called pointsArray
mapView.addPolyline(new PolylineOptions()
.add(pointsArray)
.color(Color.parseColor("#3bb2d0"))
.width(2));
Hopefully this helps you out, at least with getting started and like I said, I'll try and add the math bits later on if I get a chance.

First of all, you have to change the geodata representation.
From this:
36°46'02.5"S 174°50'03.6"E GRC
36°56'18.3"S 174°33'09.7"E CWA 36°52'19.2"S 174°29'23.5"E 5.0' NM
36°48'19.8"S 174°25'37.7"E GRC
36°41'06.1"S 174°37'32.9"E CCA 36°39'54.5"S 174°38'40.7"E 1.5' NM
36°41'00.3"S 174°39'57.1"E GRC
36°39'49.4"S 174°41'32.3"E CCA 36°38'43.5"S 174°40'15.7"E 1.5' NM
36°39'17.5"S 174°41'59.7"E GRC
36°39'38.6"S 174°44'37.7"E GRC
36°37'08.8"S 174°47'27.4"E CWA 36°42'03.8"S 174°46'17.0"E 5.0' NM
To this:
-36.76736 174.83433 GRC
-36.93842 174.55269 CWA -36.87200 174.48986 0.08333 NM
-36.80550 174.42714 GRC
-36.68503 174.62581 CCA -36.66514 174.64464 0.02500 NM
-36.68342 174.66586 GRC
-36.66372 174.69231 CCA -36.64542 174.67103 0.02500 NM
-36.65486 174.69992 GRC
-36.66072 174.74381 GRC
-36.61911 174.79094 CWA -36.70106 174.77139 0.08333 NM
Then you can use them to build a path (e.g., SVG path):
<svg
...>
<g
transform="matrix(1250,0,0,1250,46206.909,-217995.49)"
...>
<path
d=" M -36.76736,174.83433 L -36.93842,174.55269 A 0.08333,0.08333 0 0,1 -36.80550,174.42714 L -36.68503,174.62581 A 0.02500,0.02500 0 0,0 -36.68342,174.66586 L -36.66372,174.69231 A 0.02500,0.02500 0 0,0 -36.65486,174.69992 L -36.66072,174.74381 L -36.61911,174.79094 A 0.08333,0.08333 0 0,1 -36.76736,174.83433 Z"
.../>
</g>
</svg>
Actually, there is no API to create curves, arcs, etc. in GMaps/OSMDroid, so, you should draw them as many small lines. It means doing some math, but this math can be made by GeographicLib library:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
MapView map = (MapView) findViewById(R.id.map);
map.setTileSource(TileSourceFactory.MAPNIK);
map.setBuiltInZoomControls(true);
map.setMultiTouchControls(true);
IMapController mapController = map.getController();
mapController.setZoom(9);
GeoPoint startPoint = new GeoPoint(-36.66372, 174.69231);
mapController.setCenter(startPoint);
Polygon polyline = new Polygon(this);
List<GeoPoint> points = new LinkedList<>();
// I filled the data manually, but it can be done more clever, of course.
GRC(points, new GeoPoint(-36.76736, 174.83433));
CWA(points, new GeoPoint(-36.93842, 174.55269),
new GeoPoint(-36.87200, 174.48986),
new GeoPoint(-36.80550, 174.42714),
0.08333);
CCA(points, new GeoPoint(-36.68503, 174.62581),
new GeoPoint(-36.66514, 174.64464),
new GeoPoint(-36.68342, 174.66586),
0.02500);
CCA(points, new GeoPoint(-36.66372, 174.69231),
new GeoPoint(-36.64542, 174.67103),
new GeoPoint(-36.65486, 174.69992),
0.02500);
GRC(points, new GeoPoint(-36.66072, 174.74381));
CWA(points, new GeoPoint(-36.61911, 174.79094),
new GeoPoint(-36.70106, 174.77139),
new GeoPoint(-36.76736, 174.83433), //close shape going to the start point
0.08333);
polyline.setPoints(points);
polyline.setFillColor(0xA0FF00FF);
polyline.setStrokeColor(Color.BLACK);
polyline.setStrokeWidth(2f);
map.getOverlays().add(polyline);
map.invalidate();
}
private void CCA(List<GeoPoint> points, GeoPoint startPoint, GeoPoint centerPoint, GeoPoint endPoint, double radius) {
points.add(startPoint);
GeodesicData f = Geodesic.WGS84.Inverse(centerPoint.getLatitude(), centerPoint.getLongitude(), startPoint.getLatitude(), startPoint.getLongitude());
GeodesicData t = Geodesic.WGS84.Inverse(centerPoint.getLatitude(), centerPoint.getLongitude(), endPoint.getLatitude(), endPoint.getLongitude());
double ffaz = f.azi1;
double tfaz = t.azi1;
final int decrement = 1;
while (Math.abs((int)ffaz) != Math.abs((int)tfaz)) {
GeodesicData llb = Geodesic.WGS84.Direct(centerPoint.getLatitude(), centerPoint.getLongitude(), ffaz, f.s12);
points.add(new GeoPoint(llb.lat2, llb.lon2));
ffaz -= decrement;
if (ffaz <0) {
ffaz += 360;
}
}
points.add(endPoint);
}
private void CWA(List<GeoPoint> points, GeoPoint startPoint, GeoPoint centerPoint, GeoPoint endPoint, double radius) {
points.add(startPoint);
GeodesicData f = Geodesic.WGS84.Inverse(centerPoint.getLatitude(), centerPoint.getLongitude(), startPoint.getLatitude(), startPoint.getLongitude());
GeodesicData t = Geodesic.WGS84.Inverse(centerPoint.getLatitude(), centerPoint.getLongitude(), endPoint.getLatitude(), endPoint.getLongitude());
double ffaz = f.azi1;
double tfaz = t.azi1 > 0 ? t.azi1 : 360 + t.azi1;
final int increment = 1;
while (Math.abs((int)ffaz) != Math.abs((int)tfaz)) {
GeodesicData llb = Geodesic.WGS84.Direct(centerPoint.getLatitude(), centerPoint.getLongitude(), ffaz, f.s12);
points.add(new GeoPoint(llb.lat2, llb.lon2));
ffaz += increment;
if (ffaz>360) {
ffaz -= 360;
}
}
points.add(endPoint);
}
private void GRC(List<GeoPoint> points, GeoPoint geoPoint) {
points.add(geoPoint);
}
I used osmdroid+OSMBonusPack, but the code is quite universal and can be used with Google Maps easily.
The full source code.
Result:
Also, would you mind telling what kind of geodata you posted? The shape on the map looks like an allowed flight zone, I guess.

Related

Animate a car on google map on firebase change event [duplicate]

What is the best way to animate markers on Google Maps using v2 API?
I am working on a map-centered game where I track locations of people and display them on the map for each other to see. As people move, I want to animate a marker from his current to his latest position. Every person has a direction, so I need to rotate the marker appropriately.
What is the best way to do it using the new Google Maps API?
Some Google engineers have provided a nice demo video with some elegant sample code about how to animate markers from a starting point to an ending point, for all various versions of Android:
The relevant code is here:
https://gist.github.com/broady/6314689
And a nice demo video of all of it in action.
http://youtu.be/WKfZsCKSXVQ
OLD DEPRECATED ANSWER BELOW
In the documentation, it is mentioned that Marker Icons cannot be changed:
Icon
A bitmap that's displayed for the marker. If the icon is left unset, a default icon is displayed. You can specify an alternative coloring of the default icon using defaultMarker(float). You can't change the icon once you've created the marker.
Google Maps API v2 Documentation
You're going to have to keep track of specific markers, perhaps using a method similar to that described here: Link a Marker to an Object, then figure out which marker you need to update. Call .remove() on the marker, then create a rotated image depending on the "direction" you want, create a new Marker with that image, and add the new Marker to the map.
You do not need to "clear" the map, simply remove the marker you want to modify, create a new one, then add it back to the map.
Unfortunately, the new Maps API is not very flexible yet. Hopefully Google continues to improve upon it.
Full Example for DiscDev's answer (Above):
LatLng fromLocation = new LatLng(38.5, -100.4); // Whatever origin coordinates
LatLng toLocation = new LatLng(37.7, -107.7); // Whatever destination coordinates
Marker marker = mMap.addMarker(new MarkerOptions().position(firstLocation));
MarkerAnimation.animateMarkerToICS(marker, toLocation, new LatLngInterpolator.Spherical());
And for those of you who uses GPS / or any position provider that receives location updates:
Marker ourGlobalMarker;
// We've got a location from some provider of ours, now we can call:
private void updateMarkerPosition(Location newLocation) {
LatLng newLatLng = new LatLng(newLocation.getLatitude(), newLocation.getLongitude());
if(ourGlobalMarker == null) { // First time adding marker to map
ourGlobalMarker = mMap.addMarker(new MarkerOptions().position(newLatLng));
}
else {
MarkerAnimation.animateMarkerToICS(ourGlobalMarker, newLatLng, new LatLngInterpolator.Spherical());
}
}
IMPORTANT:
Within 1MarkerAnimation.java If the animation duration is set to X,
and you are receiving location updates in a rate smaller then X, multiple animations will be triggered, and you might see the marker animation flickers a bit (which is not a nice user experience).
To avoid this, the animationMarkerToICS method (I took here animationMarkerToICS for example), should look something like this,
full method implementation:
private static Animator animator; // MAKING ANIMATOR GLOBAL INSTEAD OF LOCAL TO THE STATIC FUNCTION
...
// Ice Cream Sandwich compatible
#TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
public static void animateMarkerToICS(Marker marker, LatLng finalPosition, final LatLngInterpolator latLngInterpolator) {
TypeEvaluator<LatLng> typeEvaluator = new TypeEvaluator<LatLng>() {
#Override
public LatLng evaluate(float fraction, LatLng startValue, LatLng endValue) {
return latLngInterpolator.interpolate(fraction, startValue, endValue);
}
};
Property<Marker, LatLng> property = Property.of(Marker.class, LatLng.class, "position");
// ADD THIS TO STOP ANIMATION IF ALREADY ANIMATING TO AN OBSOLETE LOCATION
if(animator != null && animator.isRunning()) {
animator.cancel();
animator = null;
}
animator = ObjectAnimator.ofObject(marker, property, typeEvaluator, finalPosition);
animator.setDuration((long) ANIMATION_DURATION);
animator.start();
}
Enjoy.
Marker has a new function added as of rev.7 of API v2. Marker.setIcon, so you can use multiple icons to show direction.
//Your code
double bearing = 0.0;
bearing = getBearing(new LatLng(
currentPosition.latitude
,currentPosition.longitude),
new LatLng(
nextPosition.latitude,
nextPosition.longitude));
bearing -= 90;
CameraPosition cameraPosition = new CameraPosition
.Builder()
.target(new LatLng(nextPosition.latitude, nextPosition.longitude))
.bearing((float) bearing)
.zoom(ZOOM_LEVEL).build();
mGoogleMap.animateCamera(CameraUpdateFactory.newCameraPosition(cameraPosition), 5000, null);
animatedMarker(currentPosition,nextPosition,busMarker);
//Method for finding bearing between two points
private float getBearing(LatLng begin, LatLng end) {
double lat = Math.abs(begin.latitude - end.latitude);
double lng = Math.abs(begin.longitude - end.longitude);
if (begin.latitude < end.latitude && begin.longitude < end.longitude)
return (float) (Math.toDegrees(Math.atan(lng / lat)));
else if (begin.latitude >= end.latitude && begin.longitude < end.longitude)
return (float) ((90 - Math.toDegrees(Math.atan(lng / lat))) + 90);
else if (begin.latitude >= end.latitude && begin.longitude >= end.longitude)
return (float) (Math.toDegrees(Math.atan(lng / lat)) + 180);
else if (begin.latitude < end.latitude && begin.longitude >= end.longitude)
return (float) ((90 - Math.toDegrees(Math.atan(lng / lat))) + 270);
return -1;
}
private void animatedMarker(final LatLng startPosition,final LatLng nextPosition,final Marker mMarker)
{
final Handler handler = new Handler();
final long start = SystemClock.uptimeMillis();
final Interpolator interpolator = new AccelerateDecelerateInterpolator();
final float durationInMs = 3000;
final boolean hideMarker = false;
handler.post(new Runnable() {
long elapsed;
float t;
float v;
#Override
public void run() {
// Calculate progress using interpolator
elapsed = SystemClock.uptimeMillis() - start;
t = elapsed / durationInMs;
v = interpolator.getInterpolation(t);
LatLng currentPosition = new LatLng(
startPosition.latitude * (1 - t) + nextPosition.latitude * t,
startPosition.longitude * (1 - t) + nextPosition.longitude * t);
mMarker.setPosition(currentPosition);
// Repeat till progress is complete.
if (t < 1) {
// Post again 16ms later.
handler.postDelayed(this, 16);
} else {
if (hideMarker) {
mMarker.setVisible(false);
} else {
mMarker.setVisible(true);
}
}
}
});
}

Setting appropriate zoom Android Google Maps API [duplicate]

I have 10 markers in the GoogleMap. I want to zoom in as much as possible and keep all markers in view? In the earlier version this can be achieved from zoomToSpan() but in v2 I have no idea how about doing that. Further, I know the radius of the circle that needs to be visible.
You should use the CameraUpdate class to do (probably) all programmatic map movements.
To do this, first calculate the bounds of all the markers like so:
LatLngBounds.Builder builder = new LatLngBounds.Builder();
for (Marker marker : markers) {
builder.include(marker.getPosition());
}
LatLngBounds bounds = builder.build();
Then obtain a movement description object by using the factory: CameraUpdateFactory:
int padding = 0; // offset from edges of the map in pixels
CameraUpdate cu = CameraUpdateFactory.newLatLngBounds(bounds, padding);
Finally move the map:
googleMap.moveCamera(cu);
Or if you want an animation:
googleMap.animateCamera(cu);
That's all :)
Clarification 1
Almost all movement methods require the Map object to have passed the layout process. You can wait for this to happen using the addOnGlobalLayoutListener construct. Details can be found in comments to this answer and remaining answers. You can also find a complete code for setting map extent using addOnGlobalLayoutListener here.
Clarification 2
One comment notes that using this method for only one marker results in map zoom set to a "bizarre" zoom level (which I believe to be maximum zoom level available for given location). I think this is expected because:
The LatLngBounds bounds instance will have northeast property equal to southwest, meaning that the portion of area of the earth covered by this bounds is exactly zero. (This is logical since a single marker has no area.)
By passing bounds to CameraUpdateFactory.newLatLngBounds you essentially request a calculation of such a zoom level that bounds (having zero area) will cover the whole map view.
You can actually perform this calculation on a piece of paper. The theoretical zoom level that is the answer is +∞ (positive infinity). In practice the Map object doesn't support this value so it is clamped to a more reasonable maximum level allowed for given location.
Another way to put it: how can Map object know what zoom level should it choose for a single location? Maybe the optimal value should be 20 (if it represents a specific address). Or maybe 11 (if it represents a town). Or maybe 6 (if it represents a country). API isn't that smart and the decision is up to you.
So, you should simply check if markers has only one location and if so, use one of:
CameraUpdate cu = CameraUpdateFactory.newLatLng(marker.getPosition()) - go to marker position, leave current zoom level intact.
CameraUpdate cu = CameraUpdateFactory.newLatLngZoom(marker.getPosition(), 12F) - go to marker position, set zoom level to arbitrarily chosen value 12.
Google Map V2
The following solution works for Android Marshmallow 6 (API 23, API 24, API 25, API 26, API 27, API 28). It also works in Xamarin.
LatLngBounds.Builder builder = new LatLngBounds.Builder();
//the include method will calculate the min and max bound.
builder.include(marker1.getPosition());
builder.include(marker2.getPosition());
builder.include(marker3.getPosition());
builder.include(marker4.getPosition());
LatLngBounds bounds = builder.build();
int width = getResources().getDisplayMetrics().widthPixels;
int height = getResources().getDisplayMetrics().heightPixels;
int padding = (int) (width * 0.10); // offset from edges of the map 10% of screen
CameraUpdate cu = CameraUpdateFactory.newLatLngBounds(bounds, width, height, padding);
mMap.animateCamera(cu);
I couldnt use the onGlobalLayoutlistener, so here is another solution to prevent the
"Map size can't be 0. Most likely, layout has not yet occured for the map view. Either wait until layout has occurred or use newLatLngBounds(LatLngBounds, int, int, int) which allows you to specify the map's dimensions." error:
mMap.setOnMapLoadedCallback(new GoogleMap.OnMapLoadedCallback() {
#Override
public void onMapLoaded() {
mMap.moveCamera(CameraUpdateFactory.newLatLngBounds(builder.build(), 15));
}
});
So
I needed to use addOnGlobalLayoutListener to get the appropriate sample
for example, your Google Map is inside RelativeLayout:
RelativeLayout mapLayout = (RelativeLayout)findViewById(R.id.map_layout);
mapLayout.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
//and write code, which you can see in answer above
}
});
Working fine for me.
From this code, I am displaying multiple markers with particular zoom on map screen.
// Declared variables
private LatLngBounds bounds;
private LatLngBounds.Builder builder;
// Method for adding multiple marker points with drawable icon
private void drawMarker(LatLng point, String text) {
MarkerOptions markerOptions = new MarkerOptions();
markerOptions.position(point).title(text).icon(BitmapDescriptorFactory.fromResource(R.drawable.icon));
mMap.addMarker(markerOptions);
builder.include(markerOptions.getPosition());
}
// For adding multiple markers visible on map
#Override
public void onMapReady(GoogleMap googleMap) {
mMap = googleMap;
builder = new LatLngBounds.Builder();
for (int i = 0; i < locationList.size(); i++) {
drawMarker(new LatLng(Double.parseDouble(locationList.get(i).getLatitude()), Double.parseDouble(locationList.get(i).getLongitude())), locationList.get(i).getNo());
}
bounds = builder.build();
CameraUpdate cu = CameraUpdateFactory.newLatLngBounds(bounds, 0);
mMap.animateCamera(cu);
Note - This is not a solution to the original question. This is a solution to one of the subproblems discussed above.
Solution to #andr Clarification 2 -
Its really problematic when there's only one marker in the bounds and due to it the zoom level is set to a very high level (level 21). And Google does not provide any way to set the max zoom level at this point. This can also happen when there are more than 1 marker but they are all pretty close to each other. Then also the same problem will occur.
Solution - Suppose you want your Map to never go beyond 16 zoom level. Then after doing -
CameraUpdate cu = CameraUpdateFactory.newLatLngBounds(bounds, padding);
mMap.moveCamera(cu);
Check if your zoom level has crossed level 16(or whatever you want) -
float currentZoom = mMap.getCameraPosition().zoom;
And if this level is greater than 16, which it will only be if there are very less markers or all the markers are very close to each other, then simply zoom out your map at that particular position only by seting the zoom level to 16.
mMap.moveCamera(CameraUpdateFactory.zoomTo(16));
This way you'll never have the problem of "bizarre" zoom level explained very well by #andr too.
this would help.. from google apis demos
private List<Marker> markerList = new ArrayList<>();
Marker marker = mGoogleMap.addMarker(new MarkerOptions().position(geoLatLng)
.title(title));
markerList.add(marker);
// Pan to see all markers in view.
// Cannot zoom to bounds until the map has a size.
final View mapView = getSupportFragmentManager().findFragmentById(R.id.map).getView();
if (mapView!=null) {
if (mapView.getViewTreeObserver().isAlive()) {
mapView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#SuppressWarnings("deprecation") // We use the new method when supported
#SuppressLint("NewApi") // We check which build version we are using.
#Override
public void onGlobalLayout() {
//Calculate the markers to get their position
LatLngBounds.Builder b = new LatLngBounds.Builder();
for (Marker m : markerList) {
b.include(m.getPosition());
}
// also include current location to include in the view
b.include(new LatLng(mLocation.getLatitude(),mLocation.getLongitude()));
LatLngBounds bounds = b.build();
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
mapView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
} else {
mapView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
mGoogleMap.moveCamera(CameraUpdateFactory.newLatLngBounds(bounds, 50));
}
});
}
}
for clear info look at this url.
https://github.com/googlemaps/android-samples/blob/master/ApiDemos/app/src/main/java/com/example/mapdemo/MarkerDemoActivity.java
Show All Markers with Google map
In these Methods store all Markers and automatically zoom to show all markers in google map.
// Declare the Markers List.
List<MarkerOptions> markerList;
private BitmapDescriptor vnrPoint,banPoint;
public void storeAllMarkers()
{
markerList=new ArrayList<>();
markerList.removeAll(markerList);
// latitude and longitude of Virudhunagar
double latitude1=9.587209;
double longitude1=77.951431;
vnrPoint=BitmapDescriptorFactory.fromResource(R.drawable.location_icon_1);
LatLng vnr = new LatLng(latitude1, longitude1);
MarkerOptions vnrMarker = new MarkerOptions();
vnrMarker.position(vnr);
vnrMarker.icon(vnrPoint);
markerList.add(vnrMarker);
// latitude and longitude of Bengaluru
double latitude2=12.972442;
double longitude2=77.580643;
banPoint=BitmapDescriptorFactory.fromResource(R.drawable.location_icon_2);
LatLng ban = new LatLng(latitude2, longitude2);
MarkerOptions bengalureMarker = new MarkerOptions();
bengalureMarker.position(ban);
bengalureMarker.icon(banPoint);
markerList.add(bengalureMarker);
// You can add any numbers of MarkerOptions like this.
showAllMarkers();
}
public void showAllMarkers()
{
LatLngBounds.Builder builder = new LatLngBounds.Builder();
for (MarkerOptions m : markerList) {
builder.include(m.getPosition());
}
LatLngBounds bounds = builder.build();
int width = getResources().getDisplayMetrics().widthPixels;
int height = getResources().getDisplayMetrics().heightPixels;
int padding = (int) (width * 0.30);
// Zoom and animate the google map to show all markers
CameraUpdate cu = CameraUpdateFactory.newLatLngBounds(bounds, width, height, padding);
googleMap.animateCamera(cu);
}
I had similar problem, using following code solved the issue:
CameraUpdateFactory.newLatLngBounds(bounds, 200, 200, 5) generally the location differences in my case are no more than two neighbour cities.
zoom to fit all markers on map google maps v2
I have one other way to do this same thing works perfectly. so the idea behind to show all markers on the screen we need a center lat long and zoom level. here is the function which will give you both and need all marker's Latlng objects as input.
public Pair<LatLng, Integer> getCenterWithZoomLevel(LatLng... l) {
float max = 0;
if (l == null || l.length == 0) {
return null;
}
LatLngBounds.Builder b = new LatLngBounds.Builder();
for (int count = 0; count < l.length; count++) {
if (l[count] == null) {
continue;
}
b.include(l[count]);
}
LatLng center = b.build().getCenter();
float distance = 0;
for (int count = 0; count < l.length; count++) {
if (l[count] == null) {
continue;
}
distance = distance(center, l[count]);
if (distance > max) {
max = distance;
}
}
double scale = max / 1000;
int zoom = ((int) (16 - Math.log(scale) / Math.log(2)));
return new Pair<LatLng, Integer>(center, zoom);
}
This function return Pair object which you can use like
Pair pair = getCenterWithZoomLevel(l1,l2,l3..);
mGoogleMap.moveCamera(CameraUpdateFactory.newLatLngZoom(pair.first, pair.second));
you can instead of using padding to keep away your markers from screen boundaries, you can adjust zoom by -1.
I worked the same problem for showing multiple markers in Kotlin using a fragment
first declare a list of markers
private lateinit var markers: MutableList<Marker>
initialize this in the oncreate method of the frament
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
//initialize markers list
markers = mutableListOf()
return inflater.inflate(R.layout.fragment_driver_map, container, false)
}
on the OnMapReadyCallback add the markers to the markers list
private val callback = OnMapReadyCallback { googleMap ->
map = googleMap
markers.add(
map.addMarker(
MarkerOptions().position(riderLatLng)
.title("Driver")
.snippet("Driver")
.icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_RED))))
markers.add(
map.addMarker(
MarkerOptions().position(driverLatLng)
.title("Driver")
.snippet("Driver")
.icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_GREEN))))
Still on the callback
//create builder
val builder = LatLngBounds.builder()
//loop through the markers list
for (marker in markers) {
builder.include(marker.position)
}
//create a bound
val bounds = builder.build()
//set a 200 pixels padding from the edge of the screen
val cu = CameraUpdateFactory.newLatLngBounds(bounds,200)
//move and animate the camera
map.moveCamera(cu)
//animate camera by providing zoom and duration args, callBack set to null
map.animateCamera(CameraUpdateFactory.zoomTo(10f), 2000, null)
Merry coding guys
Use the method "getCenterCoordinate" to obtain the center coordinate and use in CameraPosition.
private void setUpMap() {
mMap.setMyLocationEnabled(true);
mMap.getUiSettings().setScrollGesturesEnabled(true);
mMap.getUiSettings().setTiltGesturesEnabled(true);
mMap.getUiSettings().setRotateGesturesEnabled(true);
clientMarker = mMap.addMarker(new MarkerOptions()
.position(new LatLng(Double.valueOf(-12.1024174), Double.valueOf(-77.0262274)))
.icon(BitmapDescriptorFactory.fromResource(R.mipmap.ic_taxi))
);
clientMarker = mMap.addMarker(new MarkerOptions()
.position(new LatLng(Double.valueOf(-12.1024637), Double.valueOf(-77.0242617)))
.icon(BitmapDescriptorFactory.fromResource(R.mipmap.ic_location))
);
camPos = new CameraPosition.Builder()
.target(getCenterCoordinate())
.zoom(17)
.build();
camUpd3 = CameraUpdateFactory.newCameraPosition(camPos);
mMap.animateCamera(camUpd3);
}
public LatLng getCenterCoordinate(){
LatLngBounds.Builder builder = new LatLngBounds.Builder();
builder.include(new LatLng(Double.valueOf(-12.1024174), Double.valueOf(-77.0262274)));
builder.include(new LatLng(Double.valueOf(-12.1024637), Double.valueOf(-77.0242617)));
LatLngBounds bounds = builder.build();
return bounds.getCenter();
}
//For adding a marker in Google map
MarkerOptions mp = new MarkerOptions();
mp.position(new LatLng(Double.parseDouble(latitude), Double.parseDouble(longitude)));
mp.snippet(strAddress);
map.addMarker(mp);
try {
b = new LatLngBounds.Builder();
if (MapDetailsList.list != null && MapDetailsList.list.size() > 0) {
for (int i = 0; i < MapDetailsList.list.size(); i++) {
b.include(new LatLng(Double.parseDouble(MapDetailsList.list.get(i).getLatitude()),
Double.parseDouble(MapDetailsList.list.get(i).getLongitude())));
}
LatLngBounds bounds = b.build();
DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
int width = displayMetrics.widthPixels;
int height = displayMetrics.heightPixels;
// Change the padding as per needed
CameraUpdate cu = CameraUpdateFactory.newLatLngBounds(bounds, width-200, height-200, 5);
// map.setCenter(bounds.getCenter());
map.animateCamera(cu);
}
} catch (Exception e) {
}
http://i64.tinypic.com/2qjybh4.png
http://i63.tinypic.com/flzwus.png
http://i63.tinypic.com/112g5fm.png

LibGDX Path (CatmullRomSpline) Constant Speed

I’m trying to achieve constant speed on a path using the LibGDX CatmullRomSpline and I’m having problems getting it to work. I’ve tried researching on this topic a lot including reading the LibGDX wiki, but their explanation for achieving constant speed doesn’t really make sense and I wasn’t able to get their method to work. https://github.com/libgdx/libgdx/wiki/Path-interface-&-Splines
In my case, the derivative values are very large (in the hundreds) so when dividing a number between 0-1 by the derivative the result is very small and the movement is very slow and still not constant. So I’m not sure exactly how their example works.
In my example I have a couple visual aids coinciding with the speed of the ball, the bar at the bottom of the screen increases in length as the speed increases and the color also changes from white to red as the speed increases.
In the act() method of MyPath.java I have two sections commented out starting with [1] and [2]. The first one is normal with the variable speed through the path and the second one is my failed attempt at getting the LibGDX wiki constant speed to work. So just un-comment these lines to switch between the two versions.
My idea for constant speed involves figuring out the speed based on the total length of the path (using the approxLength(1000) method on the spline), then using the derivative function to determine the actual speed at a given instant, and adjusting the percentage value sent into the spline to compensate for the speed changes in order to make the speed constant. However, I don’t quite understand what the derivative function actually represents. I posted a question about the derivative function earlier, but based a comment I received I figured it might be easier to ask about achieving constant speed instead. Here is my previous question on the derivative function:
LibGDX CatmullRomSpline Derivative Meaning?
Any ideas on how to achieve constant speed in my example (or explaining what the derivative function for the CatmullRomSpline actually represents so I could better understand how to use it) would be greatly appreciated.
For anyone who'd like to run the program, here are the two image files I created for my example (add these to the root of the assets folder):
http://dropshots.com/Tekker/date/2015-09-19
Here is my example code:
DesktopLauncher.java: (changed desktop window width and height to 1000)
public class DesktopLauncher {
public static void main (String[] arg) {
LwjglApplicationConfiguration config = new LwjglApplicationConfiguration();
config.width = 1000;
config.height = 1000;
new LwjglApplication(new TEST(), config);
}
}
TEST.java:
public class TEST extends Game {
Stage stage;
MyPath path;
#Override
public void create () {
stage = new Stage();
stage.setViewport(new ScreenViewport(stage.getViewport().getCamera()));
Gdx.input.setInputProcessor(stage);
path = new MyPath(1000, 1000);
stage.addActor(path);
}
#Override
public void render () {
Gdx.gl.glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
stage.act(Gdx.graphics.getDeltaTime());
stage.draw();
}
#Override
public void dispose(){
stage.dispose();
super.dispose();
}
}
MyPath.java:
public class MyPath extends WidgetGroup {
Image start, end, path, bar1, horizontal;
float time, percent, dVal, pathLength, dMax=1000, cycle=6, maxPercent, deltaCycle;
CatmullRomSpline<Vector2> catmull;
Vector2 result = new Vector2();
Vector2 previousResult = new Vector2(50,150);
Vector2 derivative = new Vector2();
Vector2 previousDerivative = new Vector2();
Vector2[] points = {
new Vector2(50,150), new Vector2(50,150),
new Vector2(400,800), new Vector2(600,150), new Vector2(700,400),
new Vector2(860,150), new Vector2(860,150)
};
boolean print = true;
public MyPath(int width, int height){
this.setSize(width, height);
catmull = new CatmullRomSpline<Vector2>(points, false);
createPath();
createBar();
pathLength = catmull.approxLength(1000);
}
#Override
public void act(float delta){
// [1] VARIABLE SPEED
//time += delta;
//percent = (time / cycle) % 1;
// [2] CONSTANT SPEED FAIL!
//catmull.derivativeAt(previousDerivative, percent);
//time += delta;
//percent = ((time / cycle) / previousDerivative.len() ) % 1;
catmull.valueAt(result, percent);
path.setPosition(result.x, this.getHeight() - result.y);
updateSpeedVisuals();
debugPrint();
previousResult.set(result);
}
private void createPath(){
start = new Image(new Texture("dot.png"));
start.setColor(Color.GRAY);
start.setPosition(50, this.getHeight() - 150);
this.addActor(start);
end = new Image(new Texture("dot.png"));
end.setColor(Color.GRAY);
end.setPosition(860, this.getHeight() - 150);
this.addActor(end);
path = new Image(new Texture("dot.png"));
path.setColor(Color.WHITE);
this.addActor(path);
}
private void createBar(){
Texture texture = new Texture("ninepatch.png");
int crop = (int)(texture.getWidth()/2)-1;
NinePatch patch9 = new NinePatch(texture, crop, crop, crop, crop);
bar1 = new Image(patch9);
bar1.setColor(Color.GRAY);
bar1.setPosition(5, this.getHeight()-900);
this.addActor(bar1);
}
private void updateSpeedVisuals(){
catmull.derivativeAt(derivative, percent);
dVal = derivative.len() / dMax;
path.setColor(1f, 1f-dVal, 1f-dVal, 1f);
bar1.setWidth(derivative.len());
bar1.setColor(1f, 1f-dVal, 1f-dVal, 1f);
}
private void debugPrint(){
maxPercent = (percent > maxPercent) ? percent : maxPercent;
if (maxPercent > percent){
print = false;
}
if (print){
String debugPrint = "";
debugPrint = debugPrint + "pathLength=" + pathLength + "\t";
debugPrint = debugPrint + "derivative=" + derivative.len() + "\t";
System.out.println(debugPrint);
}
}
}
Since the derivative is the rate of change of the spline position it is indeed the 'speed', and when the spline is bending away from the underlying data points it has to 'speed up' to make the calculated spline reach the next data point in time, you must divide out this speed to perceive a visual constant speed.
You aren't getting a constant speed because you are still incrementing your time variable by delta instead of delta divided by the rate of change (derivative). You should be adding a variable amount to the percent variable each frame, instead you were modifying everything by the derivative of a single point along the Catmull-Rom spline.
Instead of:
catmull.derivativeAt(previousDerivative, percent);
time += delta;
percent = ((time / cycle) / previousDerivative.len() ) % 1;
You should:
catmull.derivativeAt(previousDerivative, percent);
percent += derivativeAverage / cycle * delta / previousDerivative.len();
percent %= 1;
you should use the average derivative divided by cycle now since you can't use cycle alone as a percent per second variable anymore.
Iterating over the spline to find the average value of the derivativeAverage:
int samples = 100; //the higher the more accurate, however slower
float derivativeAverage = 0;
Vector2 out = new Vector2();
for (float i=0;i<1;i+=1f/samples) {
catmull.derivativeAt(out, i);
derivativeAverage += out.len();
}
derivativeAverage /= samples;

Android How to draw a polyline with missing coordinates?

I have a json file with coordinates and putting them in LatLng array. Some coordinates are missing because device is only sending coordinates if it's turned on. So when you turn off device and put it some where else polyline goes through whole map to another point and connect it. I need to somehow split polyline if the distance between two points gets too big. Drawing many polylines for each connecting point is not an option, because there can be 500000 points.
Can not post images here because I don't have reputation.
http://i.imgur.com/YtslNwX.png
This is how I put coordinates inside polyline options.
mapData.routeOptions here is equal to polyline options.
List<LatLng> route = new ArrayList<LatLng>();
route.addAll(positions);
double totalDistance=0;
if (route.size() > 1) {
mapData.color = suppliedMapData.color;
int color = mapData.color;
int colorAlpha = Utils.adjustAlpha(color, ROUTE_TRANSPERENCY_PERCENTAGE);
LatLngBounds.Builder boundsBuilder = LatLngBounds.builder();
for (LatLng position : route) {
boundsBuilder.include(position);
}
mapData.routeBounds = boundsBuilder.build();
mapData.routeOptions = new PolylineOptions().addAll(route).color(colorAlpha);
This is how I draw polyline.
int size = sArray.size();
for (int i = 0; i < size; i++) {
MapData mapData = sArray.valueAt(i);
if (mapData.routeOptions != null) {
mapData.routeOptions.width(7);
mPolylines.add(mMap.addPolyline(mapData.routeOptions));
hasPosData = true;
}}
Polylines are added to mPolylines array. because there are more devices than one and all off them need to be drawn.

Perspective Projection: determine the 2D screen coordinates (x,y) of points in 3D space (x,y,z)

I wish to determine the 2D screen coordinates (x,y) of points in 3D space (x,y,z).
The points I wish to project are real-world points represented by GPS coordinates and elevation above sea level.
For example:
Point (Lat:49.291882, Long:-123.131676, Height: 14m)
The camera position and height can also be determined as a x,y,z point. I also have the heading of the camera (compass degrees), its degree of tilt (above/below horizon) and the roll (around the z axis).
I have no experience of 3D programming, therefore, I have read around the subject of perspective projection and learnt that it requires knowledge of matrices, transformations etc - all of which completely confuse me at present.
I have been told that OpenGL may be of use to construct a 3D model of the real-world points, set up the camera orientation and retrieve the 2D coordinates of the 3D points.
However, I am not sure if using OpenGL is the best solution to this problem and even if it is I have no idea how to create models, set up cameras etc
Could someone suggest the best method to solve my problem? If OpenGL is a feasible solution i'd have to use OpenGL ES if that makes any difference. Oh and whatever solution I choose it must execute quickly.
Here's a very general answer. Say the camera's at (Xc, Yc, Zc) and the point you want to project is P = (X, Y, Z). The distance from the camera to the 2D plane onto which you are projecting is F (so the equation of the plane is Z-Zc=F). The 2D coordinates of P projected onto the plane are (X', Y').
Then, very simply:
X' = ((X - Xc) * (F/Z)) + Xc
Y' = ((Y - Yc) * (F/Z)) + Yc
If your camera is the origin, then this simplifies to:
X' = X * (F/Z)
Y' = Y * (F/Z)
You do indeed need a perspective projection and matrix operations greatly simplify doing so. I assume you are already aware that your spherical coordinates must be transformed to Cartesian coordinates for these calculations.
Using OpenGL would likely save you a lot of work over rolling your own software rasterizer. So, I would advise trying it first. You can prototype your system on a PC since OpenGL ES is not too different as long as you keep it simple.
If just need to compute coordinates of some points, you should only need some algebra skills, not 3D programming with openGL.
Moreover openGL does not deal with Geographic coordinates
First get some doc about WGS84 and geodesic coordinates, you have first to convert your GPS data into a cartesian frame ( for instance the earth centric cartesian frame in which is defined the WGS84 ellipsoid ).
Then the computations with matrix can take place.
The chain of transformations is roughly :
WGS84
earth centric coordinates
some local frame
camera frame
2D projection
For the first conversion see this
The last involves a projection matrix
The others are only coordinates rotations and translation.
The "some local frame" is the local cartesian frame with origin as your camera location
tangent to the ellipsoid.
I'd recommend "Mathematics for 3D Game Programming and Computer Graphics" by Eric Lengyel. It covers matrices, transformations, the view frustum, perspective projection and more.
There is also a good chapter in The OpenGL Programming Guide (red book) on viewing transformations and setting up a camera (including how to use gluLookAt).
If you aren't interested in displaying the 3D scene and are limited to using OpenGL ES then it may be better to just write your own code to do the mapping from 3D to 2D window coords. As a starting point you could download Mesa 3D, an open source implementation of OpenGL, to see how they implement gluPerspective (to set a projection matrix), gluLookAt (to set a camera transformation) and gluProject (to project a 3D point to 2D window coords).
return [((fol/v[2])*v[0]+x),((fol/v[2])*v[1]+y)];
Point at [0,0,1] will be x=0 and y=0, unless you add center screen xy - it's not camera xy. fol is focal length, derived from fov angle and screen width - how high is the triangle (tangent). This method will not match three.js perspective matrix, which is why am I looking for that.
I should not be looking for it. I matched xy on openGL, perfectly like super glue! But I cannot get it to work right in java. THAT Perfect match follows.
var pmat = [0,0,0,0,0,0,0,0,0,0,
(farclip + nearclip) / (nearclip - farclip),-1,0,0,
2*farclip*nearclip / (nearclip - farclip),0 ];
void setpmat() {
double fl; // = tan(dtor(90-fovx/aspect/2)); /// UNIT focal length
fl = 1/tan(dtor(fov/Aspect/2)); /// same number
pmat[0] = fl/Aspect;
pmat[5] = fl;
}
void fovmat(double v[],double p[]) {
int cx = (int)(_Width/2),cy = (int)(_Height/2);
double pnt2[4], pnt[4] = { 0,0,0,1 } ;
COPYVECTOR(pnt,p);NORMALIZE(pnt);
popmatrix4(pnt2,pmat,pnt);
COPYVECTOR(v,pnt2);
v[0] *= -cx; v[1] *= -cy;
v[0] += cx; v[1] += cy;
} // world to screen matrix
void w2sm(int xy[],double p[]) {
double v[3]; fovmat(v,p);
xy[0] = (int)v[0];
xy[1] = (int)v[1];
}
I have one more way to match three.js xy, til I get the matrix working, just one condition. must run at Aspect of 2
function w2s(fol,v,x,y) {
var a = width / height;
var b = height/width ;
/// b = .5 // a = 2
var f = 1/Math.tan(dtor(_fov/a)) * x * b;
return [intr((f/v[2])*v[0]+x),intr((f/v[2])*v[1]+y)];
}
Use it with the inverted camera matrix, you will need invert_matrix().
v = orbital(i);
v = subv(v,campos);
v3 = popmatrix(wmatrix,v); //inverted mat
if (v3[2] > 0) {
xy = w2s(flen,v3,cx,cy);
Finally here it is, (everyone ought to know by now), the no-matrix match, any aspect.
function angle2fol(deg,centerx) {
var b = width / height;
var a = dtor(90 - (clamp(deg,0.0001,174.0) / 2));
return asa_sin(PI_5,centerx,a) / b;
}
function asa_sin(a,s,b) {
return Math.sin(b) * (s / Math.sin(PI-(a+b)));
} // ASA solve opposing side of angle2 (b)
function w2s(fol,v,x,y) {
return [intr((fol/v[2])*v[0]+x),intr((fol/v[2])*v[1]+y)];
}
Updated the image for the proof. Input _fov gets you 1.5 that, "approximately." To see the FOV readout correctly, redo the triangle with the new focal length.
function afov(deg,centerx) {
var f = angle2fol(deg,centerx);
return rtod(2 * sss_cos(f,centerx,sas_cos(f,PI_5,centerx)));
}
function sas_cos(s,a,ss) {
return Math.sqrt((Math.pow(s,2)+Math.pow(ss,2))-(2*s*ss*Math.cos(a)));
} // Side Angle Side - solve length of missing side
function sss_cos(a,b,c) {
with (Math) {
return acos((pow(a,2)+pow(c,2)-pow(b,2))/(2*a*c));
}
} // SSS solve angle opposite side2 (b)
Star library confirmed the perspective, then possible to measure the VIEW! http://innerbeing.epizy.com/cwebgl/perspective.jpg
I can explain the 90 deg correction to moon's north pole in one word precession. So what is the current up vector. pnt? radec?
function ininorths() {
if (0) {
var c = ctime;
var v = LunarPos(jdm(c));
c += secday();
var vv = LunarPos(jdm(c));
vv = crossprod(v,vv);
v = eyeradec(vv);
echo(v,vv);
v = [266.86-90,65.64]; //old
}
var v = [282.6425,65.8873]; /// new.
// ...
}
I have yet to explain the TWO sets of vectors: Three.milkyway.matrix and the 3D to 2D drawing. They ARE:
function drawmilkyway() {
var v2 = radec2pos(dtor(192.8595), dtor(27.1283),75000000);
// gcenter 266.4168 -29.0078
var v3 = radec2pos(dtor(266.4168), dtor(-29.0078),75000000);
// ...
}
function initmwmat() {
var r,u,e;
e = radec2pos(dtor(156.35), dtor(12.7),1);
u = radec2pos(dtor(60.1533), dtor(25.5935),1);
r = normaliz(crossprod(u,e));
u = normaliz(crossprod(e,r));
e = normaliz(crossprod(r,u));
var m = MilkyWayMatrix;
m[0]=r[0];m[1]=r[1];m[2]=r[2];m[3]=0.0;
m[4]=u[0];m[5]=u[1];m[6]=u[2];m[7]=0.0;
m[8]=e[0];m[9]=e[1];m[10]=e[2];m[11]=0.0;
m[12]=0.0;m[13]=0.0;m[14]=0.0;m[15]=1.0;
}
/// draw vectors and matrix were the same in C !
void initmwmat(double m[16]) {
double r[3], u[3], e[3];
radec2pos(e,dtor(192.8595), dtor(27.1283),1); //up
radec2pos(u,dtor(266.4051), dtor(-28.9362),-1); //eye
}

Categories