/* * Geopaparazzi - Digital field mapping on Android based devices * Copyright (C) 2016 HydroloGIS (www.hydrologis.com) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package eu.geopaparazzi.core.mapview.overlays; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Paint.Align; import android.graphics.Path; import android.graphics.Point; import android.graphics.PointF; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.preference.PreferenceManager; import com.vividsolutions.jts.android.PointTransformation; import com.vividsolutions.jts.android.ShapeWriter; import com.vividsolutions.jts.android.geom.DrawableShape; import com.vividsolutions.jts.android.geom.PathShape; import com.vividsolutions.jts.geom.Coordinate; import com.vividsolutions.jts.geom.Envelope; import com.vividsolutions.jts.geom.Geometry; import org.mapsforge.android.maps.MapView; import org.mapsforge.android.maps.Projection; import org.mapsforge.android.maps.overlay.ItemizedOverlay; import org.mapsforge.android.maps.overlay.Overlay; import org.mapsforge.android.maps.overlay.OverlayItem; import org.mapsforge.android.maps.overlay.OverlayWay; import org.mapsforge.core.model.GeoPoint; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.ConcurrentModificationException; import java.util.HashMap; import java.util.List; import java.util.Map; import eu.geopaparazzi.library.core.maps.SpatialiteMap; import eu.geopaparazzi.library.core.maps.SpatialiteMapOrderComparator; import eu.geopaparazzi.library.database.GPLog; import eu.geopaparazzi.library.forms.FormActivity; import eu.geopaparazzi.library.forms.FormInfoHolder; import eu.geopaparazzi.library.gps.GpsLoggingStatus; import eu.geopaparazzi.library.gps.GpsService; import eu.geopaparazzi.library.gps.GpsServiceStatus; import eu.geopaparazzi.library.images.ImageUtilities; import eu.geopaparazzi.library.util.AppsUtilities; import eu.geopaparazzi.library.util.Compat; import eu.geopaparazzi.library.util.GPDialogs; import eu.geopaparazzi.library.util.LibraryConstants; import eu.geopaparazzi.spatialite.database.spatial.SpatialiteSourcesManager; import eu.geopaparazzi.spatialite.database.spatial.core.databasehandlers.SpatialiteDatabaseHandler; import eu.geopaparazzi.spatialite.database.spatial.core.enums.GeometryType; import eu.geopaparazzi.spatialite.database.spatial.core.geometry.GeometryIterator; import eu.geopaparazzi.spatialite.database.spatial.core.tables.SpatialVectorTable; import eu.geopaparazzi.library.style.Style; import eu.geopaparazzi.core.GeopaparazziApplication; import eu.geopaparazzi.core.R; import eu.geopaparazzi.core.database.DaoImages; import eu.geopaparazzi.core.database.DaoNotes; import eu.geopaparazzi.core.database.objects.Note; import eu.geopaparazzi.core.database.objects.NoteOverlayItem; import eu.geopaparazzi.core.mapview.MapviewActivity; import eu.geopaparazzi.core.utilities.Constants; import jsqlite.Exception; /** * GeopaparazziOverlay is an abstract base class to display {@link OverlayWay OverlayWays}. The class defines some methods to * access the backing data structure of deriving subclasses. * <p/> * The overlay may be used to show additional ways such as calculated routes. Closed polygons, for example buildings or * areas, are also supported. A way node sequence is considered as a closed polygon if the first and the last way node * are equal. */ public abstract class GeopaparazziOverlay extends Overlay { private static final String THREAD_NAME = "GeopaparazziOverlay"; //$NON-NLS-1$ /** * Sets the bounds of the given drawable so that (0,0) is the center of the bottom row. * * @param balloon the drawable whose bounds should be set. * @return the given drawable with set bounds. */ public static Drawable boundCenter(Drawable balloon) { balloon.setBounds(balloon.getIntrinsicWidth() / -2, balloon.getIntrinsicHeight() / -2, balloon.getIntrinsicWidth() / 2, balloon.getIntrinsicHeight() / 2); return balloon; } /* * way stuff */ private Paint wayStartPaintFill; private Paint defaultWayPaintFill; private Paint defaultWayPaintOutline; private Path wayPath; private Point itemPosition; private final List<Integer> visibleItems = new ArrayList<>(); private final List<Integer> visibleItemsRedraw = new ArrayList<>(); /* * gps stuff */ private final Point circlePosition; private final Path path; private final GpsData overlayGps; private Drawable gpsMarker; private Path gpsPath; private final OverlayWay gpslogOverlay; private Paint gpsTrackPaintYellow; private Paint gpsTrackPaintBlack; private Paint gpsOutline; private Paint gpsFill; private List<GeoPoint> currentGpsLog = new ArrayList<>(); private Paint textPaint; private Paint textHaloPaint; private boolean isNotesTextVisible; private boolean doNotesTextHalo; private GpsServiceStatus gpsServiceStatus = GpsServiceStatus.GPS_OFF; private GpsLoggingStatus gpsLoggingStatus = GpsLoggingStatus.GPS_DATABASELOGGING_OFF; /** * Create a {@link OverlayWay} wrapped type. * * @param context the context to use. */ public GeopaparazziOverlay(Context context) { super(); this.wayPath = new Path(); this.wayPath.setFillType(Path.FillType.EVEN_ODD); this.itemPosition = new Point(); // cross SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(GeopaparazziApplication.getInstance()); boolean isHighDensity = preferences.getBoolean(Constants.PREFS_KEY_RETINA, false); // gps overlayGps = new GpsData(); this.circlePosition = new Point(); this.path = new Path(); this.gpsPath = new Path(); wayStartPaintFill = new Paint(Paint.ANTI_ALIAS_FLAG); wayStartPaintFill.setStyle(Paint.Style.FILL); defaultWayPaintFill = new Paint(Paint.ANTI_ALIAS_FLAG); defaultWayPaintFill.setStyle(Paint.Style.FILL); defaultWayPaintFill.setColor(Color.RED); defaultWayPaintOutline = new Paint(Paint.ANTI_ALIAS_FLAG); defaultWayPaintOutline.setStyle(Paint.Style.STROKE); defaultWayPaintOutline.setColor(Color.BLACK); gpsMarker = Compat.getDrawable(context, R.drawable.ic_my_location_black_24dp); gpsFill = new Paint(Paint.ANTI_ALIAS_FLAG); gpsFill.setStyle(Paint.Style.FILL); gpsFill.setColor(Color.BLUE); gpsFill.setAlpha(48); gpsOutline = new Paint(Paint.ANTI_ALIAS_FLAG); gpsOutline.setStyle(Paint.Style.STROKE); gpsOutline.setColor(Color.BLUE); gpsOutline.setAlpha(128); gpsOutline.setStrokeWidth(2); gpsTrackPaintYellow = new Paint(Paint.ANTI_ALIAS_FLAG); gpsTrackPaintYellow.setStyle(Paint.Style.STROKE); gpsTrackPaintYellow.setColor(Color.YELLOW); gpsTrackPaintBlack = new Paint(Paint.ANTI_ALIAS_FLAG); gpsTrackPaintBlack.setStyle(Paint.Style.STROKE); gpsTrackPaintBlack.setColor(Color.BLACK); if (!isHighDensity) { gpsTrackPaintYellow.setStrokeWidth(3); gpsTrackPaintBlack.setStrokeWidth(5); } else { gpsTrackPaintYellow.setStrokeWidth(8); gpsTrackPaintBlack.setStrokeWidth(12); } isNotesTextVisible = preferences.getBoolean(Constants.PREFS_KEY_NOTES_TEXT_VISIBLE, true); if (isNotesTextVisible) { String notesTextSizeStr = preferences.getString(Constants.PREFS_KEY_NOTES_TEXT_SIZE, LibraryConstants.DEFAULT_NOTES_SIZE + ""); //$NON-NLS-1$ float notesTextSize = LibraryConstants.DEFAULT_NOTES_SIZE; try { notesTextSize = (float) Double.parseDouble(notesTextSizeStr); } catch (NumberFormatException e) { // ignore and use default } doNotesTextHalo = preferences.getBoolean(Constants.PREFS_KEY_NOTES_TEXT_DOHALO, true); textPaint = new Paint(Paint.ANTI_ALIAS_FLAG); textPaint.setStyle(Paint.Style.FILL); textPaint.setColor(Color.BLACK); textPaint.setTextSize(notesTextSize); textHaloPaint = new Paint(Paint.ANTI_ALIAS_FLAG); textHaloPaint.setStyle(Paint.Style.STROKE); textHaloPaint.setStrokeWidth(3); textHaloPaint.setColor(Color.WHITE); textHaloPaint.setTextSize(notesTextSize); } gpsMarker = ItemizedOverlay.boundCenter(gpsMarker); gpslogOverlay = new OverlayWay(null, gpsOutline); currentGpsLog.clear(); } /** * Checks whether an item has been long pressed. */ @Override public boolean onLongPress(GeoPoint geoPoint, MapView mapView) { return checkItemHit(geoPoint, mapView, EventType.LONG_PRESS); // return super.onLongPress(geoPoint, mapView); } /** * Checks whether an item has been tapped. */ @Override public boolean onTap(GeoPoint geoPoint, MapView mapView) { return checkItemHit(geoPoint, mapView, EventType.TAP); // return super.onTap(geoPoint, mapView); } /** * @return the numbers of ways in this overlay. */ public abstract int waySize(); /** * @return the numbers of items in this overlay. */ public abstract int itemSize(); private void drawWayPathOnCanvas(Canvas canvas, Point drawPosition, OverlayWay overlayWay) { // assemble the ways this.wayPath.reset(); for (int i = 0; i < overlayWay.cachedWayPositions.length; ++i) { int x = overlayWay.cachedWayPositions[i][0].x - drawPosition.x; int y = overlayWay.cachedWayPositions[i][0].y - drawPosition.y; this.wayPath.moveTo(x, y); int lastX = 0; int lastY = 0; for (int j = 1; j < overlayWay.cachedWayPositions[i].length; ++j) { lastX = overlayWay.cachedWayPositions[i][j].x - drawPosition.x; lastY = overlayWay.cachedWayPositions[i][j].y - drawPosition.y; this.wayPath.lineTo(lastX, lastY); } // draw start points float size = 2; if (overlayWay.hasPaint) { // use the paints from the current way if (overlayWay.paintOutline != null) { wayStartPaintFill.setColor(overlayWay.paintOutline.getColor()); size = overlayWay.paintOutline.getStrokeWidth(); } else if (overlayWay.paintFill != null) { wayStartPaintFill.setColor(overlayWay.paintFill.getColor()); } } else { // use the default paint objects if (this.defaultWayPaintOutline != null) { wayStartPaintFill.setColor(defaultWayPaintOutline.getColor()); size = defaultWayPaintOutline.getStrokeWidth(); } else if (this.defaultWayPaintFill != null) { wayStartPaintFill.setColor(defaultWayPaintFill.getColor()); } } size = size * 2; canvas.drawCircle(lastX, lastY, size, wayStartPaintFill); canvas.drawRect(x - size, y - size, x + size, y + size, wayStartPaintFill); } // draw them if (overlayWay.hasPaint) { // use the paints from the current way if (overlayWay.paintOutline != null) { canvas.drawPath(this.wayPath, overlayWay.paintOutline); } if (overlayWay.paintFill != null) { canvas.drawPath(this.wayPath, overlayWay.paintFill); } } else { // use the default paint objects if (this.defaultWayPaintOutline != null) { canvas.drawPath(this.wayPath, this.defaultWayPaintOutline); } if (this.defaultWayPaintFill != null) { canvas.drawPath(this.wayPath, this.defaultWayPaintFill); } } } private void assembleGpsWayPath(Point drawPosition, OverlayWay overlayWay) { this.gpsPath.reset(); for (int i = 0; i < overlayWay.cachedWayPositions.length; ++i) { this.gpsPath.moveTo(overlayWay.cachedWayPositions[i][0].x - drawPosition.x, overlayWay.cachedWayPositions[i][0].y - drawPosition.y); for (int j = 1; j < overlayWay.cachedWayPositions[i].length; ++j) { this.gpsPath.lineTo(overlayWay.cachedWayPositions[i][j].x - drawPosition.x, overlayWay.cachedWayPositions[i][j].y - drawPosition.y); } } } private void drawGpsWayPathOnCanvas(Canvas canvas) { canvas.drawPath(this.gpsPath, this.gpsTrackPaintBlack); canvas.drawPath(this.gpsPath, this.gpsTrackPaintYellow); } private void drawGpsOnCanvas(Canvas canvas) { canvas.drawPath(this.path, gpsOutline); canvas.drawPath(this.path, gpsFill); } /** * Set the current gps position. * * @param position the {@link GeoPoint}. * @param accuracy the accuracy. * @param gpsServiceStatus the gps status as defined by {@link GpsService#GPS_SERVICE_STATUS}. * @param gpsLoggingStatus the database logging status as defined by {@link GpsService#GPS_LOGGING_STATUS}. */ @SuppressWarnings("nls") public void setGpsPosition(GeoPoint position, float accuracy, GpsServiceStatus gpsServiceStatus, GpsLoggingStatus gpsLoggingStatus) { this.gpsServiceStatus = gpsServiceStatus; this.gpsLoggingStatus = gpsLoggingStatus; if (gpsLoggingStatus == GpsLoggingStatus.GPS_DATABASELOGGING_ON) { currentGpsLog.add(position); } else { currentGpsLog.clear(); } if (GPLog.LOG_ABSURD && position != null) GPLog.addLogEntry(this, "Set gps data: " + position.getLongitude() + "/" + position.getLatitude() + "/" + accuracy); if (position != null) { overlayGps.setCircleData(position, accuracy); } } /** * Creates a way in this overlay. * * @param index the index of the way. * @return the way. */ protected abstract OverlayWay createWay(int index); @Override protected void drawOverlayBitmap(Canvas canvas, Point drawPosition, Projection projection, byte drawZoomLevel) { /* * first spatialite layers, if any */ drawFromSpatialite(canvas, drawPosition, projection, drawZoomLevel); /* * WAYS */ int numberOfWays = waySize(); for (int wayIndex = 0; wayIndex < numberOfWays; ++wayIndex) { if (stopDrawing()) { // stop working return; } // get the current way OverlayWay overlayWay = createWay(wayIndex); if (overlayWay == null) { continue; } // make sure that the current way has way nodes if (overlayWay.wayNodes == null || overlayWay.wayNodes.length == 0) { continue; } // make sure that the cached way node positions are valid if (drawZoomLevel != overlayWay.cachedZoomLevel) { for (int i = 0; i < overlayWay.cachedWayPositions.length; ++i) { for (int j = 0; j < overlayWay.cachedWayPositions[i].length; ++j) { overlayWay.cachedWayPositions[i][j] = projection.toPoint(overlayWay.wayNodes[i][j], overlayWay.cachedWayPositions[i][j], drawZoomLevel); } } overlayWay.cachedZoomLevel = drawZoomLevel; } drawWayPathOnCanvas(canvas, drawPosition, overlayWay); } /* * ITEMS */ // erase the list of visible items this.visibleItemsRedraw.clear(); int canvasHeight = canvas.getHeight(); int canvasWidth = canvas.getWidth(); int numberOfItems = itemSize(); for (int itemIndex = 0; itemIndex < numberOfItems; ++itemIndex) { if (stopDrawing()) { // stop working return; } // get the current item OverlayItem overlayItem = createItem(itemIndex); if (overlayItem == null) { continue; } // make sure that the current item has a position if (overlayItem.getPoint() == null) { continue; } // make sure that the cached item position is valid if (drawZoomLevel != overlayItem.cachedZoomLevel) { overlayItem.cachedMapPosition = projection.toPoint(overlayItem.getPoint(), overlayItem.cachedMapPosition, drawZoomLevel); overlayItem.cachedZoomLevel = drawZoomLevel; } // calculate the relative item position on the canvas this.itemPosition.x = overlayItem.cachedMapPosition.x - drawPosition.x; this.itemPosition.y = overlayItem.cachedMapPosition.y - drawPosition.y; // get the correct marker for the item Drawable itemMarker = overlayItem.getMarker(); if (itemMarker == null) continue; // get the position of the marker Rect markerBounds = itemMarker.copyBounds(); int intrinsicWidth = itemMarker.getIntrinsicWidth() / 2; int intrinsicHeight = itemMarker.getIntrinsicHeight() / 2; // calculate the bounding box of the marker int left; int right; int top; int itemBottom; if (overlayItem instanceof NoteOverlayItem) { left = this.itemPosition.x - intrinsicWidth; right = this.itemPosition.x + intrinsicWidth; top = this.itemPosition.y - intrinsicHeight; itemBottom = this.itemPosition.y + intrinsicHeight; } else { left = this.itemPosition.x + intrinsicWidth / 2 - intrinsicWidth; right = this.itemPosition.x + intrinsicWidth / 2 + intrinsicWidth; top = this.itemPosition.y + intrinsicHeight / 2 - intrinsicHeight; itemBottom = this.itemPosition.y + intrinsicHeight / 2 + intrinsicHeight; } // check if the bounding box of the marker intersects with the canvas if (right >= 0 && left <= canvasWidth && itemBottom >= 0 && top <= canvasHeight) { // set the position of the marker itemMarker.setBounds(left, top, right, itemBottom); // draw the item marker on the canvas itemMarker.draw(canvas); // restore the position of the marker itemMarker.setBounds(markerBounds); // add the current item index to the list of visible items this.visibleItemsRedraw.add(itemIndex); if (isNotesTextVisible && overlayItem instanceof NoteOverlayItem) { String title = overlayItem.getTitle(); float delta = markerBounds.width() / 4f; float x = right - delta; float y = top + delta; if (doNotesTextHalo) canvas.drawText(title, x, y, textHaloPaint); canvas.drawText(title, x, y, textPaint); } } } // swap the two visible item lists synchronized (visibleItems) { List<Integer> visibleItemsTemp = new ArrayList<>(visibleItems); visibleItems.clear(); visibleItems.addAll(visibleItemsRedraw); visibleItemsRedraw.addAll(visibleItemsTemp); } /* * gps logging track */ if (gpsLoggingStatus == GpsLoggingStatus.GPS_DATABASELOGGING_ON) { // if a track is recorded, show it synchronized (gpslogOverlay) { int size = currentGpsLog.size(); if (size > 1) { GeoPoint[] geoPoints = currentGpsLog.toArray(new GeoPoint[currentGpsLog.size()]); gpslogOverlay.setWayNodes(new GeoPoint[][]{geoPoints}); // make sure that the current way has way nodes if (gpslogOverlay.wayNodes != null && gpslogOverlay.wayNodes.length != 0) { // make sure that the cached way node positions are valid if (drawZoomLevel != gpslogOverlay.cachedZoomLevel) { for (int i = 0; i < gpslogOverlay.cachedWayPositions.length; ++i) { for (int j = 0; j < gpslogOverlay.cachedWayPositions[i].length; ++j) { gpslogOverlay.cachedWayPositions[i][j] = projection.toPoint(gpslogOverlay.wayNodes[i][j], gpslogOverlay.cachedWayPositions[i][j], drawZoomLevel); } } gpslogOverlay.cachedZoomLevel = drawZoomLevel; } assembleGpsWayPath(drawPosition, gpslogOverlay); drawGpsWayPathOnCanvas(canvas); } } } } /* * GPS position */ if (stopDrawing()) { // stop working return; } // get the current circle if (gpsServiceStatus == GpsServiceStatus.GPS_FIX && overlayGps.center != null) { synchronized (overlayGps) { // make sure that the current circle has a center position and a radius if (overlayGps.center != null && overlayGps.radius >= 0) { // make sure that the cached center position is valid if (drawZoomLevel != overlayGps.cachedZoomLevel) { overlayGps.cachedCenterPosition = projection.toPoint(overlayGps.center, overlayGps.cachedCenterPosition, drawZoomLevel); overlayGps.cachedZoomLevel = drawZoomLevel; overlayGps.cachedRadius = projection.metersToPixels(overlayGps.radius, drawZoomLevel); } // calculate the relative circle position on the canvas this.circlePosition.x = overlayGps.cachedCenterPosition.x - drawPosition.x; this.circlePosition.y = overlayGps.cachedCenterPosition.y - drawPosition.y; float circleRadius = overlayGps.cachedRadius; // check if the bounding box of the circle intersects with the canvas if ((this.circlePosition.x + circleRadius) >= 0 && (this.circlePosition.x - circleRadius) <= canvasWidth && (this.circlePosition.y + circleRadius) >= 0 && (this.circlePosition.y - circleRadius) <= canvasHeight) { // assemble the path this.path.reset(); this.path.addCircle(this.circlePosition.x, this.circlePosition.y, circleRadius, Path.Direction.CCW); if (circleRadius > 0) { drawGpsOnCanvas(canvas); } // get the position of the marker Rect markerBounds = gpsMarker.copyBounds(); // calculate the bounding box of the marker int left = this.circlePosition.x + markerBounds.left; int right = this.circlePosition.x + markerBounds.right; int top = this.circlePosition.y + markerBounds.top; int bottom = this.circlePosition.y + markerBounds.bottom; // check if the bounding box of the marker intersects with the canvas if (right >= 0 && left <= canvasWidth && bottom >= 0 && top <= canvasHeight) { // set the position of the marker gpsMarker.setBounds(left, top, right, bottom); // draw the item marker on the canvas gpsMarker.draw(canvas); // restore the position of the marker gpsMarker.setBounds(markerBounds); } } } } } } private void drawFromSpatialite(Canvas canvas, Point drawPosition, Projection projection, byte drawZoomLevel) { /* * draw from spatialite */ double n = 90; double w = -180; double s = -90; double e = 180; try { GeoPoint zeroPoint = projection.fromPixels(0, 0); GeoPoint whPoint = projection.fromPixels(canvas.getWidth(), canvas.getHeight()); n = zeroPoint.getLatitude(); w = zeroPoint.getLongitude(); s = whPoint.getLatitude(); e = whPoint.getLongitude(); } catch (java.lang.Exception e2) { GPLog.error(this, "Problems retrieving viewport bounds", e2); //$NON-NLS-1$ } Envelope canvasEnvelope = new Envelope(w, e, s, n); try { HashMap<SpatialiteMap, SpatialVectorTable> spatialiteMaps2TablesMap = SpatialiteSourcesManager.INSTANCE.getSpatialiteMaps2TablesMap(); HashMap<SpatialiteMap, SpatialiteDatabaseHandler> spatialiteMaps2DbHandlersMap = SpatialiteSourcesManager.INSTANCE.getSpatialiteMaps2DbHandlersMap(); List<SpatialiteMap> spatialiteMaps = SpatialiteSourcesManager.INSTANCE.getSpatialiteMaps(); Collections.sort(spatialiteMaps, new SpatialiteMapOrderComparator()); try { for (SpatialiteMap spatialiteMap : spatialiteMaps) { if (stopDrawing()) { // stop working return; } if (!spatialiteMap.isVisible) { continue; } SpatialiteDatabaseHandler spatialDatabaseHandler = spatialiteMaps2DbHandlersMap.get(spatialiteMap); SpatialVectorTable spatialTable = spatialiteMaps2TablesMap.get(spatialiteMap); Style style = spatialTable.getStyle(); if (drawZoomLevel < style.minZoom || drawZoomLevel > style.maxZoom) { // we do not draw outside of the zoom levels continue; } GeometryIterator geometryIterator = null; try { Paint fill = null; Paint stroke = null; if (style.fillcolor != null && style.fillcolor.trim().length() > 0) fill = spatialDatabaseHandler.getFillPaint4Style(style); if (style.strokecolor != null && style.strokecolor.trim().length() > 0) stroke = spatialDatabaseHandler.getStrokePaint4Style(style); PointTransformation pointTransformer = new MapsforgePointTransformation(projection, drawPosition, drawZoomLevel); ShapeWriter shapeWriter; ShapeWriter shape_writer_point = null; if (spatialTable.isPoint()) { shapeWriter = new ShapeWriter(pointTransformer, style.shape, style.size); } else { shapeWriter = new ShapeWriter(pointTransformer); if (spatialTable.isGeometryCollection()) { shape_writer_point = new ShapeWriter(pointTransformer, style.shape, style.size); } } shapeWriter.setRemoveDuplicatePoints(true); shapeWriter.setDecimation(style.decimationFactor); geometryIterator = spatialDatabaseHandler.getGeometryIteratorInBounds( LibraryConstants.SRID_WGS84_4326, spatialTable, n, s, e, w); while (geometryIterator.hasNext()) { Geometry geom = geometryIterator.next(); if (geom != null) { if (!canvasEnvelope.intersects(geom.getEnvelopeInternal())) { // TODO check the performance impact of this continue; } if (spatialTable.isGeometryCollection()) { int geometriesCount = geom.getNumGeometries(); for (int j = 0; j < geometriesCount; j++) { Geometry geom_collect = geom.getGeometryN(j); if (geom_collect != null) { String geometryType = geom_collect.getGeometryType(); if (geometryType.toUpperCase().contains("POINT")) { drawGeometry(geom_collect, canvas, shape_writer_point, fill, stroke); } else { drawGeometry(geom_collect, canvas, shapeWriter, fill, stroke); } if (stopDrawing()) { // stop working return; } } } } else { drawGeometry(geom, canvas, shapeWriter, fill, stroke); if (stopDrawing()) { // stop working return; } } } else { GPLog.error(this, "GeopaparazziOverlay.drawFromSpatialite [geom == null] description[" + spatialTable.getTableName() + "]", new NullPointerException()); } } } finally { if (geometryIterator != null) geometryIterator.close(); } } } catch (ConcurrentModificationException cme) { GPLog.error(this, "Error while looping on spatialite maps, skipped rendering.", cme); return; } /* * draw labels */ for (Map.Entry<SpatialiteMap, SpatialiteDatabaseHandler> entry : spatialiteMaps2DbHandlersMap.entrySet()) { if (stopDrawing()) { // stop working return; } SpatialiteMap spatialiteMap = entry.getKey(); if (!spatialiteMap.isVisible) { continue; } SpatialiteDatabaseHandler spatialDatabaseHandler = entry.getValue(); SpatialVectorTable spatialTable = spatialiteMaps2TablesMap.get(spatialiteMap); Style style = spatialTable.getStyle(); if (style.labelvisible == 0) { continue; } if (drawZoomLevel < style.minZoom || drawZoomLevel > style.maxZoom) { // we do not draw outside of the zoom levels continue; } float delta = style.size / 2f; if (delta < 2) { delta = 2; } Paint dbTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG); dbTextPaint.setStyle(Paint.Style.FILL); dbTextPaint.setColor(Color.BLACK); dbTextPaint.setTextSize(style.labelsize); Paint dbTextHaloPaint = new Paint(Paint.ANTI_ALIAS_FLAG); dbTextHaloPaint.setStyle(Paint.Style.STROKE); dbTextHaloPaint.setStrokeWidth(3); dbTextHaloPaint.setColor(Color.WHITE); dbTextHaloPaint.setTextSize(style.labelsize); GeometryIterator geometryIterator = null; try { PointTransformation pointTransformer = new MapsforgePointTransformation(projection, drawPosition, drawZoomLevel); ShapeWriter linesWriter = null; if (spatialTable.isLine()) { linesWriter = new ShapeWriter(pointTransformer, spatialTable.getStyle().shape, spatialTable.getStyle().size); dbTextHaloPaint.setTextAlign(Align.CENTER); dbTextPaint.setTextAlign(Align.CENTER); } else { dbTextHaloPaint.setTextAlign(Align.LEFT); dbTextPaint.setTextAlign(Align.LEFT); } if (spatialDatabaseHandler.isOpen()) { geometryIterator = spatialDatabaseHandler.getGeometryIteratorInBounds( LibraryConstants.SRID_WGS84_4326, spatialTable, n, s, e, w); while (geometryIterator.hasNext()) { Geometry geom = geometryIterator.next(); if (geom != null) { if (!canvasEnvelope.intersects(geom.getEnvelopeInternal())) { // TODO check the performance impact of this continue; } String labelText = geometryIterator.getLabelText(); if (labelText == null || labelText.length() == 0) { continue; } if (spatialTable.isGeometryCollection()) { int geometriesCount = geom.getNumGeometries(); for (int j = 0; j < geometriesCount; j++) { Geometry geom_collect = geom.getGeometryN(j); if (geom_collect != null) { drawLabel(pointTransformer, geom_collect, labelText, canvas, dbTextPaint, dbTextHaloPaint, delta, linesWriter); if (stopDrawing()) { // stop working return; } } } } else { drawLabel(pointTransformer, geom, labelText, canvas, dbTextPaint, dbTextHaloPaint, delta, linesWriter); if (stopDrawing()) { // stop working return; } } } } } } finally { if (geometryIterator != null) geometryIterator.close(); } } } catch (Exception e1) { GPLog.error(this, "GeopaparazziOverlay.drawFromSpatialite [failed]", e1); //$NON-NLS-1$ } } private boolean stopDrawing() { return isInterrupted() || sizeHasChanged() || needRedraw(); } private static void drawGeometry(Geometry geom, Canvas canvas, ShapeWriter shape_writer, Paint fill, Paint stroke) { String s_geometry_type = geom.getGeometryType(); int i_geometry_type = GeometryType.forValue(s_geometry_type); GeometryType geometry_type = GeometryType.forValue(i_geometry_type); DrawableShape shape = shape_writer.toShape(geom); switch (geometry_type) { case POINT_XY: case POINT_XYM: case POINT_XYZ: case POINT_XYZM: case MULTIPOINT_XY: case MULTIPOINT_XYM: case MULTIPOINT_XYZ: case MULTIPOINT_XYZM: { if (fill != null) shape.fill(canvas, fill); if (stroke != null) shape.draw(canvas, stroke); // GPLog.androidLog(-1,"GeopaparazziOverlay.drawGeometry geometry_type["+s_geometry_type+"]: ["+i_geometry_type+"]"); } break; case LINESTRING_XY: case LINESTRING_XYM: case LINESTRING_XYZ: case LINESTRING_XYZM: case MULTILINESTRING_XY: case MULTILINESTRING_XYM: case MULTILINESTRING_XYZ: case MULTILINESTRING_XYZM: { if (stroke != null) shape.draw(canvas, stroke); } break; case POLYGON_XY: case POLYGON_XYM: case POLYGON_XYZ: case POLYGON_XYZM: case MULTIPOLYGON_XY: case MULTIPOLYGON_XYM: case MULTIPOLYGON_XYZ: case MULTIPOLYGON_XYZM: { if (fill != null) shape.fill(canvas, fill); if (stroke != null) shape.draw(canvas, stroke); } break; default: break; } } private static void drawLabel(PointTransformation pointTransformer, Geometry geom, String label, Canvas canvas, Paint dbTextPaint, Paint dbTextHaloPaint, float delta, ShapeWriter linesWriter) { if (linesWriter == null) { /* * for points and polygons for now just use the centroid */ com.vividsolutions.jts.geom.Point centroid = geom.getCentroid(); Coordinate coordinate = centroid.getCoordinate(); PointF dest = new PointF(); pointTransformer.transform(coordinate, dest); float x = dest.x + delta; float y = dest.y - delta; // if (doNotesTextHalo) canvas.drawText(label, x, y, dbTextHaloPaint); canvas.drawText(label, x, y, dbTextPaint); } else { DrawableShape shape = linesWriter.toShape(geom); if (shape instanceof PathShape) { PathShape lineShape = (PathShape) shape; Path linePath = lineShape.getPath(); // if (doNotesTextHalo) int hOffset = 15; int vOffset = -5; canvas.drawTextOnPath(label, linePath, hOffset, vOffset, dbTextHaloPaint); canvas.drawTextOnPath(label, linePath, hOffset, vOffset, dbTextPaint); } } } @Override protected String getThreadName() { return THREAD_NAME; } /** * This method should be called after ways have been added to the overlay. */ protected final void populate() { super.requestRedraw(); } /** * Creates an item in this overlay. * * @param index the index of the item. * @return the item. */ protected abstract OverlayItem createItem(int index); /** * Checks whether an item has been hit by an event and calls the appropriate handler. * * @param geoPoint the point of the event. * @param mapView the {@link MapView} that triggered the event. * @param eventType the type of the event. * @return true if an item has been hit, false otherwise. */ protected boolean checkItemHit(GeoPoint geoPoint, MapView mapView, EventType eventType) { Projection projection = mapView.getProjection(); Point eventPosition = projection.toPixels(geoPoint, null); Context context = mapView.getContext(); // check if the translation to pixel coordinates has failed if (eventPosition == null) { return false; } Point checkItemPoint = new Point(); synchronized (this.visibleItems) { // iterate over all visible items for (int i = this.visibleItems.size() - 1; i >= 0; --i) { Integer itemIndex = this.visibleItems.get(i); // get the current item OverlayItem checkOverlayItem = createItem(itemIndex); if (checkOverlayItem == null) { continue; } // make sure that the current item has a position if (checkOverlayItem.getPoint() == null) { continue; } checkItemPoint = projection.toPixels(checkOverlayItem.getPoint(), checkItemPoint); // check if the translation to pixel coordinates has failed if (checkItemPoint == null) { continue; } // select the correct marker for the item and get the position Drawable marker = checkOverlayItem.getMarker(); if (marker == null) return false; Rect checkMarkerBounds = marker.getBounds(); // calculate the bounding box of the marker int checkLeft = checkItemPoint.x + checkMarkerBounds.left; int checkRight = checkItemPoint.x + checkMarkerBounds.right; int checkTop = checkItemPoint.y + checkMarkerBounds.top; int checkBottom = checkItemPoint.y + checkMarkerBounds.bottom; // check if the event position is within the bounds of the marker if (checkRight >= eventPosition.x && checkLeft <= eventPosition.x && checkBottom >= eventPosition.y && checkTop <= eventPosition.y) { switch (eventType) { case LONG_PRESS: if (onLongPress(itemIndex)) { return true; } break; case TAP: if (onTap(context, itemIndex)) { return true; } break; } } } } // no hit return false; } /** * Handles a long press event. * <p/> * The default implementation of this method does nothing and returns false. * * @param index the index of the item that has been long pressed. * @return true if the event was handled, false otherwise. */ protected boolean onLongPress(int index) { return false; } /** * Handles a tap event. * <p/> * The default implementation of this method does nothing and returns false. * * @param index the index of the item that has been tapped. * @return true if the event was handled, false otherwise. */ protected boolean onTap(Context context, int index) { OverlayItem item = createItem(index); if (item != null) { String title = item.getTitle(); String snippet = item.getSnippet(); GeoPoint position = item.getPoint(); int latE6 = position.latitudeE6; int lonE6 = position.longitudeE6; float lat = latE6 / LibraryConstants.E6; float lon = lonE6 / LibraryConstants.E6; if (title != null && ImageUtilities.isImagePath(title)) { openImage(context, title, snippet); } else { boolean doInfo = true; if (context instanceof MapviewActivity) { MapviewActivity mapActivity = (MapviewActivity) context; float n = lat + LibraryConstants.PICKRADIUS; float s = lat - LibraryConstants.PICKRADIUS; float w = lon - LibraryConstants.PICKRADIUS; float e = lon + LibraryConstants.PICKRADIUS; try { List<Note> notesInWorldBounds = DaoNotes.getNotesList(new float[]{n, s, w, e}, false); if (notesInWorldBounds.size() > 0) { Note note = notesInWorldBounds.get(0); // String description = note.getDescription(); String name = note.getName(); String form = note.getForm(); if (form != null && form.length() > 0) { double altim = note.getAltim(); Intent formIntent = new Intent(context, FormActivity.class); FormInfoHolder formInfoHolder = new FormInfoHolder(); formInfoHolder.sectionName = name; formInfoHolder.formName = null; formInfoHolder.noteId = note.getId(); formInfoHolder.longitude = lon; formInfoHolder.latitude = lat; formInfoHolder.elevation = altim; formInfoHolder.sectionObjectString = form; formInfoHolder.objectExists = true; formIntent.putExtra(FormInfoHolder.BUNDLE_KEY_INFOHOLDER, formInfoHolder); mapActivity.startActivityForResult(formIntent, MapviewActivity.FORMUPDATE_RETURN_CODE); doInfo = false; } } } catch (IOException e1) { GPLog.error(this, "Error", e1); } } if (doInfo) { StringBuilder sb = new StringBuilder(); if (snippet != null && snippet.length() > 0) { sb.append(snippet); sb.append("\n"); //$NON-NLS-1$ } String latStr = context.getString(R.string.lat); String lonStr = context.getString(R.string.lon); sb.append(latStr).append(" ").append(lat).append("\n"); //$NON-NLS-1$ //$NON-NLS-2$ sb.append(lonStr).append(" ").append(lon); //$NON-NLS-1$ GPDialogs.infoDialog(context, sb.toString(), null); } } return true; } return false; } private void openImage(Context context, String title, String snippet) { try { // get image from db long imageID = Long.parseLong(snippet); int length = title.length(); String ext = title.substring(length - 4, length); String tempImageName = ImageUtilities.getTempImageName(ext); byte[] imageData = new DaoImages().getImageData(imageID); AppsUtilities.showImage(imageData, tempImageName, context ); } catch (java.lang.Exception e) { GPLog.error(this, null, e); } } @Override public void dispose() { super.dispose(); } }