diff --git a/app/src/androidTest/java/de/westnordost/streetcomplete/data/AQuestDaoTest.java b/app/src/androidTest/java/de/westnordost/streetcomplete/data/AQuestDaoTest.java index 53da7bb98c..57ed3ae5d1 100644 --- a/app/src/androidTest/java/de/westnordost/streetcomplete/data/AQuestDaoTest.java +++ b/app/src/androidTest/java/de/westnordost/streetcomplete/data/AQuestDaoTest.java @@ -47,7 +47,7 @@ public void testAddGet() Quest q2 = dao.get(id); assertEquals(q.getId(), q2.getId()); - assertEquals(q.getMarkerLocation(), q2.getMarkerLocation()); + assertEquals(q.getCenter(), q2.getCenter()); assertEquals(q.getStatus(), q2.getStatus()); } @@ -181,7 +181,7 @@ private static Quest createQuest(long id, long lastUpdate, QuestStatus status) when(quest.getId()).thenReturn(id); when(quest.getStatus()).thenReturn(status); when(quest.getLastUpdate()).thenReturn(new Date(lastUpdate)); - when(quest.getMarkerLocation()).thenReturn(new OsmLatLon(0,0)); + when(quest.getCenter()).thenReturn(new OsmLatLon(0,0)); return quest; } @@ -190,7 +190,7 @@ private static Quest createQuest(long id, double lat, double lon, QuestStatus st Quest quest = mock(Quest.class); when(quest.getStatus()).thenReturn(status); when(quest.getId()).thenReturn(id); - when(quest.getMarkerLocation()).thenReturn(new OsmLatLon(lat,lon)); + when(quest.getCenter()).thenReturn(new OsmLatLon(lat,lon)); when(quest.getLastUpdate()).thenReturn(new Date()); return quest; } diff --git a/app/src/androidTest/java/de/westnordost/streetcomplete/data/TestQuestDao.java b/app/src/androidTest/java/de/westnordost/streetcomplete/data/TestQuestDao.java index 26ee27ffaa..bc9d8519b7 100644 --- a/app/src/androidTest/java/de/westnordost/streetcomplete/data/TestQuestDao.java +++ b/app/src/androidTest/java/de/westnordost/streetcomplete/data/TestQuestDao.java @@ -56,8 +56,8 @@ public TestQuestDao(SQLiteOpenHelper dbHelper) insert.bindLong(1, quest.getId()); insert.bindString(2, quest.getStatus().name()); - insert.bindDouble(3, quest.getMarkerLocation().getLatitude()); - insert.bindDouble(4, quest.getMarkerLocation().getLongitude()); + insert.bindDouble(3, quest.getCenter().getLatitude()); + insert.bindDouble(4, quest.getCenter().getLongitude()); insert.bindDouble(5, quest.getLastUpdate().getTime()); return insert.executeInsert(); @@ -75,8 +75,8 @@ public TestQuestDao(SQLiteOpenHelper dbHelper) { ContentValues v = new ContentValues(); v.put(ID_COL, quest.getId()); - v.put(LAT_COL, quest.getMarkerLocation().getLatitude()); - v.put(LON_COL, quest.getMarkerLocation().getLongitude()); + v.put(LAT_COL, quest.getCenter().getLatitude()); + v.put(LON_COL, quest.getCenter().getLongitude()); return v; } @@ -91,7 +91,7 @@ public static Quest createQuest(long id, double lat, double lon, QuestStatus sta Quest quest = mock(Quest.class); when(quest.getStatus()).thenReturn(status); when(quest.getId()).thenReturn(id); - when(quest.getMarkerLocation()).thenReturn(new OsmLatLon(lat,lon)); + when(quest.getCenter()).thenReturn(new OsmLatLon(lat,lon)); when(quest.getLastUpdate()).thenReturn(new Date(time)); return quest; } @@ -118,4 +118,4 @@ public TestDbHelper(Context context) } } -} \ No newline at end of file +} diff --git a/app/src/androidTest/java/de/westnordost/streetcomplete/data/osm/persist/OsmQuestDaoTest.java b/app/src/androidTest/java/de/westnordost/streetcomplete/data/osm/persist/OsmQuestDaoTest.java index 3d03947ca4..ef68c91eb3 100644 --- a/app/src/androidTest/java/de/westnordost/streetcomplete/data/osm/persist/OsmQuestDaoTest.java +++ b/app/src/androidTest/java/de/westnordost/streetcomplete/data/osm/persist/OsmQuestDaoTest.java @@ -256,7 +256,7 @@ private void checkEqual(OsmQuest quest, OsmQuest dbQuest) assertEquals(quest.getChanges(), dbQuest.getChanges()); assertEquals(quest.getChangesSource(), dbQuest.getChangesSource()); assertEquals(quest.getGeometry(), dbQuest.getGeometry()); - assertEquals(quest.getMarkerLocation(), dbQuest.getMarkerLocation()); + assertEquals(quest.getCenter(), dbQuest.getCenter()); // is now updated to current time on DB insert // no: assertEquals(quest.getLastUpdate(), dbQuest.getLastUpdate()); } diff --git a/app/src/androidTest/java/de/westnordost/streetcomplete/data/osmnotes/OsmNoteQuestDaoTest.java b/app/src/androidTest/java/de/westnordost/streetcomplete/data/osmnotes/OsmNoteQuestDaoTest.java index 59d31bc772..454abd8108 100644 --- a/app/src/androidTest/java/de/westnordost/streetcomplete/data/osmnotes/OsmNoteQuestDaoTest.java +++ b/app/src/androidTest/java/de/westnordost/streetcomplete/data/osmnotes/OsmNoteQuestDaoTest.java @@ -105,7 +105,7 @@ private void checkEqual(OsmNoteQuest quest, OsmNoteQuest dbQuest) { assertEquals(quest.getLastUpdate(), dbQuest.getLastUpdate()); assertEquals(quest.getStatus(), dbQuest.getStatus()); - assertEquals(quest.getMarkerLocation(), dbQuest.getMarkerLocation()); + assertEquals(quest.getCenter(), dbQuest.getCenter()); assertEquals(quest.getComment(), dbQuest.getComment()); assertEquals(quest.getId(), dbQuest.getId()); assertEquals(quest.getType(), dbQuest.getType()); diff --git a/app/src/main/java/de/westnordost/streetcomplete/MainActivity.java b/app/src/main/java/de/westnordost/streetcomplete/MainActivity.java index 04a934e0d8..a594a33f6a 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/MainActivity.java +++ b/app/src/main/java/de/westnordost/streetcomplete/MainActivity.java @@ -759,7 +759,7 @@ private void showQuestSolvedAnimation(Quest quest, String source) int size = (int) DpUtil.toPx(42, this); int[] offset = new int[2]; mapFragment.getView().getLocationOnScreen(offset); - PointF startPos = mapFragment.getPointOf(quest.getMarkerLocation()); + PointF startPos = mapFragment.getPointOf(quest.getCenter()); startPos.x += offset[0] - size/2; startPos.y += offset[1] - size*1.5; showMarkerSolvedAnimation(quest.getType().getIcon(), startPos, source); diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/Quest.java b/app/src/main/java/de/westnordost/streetcomplete/data/Quest.java index 7b7de9969f..8abd566d4d 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/Quest.java +++ b/app/src/main/java/de/westnordost/streetcomplete/data/Quest.java @@ -11,7 +11,8 @@ public interface Quest Long getId(); void setId(long id); - LatLon getMarkerLocation(); + LatLon getCenter(); + LatLon[] getMarkerLocations(); ElementGeometry getGeometry(); QuestType getType(); diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/QuestController.java b/app/src/main/java/de/westnordost/streetcomplete/data/QuestController.java index 0cd1cea20a..7b9bc9d995 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/QuestController.java +++ b/app/src/main/java/de/westnordost/streetcomplete/data/QuestController.java @@ -152,7 +152,7 @@ public boolean createNote(long osmQuestId, String questTitle, String text, Array if(q == null || q.getStatus() != QuestStatus.NEW) return false; CreateNote createNote = new CreateNote(); - createNote.position = q.getMarkerLocation(); + createNote.position = q.getCenter(); createNote.text = text; createNote.questTitle = questTitle; createNote.elementType = q.getElementType(); diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/AOsmElementQuestType.java b/app/src/main/java/de/westnordost/streetcomplete/data/osm/AOsmElementQuestType.java index 86c315e153..467c843c50 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osm/AOsmElementQuestType.java +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osm/AOsmElementQuestType.java @@ -11,4 +11,5 @@ public abstract class AOsmElementQuestType implements OsmElementQuestType @NonNull @Override public Countries getEnabledForCountries() { return Countries.ALL; } @Override public void cleanMetadata() { } @Override public int getTitle() { return getTitle(Collections.emptyMap()); } + @Override public boolean hasMarkersAtEnds() { return false; } } diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/OsmElementQuestType.java b/app/src/main/java/de/westnordost/streetcomplete/data/osm/OsmElementQuestType.java index 2074df8620..63a1e7eb0e 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osm/OsmElementQuestType.java +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osm/OsmElementQuestType.java @@ -42,4 +42,7 @@ public interface OsmElementQuestType extends QuestType /** The quest type can clean it's metadata here, if any */ void cleanMetadata(); + + /** @return whether the markers should be at the ends instead of the center */ + boolean hasMarkersAtEnds(); } diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/OsmQuest.java b/app/src/main/java/de/westnordost/streetcomplete/data/osm/OsmQuest.java index c28ca1d0f9..d180709343 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osm/OsmQuest.java +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osm/OsmQuest.java @@ -3,6 +3,7 @@ import android.support.annotation.Nullable; import java.util.Date; +import java.util.List; import de.westnordost.streetcomplete.data.Quest; import de.westnordost.streetcomplete.data.QuestStatus; @@ -10,6 +11,7 @@ import de.westnordost.streetcomplete.data.QuestType; import de.westnordost.osmapi.map.data.Element; import de.westnordost.osmapi.map.data.LatLon; +import de.westnordost.streetcomplete.util.SphericalEarthMath; /** Represents one task for the user to complete/correct the data based on one OSM element */ public class OsmQuest implements Quest @@ -56,7 +58,24 @@ public OsmQuest(Long id, OsmElementQuestType type, Element.Type elementType, lon return id; } - @Override public LatLon getMarkerLocation() + @Override public LatLon[] getMarkerLocations() + { + if(getOsmElementQuestType().hasMarkersAtEnds() && geometry.polylines != null) + { + List polyline = geometry.polylines.get(0); + double length = SphericalEarthMath.distance(polyline); + if(length > 15*4) + { + return new LatLon[]{ + SphericalEarthMath.pointOnPolylineFromStart(polyline, 15), + SphericalEarthMath.pointOnPolylineFromEnd(polyline, 15), + }; + } + } + return new LatLon[]{getCenter()}; + } + + @Override public LatLon getCenter() { return geometry.center; } diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osmnotes/OsmNoteQuest.java b/app/src/main/java/de/westnordost/streetcomplete/data/osmnotes/OsmNoteQuest.java index dbd57cdef2..b92d6909b0 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osmnotes/OsmNoteQuest.java +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osmnotes/OsmNoteQuest.java @@ -66,9 +66,9 @@ public OsmNoteQuest(Long id, Note note, QuestStatus status, String comment, Date return id; } - @Override public LatLon getMarkerLocation() + @Override public LatLon[] getMarkerLocations() { - return note.position; + return new LatLon[]{note.position}; } @Override public ElementGeometry getGeometry() @@ -79,7 +79,12 @@ public OsmNoteQuest(Long id, Note note, QuestStatus status, String comment, Date // will/should likely not show up for other users of this app // no geometry other than the marker location - return new ElementGeometry(getMarkerLocation()); + return new ElementGeometry(getCenter()); + } + + @Override public LatLon getCenter() + { + return note.position; } public Note getNote() diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osmnotes/OsmNoteQuestChangesUpload.java b/app/src/main/java/de/westnordost/streetcomplete/data/osmnotes/OsmNoteQuestChangesUpload.java index e987c4cdc9..fabfd0f38c 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osmnotes/OsmNoteQuestChangesUpload.java +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osmnotes/OsmNoteQuestChangesUpload.java @@ -111,7 +111,7 @@ Note uploadNoteChanges(OsmNoteQuest quest) private static String getNoteQuestStringForLog(OsmNoteQuest n) { - LatLon pos = n.getMarkerLocation(); + LatLon pos = n.getCenter(); return "\"" + n.getComment() + "\" at " + pos.getLatitude() + ", " + pos.getLongitude(); } } diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osmnotes/OsmNoteQuestType.java b/app/src/main/java/de/westnordost/streetcomplete/data/osmnotes/OsmNoteQuestType.java index 28fcc2ac17..0ae71bb67a 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osmnotes/OsmNoteQuestType.java +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osmnotes/OsmNoteQuestType.java @@ -10,6 +10,5 @@ public class OsmNoteQuestType implements QuestType @Override public AbstractQuestAnswerFragment createForm() { return new NoteDiscussionForm(); } @Override public int getIcon() { return R.drawable.ic_quest_notes; } @Override public int getTitle() { return R.string.quest_noteDiscussion_title; } - @Override public int getDefaultDisabledMessage() { return 0; } } diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/construction/MarkCompletedHighwayConstruction.java b/app/src/main/java/de/westnordost/streetcomplete/quests/construction/MarkCompletedHighwayConstruction.java index be8e900ab8..9074f99c59 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/construction/MarkCompletedHighwayConstruction.java +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/construction/MarkCompletedHighwayConstruction.java @@ -75,4 +75,6 @@ else if ("footway".equals(tags.get("construction"))) } return R.string.quest_construction_generic_title; } + + @Override public boolean hasMarkersAtEnds() { return true; } } diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/localized_name/AddRoadName.java b/app/src/main/java/de/westnordost/streetcomplete/quests/localized_name/AddRoadName.java index 4d98758cb8..445ed6676b 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/localized_name/AddRoadName.java +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/localized_name/AddRoadName.java @@ -147,4 +147,6 @@ else if(noProperRoad == AddRoadNameForm.IS_LINK) if (isPedestrian) return R.string.quest_streetName_pedestrian_title; else return R.string.quest_streetName_title; } + + @Override public boolean hasMarkersAtEnds() { return true; } } diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/max_speed/AddMaxSpeed.java b/app/src/main/java/de/westnordost/streetcomplete/quests/max_speed/AddMaxSpeed.java index a052a23482..edf6de3341 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/max_speed/AddMaxSpeed.java +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/max_speed/AddMaxSpeed.java @@ -90,4 +90,6 @@ else if (maxspeed != null) // see #813: US has different rules for each different state which need to be respected return Countries.allExcept(new String[]{"US"}); } + + @Override public boolean hasMarkersAtEnds() { return true; } } diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/oneway/AddOneway.java b/app/src/main/java/de/westnordost/streetcomplete/quests/oneway/AddOneway.java index 5197eb938a..05d3bbc36b 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/oneway/AddOneway.java +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/oneway/AddOneway.java @@ -169,4 +169,6 @@ private static int findClosestPositionIndexOf(List positions, LatLon lat { return R.string.quest_oneway_title; } + + @Override public boolean hasMarkersAtEnds() { return true; } } diff --git a/app/src/main/java/de/westnordost/streetcomplete/tangram/QuestsMapFragment.java b/app/src/main/java/de/westnordost/streetcomplete/tangram/QuestsMapFragment.java index 2e6fd4c83c..d93768b313 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/tangram/QuestsMapFragment.java +++ b/app/src/main/java/de/westnordost/streetcomplete/tangram/QuestsMapFragment.java @@ -386,35 +386,39 @@ public void addQuests(Iterable quests, QuestGroup group) continue; } - if(first) first = false; - else geoJson.append(","); - - LatLon pos = quest.getMarkerLocation(); String questIconName = getActivity().getResources().getResourceEntryName(quest.getType().getIcon()); Integer order = questTypeOrder.get(quest.getType()); if(order == null) order = 0; - geoJson.append("{\"type\":\"Feature\","); - geoJson.append("\"geometry\":{\"type\":\"Point\",\"coordinates\": ["); - geoJson.append(pos.getLongitude()); - geoJson.append(","); - geoJson.append(pos.getLatitude()); - geoJson.append("]},\"properties\": {\"type\":\"point\", \"kind\":\""); - geoJson.append(questIconName); - geoJson.append("\",\""); - geoJson.append(MARKER_QUEST_GROUP); - geoJson.append("\":\""); - geoJson.append(group.name()); - geoJson.append("\",\""); - geoJson.append(MARKER_QUEST_ID); - geoJson.append("\":\""); - geoJson.append(quest.getId()); - geoJson.append("\",\""); - geoJson.append("order"); - geoJson.append("\":\""); - geoJson.append(order); - geoJson.append("\"}}"); + LatLon[] positions = quest.getMarkerLocations(); + + for (LatLon pos : positions) + { + if(first) first = false; + else geoJson.append(","); + + geoJson.append("{\"type\":\"Feature\","); + geoJson.append("\"geometry\":{\"type\":\"Point\",\"coordinates\": ["); + geoJson.append(pos.getLongitude()); + geoJson.append(","); + geoJson.append(pos.getLatitude()); + geoJson.append("]},\"properties\": {\"type\":\"point\", \"kind\":\""); + geoJson.append(questIconName); + geoJson.append("\",\""); + geoJson.append(MARKER_QUEST_GROUP); + geoJson.append("\":\""); + geoJson.append(group.name()); + geoJson.append("\",\""); + geoJson.append(MARKER_QUEST_ID); + geoJson.append("\":\""); + geoJson.append(quest.getId()); + geoJson.append("\",\""); + geoJson.append("order"); + geoJson.append("\":\""); + geoJson.append(order); + geoJson.append("\"}}"); + } } geoJson.append("]}"); diff --git a/app/src/main/java/de/westnordost/streetcomplete/util/ReverseIterator.java b/app/src/main/java/de/westnordost/streetcomplete/util/ReverseIterator.java new file mode 100644 index 0000000000..c41b5847ad --- /dev/null +++ b/app/src/main/java/de/westnordost/streetcomplete/util/ReverseIterator.java @@ -0,0 +1,15 @@ +package de.westnordost.streetcomplete.util; + +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; + +public class ReverseIterator implements Iterator +{ + private final ListIterator iterator; + + public ReverseIterator(List list) { this.iterator = list.listIterator(list.size()); } + @Override public boolean hasNext() { return iterator.hasPrevious(); } + @Override public T next() { return iterator.previous(); } + @Override public void remove() { iterator.remove(); } +} diff --git a/app/src/main/java/de/westnordost/streetcomplete/util/SphericalEarthMath.java b/app/src/main/java/de/westnordost/streetcomplete/util/SphericalEarthMath.java index 6eb0fde11f..29ebcb0aec 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/util/SphericalEarthMath.java +++ b/app/src/main/java/de/westnordost/streetcomplete/util/SphericalEarthMath.java @@ -3,6 +3,7 @@ import java.util.ArrayList; import java.util.Iterator; import java.util.List; +import java.util.ListIterator; import de.westnordost.osmapi.map.data.BoundingBox; import de.westnordost.osmapi.map.data.LatLon; @@ -215,12 +216,39 @@ public static List centerLineOfPolyline(List positions) * @throws IllegalArgumentException if positions list is empty */ public static LatLon centerPointOfPolyline(List positions) + { + double halfDistance = distance(positions) / 2; + LatLon result = pointOnPolylineFromStart(positions, halfDistance); + if(result == null) result = positions.get(0); + return result; + } + + /** + * @return the point the given amount of meters into the polyline + * @params meters the amount of meters + * @throws IllegalArgumentException if positions list is empty + */ + public static LatLon pointOnPolylineFromStart(List positions, double meters) + { + return pointOnPolyline(positions, meters, false); + } + + /** + * @return the point the given amount of meters into the polyline, starting from the end + * @params meters the amount of meters + * @throws IllegalArgumentException if positions list is empty + */ + public static LatLon pointOnPolylineFromEnd(List positions, double meters) + { + return pointOnPolyline(positions, meters, true); + } + + private static LatLon pointOnPolyline(List positions, double meters, boolean fromEnd) { if(positions.isEmpty()) throw new IllegalArgumentException("positions list is empty"); - double halfDistance = distance(positions) / 2; double distance = 0; - Iterator it = positions.iterator(); + Iterator it = fromEnd ? new ReverseIterator<>(positions) : positions.iterator(); LatLon p0 = it.next(), p1; while (it.hasNext()) { @@ -230,9 +258,9 @@ public static LatLon centerPointOfPolyline(List positions) if(segmentDistance > 0) { distance += segmentDistance; - if (distance >= halfDistance) + if (distance >= meters) { - double ratio = (distance - halfDistance) / segmentDistance; + double ratio = (distance - meters) / segmentDistance; double lat = p1.getLatitude() - ratio * (p1.getLatitude() - p0.getLatitude()); double lon = normalizeLongitude(p1.getLongitude() - ratio * normalizeLongitude(p1.getLongitude() - p0.getLongitude())); return new OsmLatLon(lat, lon); @@ -240,7 +268,7 @@ public static LatLon centerPointOfPolyline(List positions) } p0 = p1; } - return positions.get(0); + return null; } /** diff --git a/app/src/test/java/de/westnordost/streetcomplete/util/ReverseIteratorTest.java b/app/src/test/java/de/westnordost/streetcomplete/util/ReverseIteratorTest.java new file mode 100644 index 0000000000..6d90cbc2be --- /dev/null +++ b/app/src/test/java/de/westnordost/streetcomplete/util/ReverseIteratorTest.java @@ -0,0 +1,17 @@ +package de.westnordost.streetcomplete.util; + +import junit.framework.TestCase; + +import java.util.Arrays; + +public class ReverseIteratorTest extends TestCase +{ + public void testReverse() + { + ReverseIterator it = new ReverseIterator<>(Arrays.asList("a", "b", "c")); + assertEquals("c", it.next()); + assertEquals("b", it.next()); + assertEquals("a", it.next()); + assertFalse(it.hasNext()); + } +}