Draw constant-size circles on the screen in the Google Maps API v2

I am working on an Android application using the Gooogle Maps v2 API. I have markers on my map and I would like to surround one of them. I did this easily using the Circle and Circle Options classes. But I would also like my circle to keep the same size on the screen when zoomed in or out, just like markers. This means that the circle must have a constant radius in pixels. Unfortunately, we cannot set the radius in pixels in API v2.

I tried several solutions, but I am not satisfied.

In the first, I just multiply or divide the radius:

@Override public void onCameraChange(CameraPosition position) { if(previousZoom > position.zoom) { mSelectionCircle.setRadius(Math.abs(position.zoom - previousZoom)*2*mSelectionCircle.getRadius()); } else if(previousZoom < position.zoom) { mSelectionCircle.setRadius(Math.abs(position.zoom - previousZoom)*mSelectionCircle.getRadius()/2); } previousZoom = position.zoom; } 

This seemed to work at first, but leads to incorrect results when scaling quickly or scaling with your fingers. In addition, scaling is clearly visible on the screen.

My second solution uses pixel meter conversions. The idea is to recalculate the radius in meters when scaling / zooming, so the circle has a constant size on the screen. To do this, I get the current position of the circle on the screen:

  Point p1 = mMap.getProjection().toScreenLocation(mSelectionCircle.getCenter()); 

Then I create another point located on the edge of the circle:

  Point p2 = new Point(p1.x + radiusInPixels, p1.y); 

Where

  int radiusInPixels = 40; 

After that, I use a function that returns the distance between these two points in meters.

 private double convertPixelsToMeters(Point point1, Point point2) { double angle = Math.acos(Math.sin(point1.x) * Math.sin(point2.x) + Math.cos(point1.x) * Math.cos(point2.x) * Math.cos(point1.y- point2.y)); return angle * Math.PI * 6378100.0; // distance in meters } 

6378100 - the average radius of the Earth. Finally, I set a new circle radius:

  mSelectionCircle.setRadius(convertPixelsToMeters(p1, p2)); 

It should work theoretically, but I get ridiculous values ​​for the radius (10 ^ 7 m!). Can the conversion function be wrong?

So, is there an easier way to do this, or if not, can you help me understand why my second soloeton is not working?

Thanks!

+4
source share
4 answers

Use the Marker icon instead. You can create Bitmap and Canvas , draw on the latter and use it as a Marker icon:

 new MarkerOptions().icon(BitmapDescriptorFactory.fromBitmap(bitmap))... 
+2
source

EDIT:

My previous answer is no longer valid. As Jean-Philippe Jaudouin recalled, you can simply do this with the help of markers and set their binding to 0.5 / 0.5. This is a cleaner solution.

Paste the suggested code snippet for reference:

 marker = mMap.addMarker(new MarkerOptions().position(latlng).anchor(0.5f, 0.5f)); 

Old answer:

I faced the same problem and could not find a solution, so I did it myself, I will publish in the hope that it will be useful for some other people.

The “marker” approach did not work for me because I wanted the circles to be centered on a certain wider / width, and you cannot do this with the marker: if you set the circle icon for the marker, the edge of the circle will touch the latitude. / lng, but the circle will not be centered on the latitude / lng.

I created a function to calculate the size of the circle in meters, taking into account the latitude and zoom level of the camera, then added a camera listener to the map to update the size of the circle each time the camera changes the zoom level. The result is a circle whose size does not change (at least with the naked eye).

Here is my code:

 public static double calculateCircleRadiusMeterForMapCircle(final int _targetRadiusDip, final double _circleCenterLatitude, final float _currentMapZoom) { //That base value seems to work for computing the meter length of a DIP final double arbitraryValueForDip = 156000D; final double oneDipDistance = Math.abs(Math.cos(Math.toRadians(_circleCenterLatitude))) * arbitraryValueForDip / Math.pow(2, _currentMapZoom); return oneDipDistance * (double) _targetRadiusDip; } public void addCircleWithConstantSize(){ final GoogleMap googleMap = ...//Retrieve your GoogleMap object here //Creating a circle for the example final CircleOptions co = new CircleOptions(); co.center(new LatLng(0,0)); co.fillColor(Color.BLUE); final Circle circle = googleMap.addCircle(co); //Setting a listener on the map camera to monitor when the camera changes googleMap.setOnCameraMoveListener(new GoogleMap.OnCameraMoveListener() { @Override public void onCameraMove() { //Use the function to calculate the radius final double radius = calculateCircleRadiusMeterForMapCircle(12, co.getCenter().latitude, googleMap.getCameraPosition().zoom); //Apply the radius to the circle circle.setRadius(radius); } }); } 
+2
source

You probably don’t really need the exact pixel size, it just looks the same for all zoom and rotation levels of the device.

Here is a pretty easy way to do this. Draw (and redraw if you change the scale), a circle whose radius is a certain percentage of the diagonal of the visible screen.

The Google Maps API v2 has a getProjection() function that will return the lat / long coordinates from the four corners of the visible screen. Then, using the super convenient Location class, you can calculate the distance from the diagonal of what is visible on the screen and use the percentage of this diagonal as the radius of your circle. Your circle will be the same size, no matter what the zoom scale or how the device rotates.

Here is the code in Java:

 public Circle drawMapCircle(GoogleMap googleMap,LatLng latLng,Circle currentCircle) { // get 2 of the visible diagonal corners of the map (could also use farRight and nearLeft) LatLng topLeft = googleMap.getProjection().getVisibleRegion().farLeft; LatLng bottomRight = googleMap.getProjection().getVisibleRegion().nearRight; // use the Location class to calculate the distance between the 2 diagonal map points float results[] = new float[4]; // probably only need 3 Location.distanceBetween(topLeft.latitude,topLeft.longitude,bottomRight.latitude,bottomRight.longitude,results); float diagonal = results[0]; // use 5% of the diagonal for the radius (gives a 10% circle diameter) float radius = diagonal / 20; Circle circle = null; if (currentCircle != null) { // change the radius if the circle already exists (result of a zoom change) circle = currentCircle; circle.setRadius(radius); } else { // draw a new circle circle = googleMap.addCircle(new CircleOptions() .center(latLng) .radius(radius) .strokeColor(Color.BLACK) .strokeWidth(2) .fillColor(Color.LTGRAY)); } return circle; } 
+2
source

As Maciej Gursky suggested, this is the right and simple way; but if you have a lot of markers on a Google map, say 5k markers, this will drastically reduce performance. Some suggestions to show this:

1) Let the Google Token Clustering Utility search the android map API.

2) However, marker clustering may not fully meet your goals. This way you can configure it yourself. Here is a topic discussing this issue: https://github.com/googlemaps/android-maps-utils/issues/29

Sorry, I didn’t try, because I found that using Polyline serves my purpose (path mapping).

Hope this helps, Mttdat.

0
source

Source: https://habr.com/ru/post/979896/


All Articles