package org.osmdroid.views.overlay; import java.util.ArrayList; import java.util.List; import org.osmdroid.api.IGeoPoint; import org.osmdroid.util.BoundingBox; import org.osmdroid.util.BoundingBoxE6; import org.osmdroid.util.GeoPoint; import org.osmdroid.views.MapView; import org.osmdroid.views.Projection; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Path; import android.graphics.Point; import android.graphics.RectF; import android.graphics.Region; import android.view.MotionEvent; /** * A polygon on the earth's surface that can have a * popup-{@link org.osmdroid.views.overlay.infowindow.InfoWindow} (a bubble). * * Mimics the Polygon class from Google Maps Android API v2 as much as possible. Main differences:<br> * - Doesn't support: Z-Index, Geodesic mode<br> * - Supports InfoWindow. * * <img alt="Class diagram around Marker class" width="686" height="413" src='src='./doc-files/marker-infowindow-classes.png' /> * * @author Viesturs Zarins, Martin Pearman for efficient PathOverlay.draw method * @author M.Kergall: transformation from PathOverlay to Polygon * @see <a href="http://developer.android.com/reference/com/google/android/gms/maps/model/Polygon.html">Google Maps Polygon</a> */ public class Polygon extends OverlayWithIW { /** inner class holding one ring: the polygon outline, or a hole inside the polygon */ class LinearRing { /** original GeoPoints */ int mOriginalPoints[][]; //as an array, to reduce object creation /** Stores points, converted to the map projection. */ ArrayList<Point> mConvertedPoints; /** is precomputation of points done or not */ boolean mPrecomputed; LinearRing(){ mOriginalPoints = new int[0][2]; mConvertedPoints = new ArrayList<Point>(0); mPrecomputed = false; } ArrayList<GeoPoint> getPoints(){ int size = mOriginalPoints.length; ArrayList<GeoPoint> result = new ArrayList<GeoPoint>(size); for (int i=0; i<size; i++){ GeoPoint gp = new GeoPoint(mOriginalPoints[i][0], mOriginalPoints[i][1]); result.add(gp); } return result; } void setPoints(final List<GeoPoint> points) { int size = points.size(); mOriginalPoints = new int[size][2]; mConvertedPoints = new ArrayList<Point>(size); int i=0; for (GeoPoint p:points){ mOriginalPoints[i][0] = p.getLatitudeE6(); mOriginalPoints[i][1] = p.getLongitudeE6(); mConvertedPoints.add(new Point(p.getLatitudeE6(), p.getLongitudeE6())); i++; } mPrecomputed = false; } /** * Note - highly optimized to handle long paths, proceed with care. * Should be fine up to 10K points. */ protected void buildPathPortion(Projection pj){ final int size = mConvertedPoints.size(); if (size < 2) // nothing to paint return; // precompute new points to the intermediate projection. if (!mPrecomputed){ for (int i=0; i<size; i++) { final Point pt = mConvertedPoints.get(i); pj.toProjectedPixels(pt.x, pt.y, pt); } mPrecomputed = true; } Point projectedPoint0 = mConvertedPoints.get(0); // points from the points list Point projectedPoint1; Point screenPoint0 = pj.toPixelsFromProjected(projectedPoint0, mTempPoint1); // points on screen Point screenPoint1; mPath.moveTo(screenPoint0.x, screenPoint0.y); for (int i=0; i<size; i++) { // compute next points projectedPoint1 = mConvertedPoints.get(i); screenPoint1 = pj.toPixelsFromProjected(projectedPoint1, mTempPoint2); if (Math.abs(screenPoint1.x - screenPoint0.x) + Math.abs(screenPoint1.y - screenPoint0.y) <= 1) { // skip this point, too close to previous point continue; } mPath.lineTo(screenPoint1.x, screenPoint1.y); // update starting point to next position projectedPoint0 = projectedPoint1; screenPoint0.x = screenPoint1.x; screenPoint0.y = screenPoint1.y; } mPath.close(); } } private LinearRing mOutline; private ArrayList<LinearRing> mHoles; /** Paint settings. */ protected Paint mFillPaint; protected Paint mOutlinePaint; private final Path mPath = new Path(); //Path drawn is kept for click detection private final Point mTempPoint1 = new Point(); private final Point mTempPoint2 = new Point(); // =========================================================== // Constructors // =========================================================== /** Use {@link #Polygon()} instead */ @Deprecated public Polygon(final Context ctx) { this(); } public Polygon() { super(); mFillPaint = new Paint(); mFillPaint.setColor(Color.TRANSPARENT); mFillPaint.setStyle(Paint.Style.FILL); mOutlinePaint = new Paint(); mOutlinePaint.setColor(Color.BLACK); mOutlinePaint.setStrokeWidth(10.0f); mOutlinePaint.setStyle(Paint.Style.STROKE); mOutlinePaint.setAntiAlias(true); mOutline = new LinearRing(); mHoles = new ArrayList<LinearRing>(0); mPath.setFillType(Path.FillType.EVEN_ODD); //for correct support of holes } // =========================================================== // Getter & Setter // =========================================================== public int getFillColor() { return mFillPaint.getColor(); } public int getStrokeColor() { return mOutlinePaint.getColor(); } public float getStrokeWidth() { return mOutlinePaint.getStrokeWidth(); } /** @return the Paint used for the outline. This allows to set advanced Paint settings. */ public Paint getOutlinePaint(){ return mOutlinePaint; } /** * @return a copy of the list of polygon's vertices. */ public List<GeoPoint> getPoints(){ return mOutline.getPoints(); } public boolean isVisible(){ return isEnabled(); } public void setFillColor(final int fillColor) { mFillPaint.setColor(fillColor); } public void setStrokeColor(final int color) { mOutlinePaint.setColor(color); } public void setStrokeWidth(final float width) { mOutlinePaint.setStrokeWidth(width); } public void setVisible(boolean visible){ setEnabled(visible); } /** * This method will take a copy of the points. */ public void setPoints(final List<GeoPoint> points) { mOutline.setPoints(points); } public void setHoles(List<? extends List<GeoPoint>> holes){ mHoles = new ArrayList<LinearRing>(holes.size()); for (List<GeoPoint> sourceHole:holes){ LinearRing newHole = new LinearRing(); newHole.setPoints(sourceHole); mHoles.add(newHole); } } public List<ArrayList<GeoPoint>> getHoles(){ ArrayList<ArrayList<GeoPoint>> result = new ArrayList<ArrayList<GeoPoint>>(mHoles.size()); for (LinearRing hole:mHoles){ result.add(hole.getPoints()); } return result; } /** Build a list of GeoPoint as a circle. * @param center center of the circle * @param radiusInMeters * @return the list of GeoPoint */ public static ArrayList<GeoPoint> pointsAsCircle(GeoPoint center, double radiusInMeters){ ArrayList<GeoPoint> circlePoints = new ArrayList<GeoPoint>(360/6); for (int f = 0; f < 360; f += 6){ GeoPoint onCircle = center.destinationPoint(radiusInMeters, f); circlePoints.add(onCircle); } return circlePoints; } /** Build a list of GeoPoint as a rectangle. * @param rectangle defined as a BoundingBox * @return the list of 4 GeoPoint */ @Deprecated public static ArrayList<IGeoPoint> pointsAsRect(BoundingBoxE6 rectangle){ ArrayList<IGeoPoint> points = new ArrayList<IGeoPoint>(4); points.add(new GeoPoint(rectangle.getLatNorthE6(), rectangle.getLonWestE6())); points.add(new GeoPoint(rectangle.getLatNorthE6(), rectangle.getLonEastE6())); points.add(new GeoPoint(rectangle.getLatSouthE6(), rectangle.getLonEastE6())); points.add(new GeoPoint(rectangle.getLatSouthE6(), rectangle.getLonWestE6())); return points; } /** Build a list of GeoPoint as a rectangle. * @param rectangle defined as a BoundingBox * @return the list of 4 GeoPoint */ public static ArrayList<IGeoPoint> pointsAsRect(BoundingBox rectangle){ ArrayList<IGeoPoint> points = new ArrayList<IGeoPoint>(4); points.add(new GeoPoint(rectangle.getLatNorth(), rectangle.getLonWest())); points.add(new GeoPoint(rectangle.getLatNorth(), rectangle.getLonEast())); points.add(new GeoPoint(rectangle.getLatSouth(), rectangle.getLonEast())); points.add(new GeoPoint(rectangle.getLatSouth(), rectangle.getLonWest())); return points; } /** Build a list of GeoPoint as a rectangle. * @param center of the rectangle * @param lengthInMeters on longitude * @param widthInMeters on latitude * @return the list of 4 GeoPoint */ public static ArrayList<IGeoPoint> pointsAsRect(GeoPoint center, double lengthInMeters, double widthInMeters){ ArrayList<IGeoPoint> points = new ArrayList<IGeoPoint>(4); GeoPoint east = center.destinationPoint(lengthInMeters*0.5, 90.0f); GeoPoint south = center.destinationPoint(widthInMeters*0.5, 180.0f); double westLon = center.getLongitude()*2 - east.getLongitude(); double northLat = center.getLatitude()*2 - south.getLatitude(); points.add(new GeoPoint(south.getLatitude(), east.getLongitude())); points.add(new GeoPoint(south.getLatitude(), westLon)); points.add(new GeoPoint(northLat, westLon)); points.add(new GeoPoint(northLat, east.getLongitude())); return points; } @Override public void draw(Canvas canvas, MapView mapView, boolean shadow) { if (shadow) { return; } final Projection pj = mapView.getProjection(); mPath.rewind(); mOutline.buildPathPortion(pj); for (LinearRing hole:mHoles){ hole.buildPathPortion(pj); } canvas.drawPath(mPath, mFillPaint); canvas.drawPath(mPath, mOutlinePaint); } /** Important note: this function returns correct results only if the Polygon has been drawn before, * and if the MapView positioning has not changed. * @param event * @return true if the Polygon contains the event position. */ public boolean contains(MotionEvent event){ if (mPath.isEmpty()) return false; RectF bounds = new RectF(); //bounds of the Path mPath.computeBounds(bounds, true); Region region = new Region(); //Path has been computed in #draw (we assume that if it can be clicked, it has been drawn before). region.setPath(mPath, new Region((int)bounds.left, (int)bounds.top, (int) (bounds.right), (int) (bounds.bottom))); return region.contains((int)event.getX(), (int)event.getY()); } @Override public boolean onSingleTapConfirmed(final MotionEvent event, final MapView mapView){ if (mInfoWindow == null) //no support for tap: return false; boolean tapped = contains(event); if (tapped){ Projection pj = mapView.getProjection(); GeoPoint position = (GeoPoint)pj.fromPixels((int)event.getX(), (int)event.getY()); mInfoWindow.open(this, position, 0, 0); } return tapped; } @Override public void onDetach(MapView mapView) { mOutline=null; mHoles.clear(); onDestroy(); } }