Skip to content

Commit

Permalink
refine #298:
Browse files Browse the repository at this point in the history
In case user is moving fast (passenger): distance is now the minimum distance between quest geometry and line between location when the form was opened and when the user clicked "okay"

Correct distance calculation into meters. Before, it was simply wrong (calculated bigger distances the further you were from the equator).

Change layout of dialog again: Include the reason why the question is asked in the first place (=the rough measured distance to the quest) and make the note about the stars less prominent.

Concession: the app offers the "I am there option" for quests that are up to 250 away but only does not ask for less than 50 away.
  • Loading branch information
westnordost committed Jun 10, 2017
1 parent af448b3 commit 1bc1e98
Show file tree
Hide file tree
Showing 6 changed files with 162 additions and 37 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -584,7 +584,9 @@ private void downloadAreaConfirmed(BoundingBox bbox)

@Override public void onAnsweredQuest(final long questId, final QuestGroup group, final Bundle answer)
{
questSource.findSource(questId, group, lastLocation, new FindQuestSourceComponent.Listener()
// line between location now and location when the form was opened
Location[] locations = new Location[]{ lastLocation, mapFragment.getDisplayedLocation() };
questSource.findSource(questId, group, locations, new FindQuestSourceComponent.Listener()
{
@Override public void onFindQuestSourceResult(String source)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,17 @@
import android.app.Activity;
import android.content.DialogInterface;
import android.location.Location;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextView;

import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.operation.distance.DistanceOp;

import java.util.ArrayList;
import java.util.List;

import javax.inject.Inject;

Expand All @@ -23,7 +30,22 @@
* at the GPS position or asking the user */
public class FindQuestSourceComponent
{
private static final float MAX_DISTANCE_TO_ELEMENT_FOR_SURVEY = 100; //m
/* Considerations for choosing these values:
* - users should be encouraged to *really* go right there and check even if they think they
* see it from afar already
*
* - just having walked by something should though still count as survey though. (It might be
* inappropriate or awkward to stop and flip out the smartphone directly there)
*
* - GPS position might not be updated right after they fetched it out of their pocket, but GPS
* position should be reset to "unknown" (instead of "wrong") when switching back to the app
*
* - the distance is the minimum distance between the quest geometry (i.e. a road) and the line
* between the user's position when he opened the quest form and the position when he pressed
* "ok", MINUS the current GPS accuracy, so it is a pretty forgiving calculation already
* */
private static final float MAX_DISTANCE_TO_ELEMENT_FOR_POSSIBLE_SURVEY = 250; //m
private static final float MAX_DISTANCE_TO_ELEMENT_FOR_SURVEY = 50; //m

private static final String
SURVEY = "survey",
Expand Down Expand Up @@ -51,20 +73,24 @@ public void onCreate(Activity context)
this.activity = context;
}

public void findSource(final long questId, final QuestGroup group, final Location location,
public void findSource(final long questId, final QuestGroup group, final Location[] locations,
final Listener listener)
{
Double distance = getDistanceToElement(questId, group, location);
Double distance = getDistanceToElementInMeters(questId, group, locations);
if(distance != null && distance < MAX_DISTANCE_TO_ELEMENT_FOR_SURVEY)
{
listener.onFindQuestSourceResult(SURVEY);
}
else
{
View inner = LayoutInflater.from(activity).inflate(
R.layout.quest_source_dialog_layout, null, false);
TextView distanceMessage = (TextView) inner.findViewById(R.id.distanceMessage);

AlertDialogBuilder alertDialogBuilder = new AlertDialogBuilder(activity);
alertDialogBuilder
.setTitle(R.string.quest_source_dialog_title)
.setView(R.layout.quest_source_dialog_layout)
.setView(inner)
.setPositiveButton(R.string.quest_source_dialog_button_visited_before, new DialogInterface.OnClickListener()
{
@Override public void onClick(DialogInterface dialog, int which)
Expand All @@ -75,6 +101,7 @@ public void findSource(final long questId, final QuestGroup group, final Locatio

if(distance == null)
{
distanceMessage.setText(R.string.quest_source_dialog_unknown_location);
alertDialogBuilder.setNeutralButton(R.string.quest_source_dialog_button_on_site, new DialogInterface.OnClickListener()
{
@Override public void onClick(DialogInterface dialog, int which)
Expand All @@ -85,29 +112,48 @@ public void findSource(final long questId, final QuestGroup group, final Locatio
}
else
{
alertDialogBuilder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener()
distanceMessage.setText(String.format(activity.getString(R.string.quest_source_dialog_far_away_location), roundToFifties(distance)));
if(distance < MAX_DISTANCE_TO_ELEMENT_FOR_POSSIBLE_SURVEY)
{
@Override public void onClick(DialogInterface dialog, int which)
alertDialogBuilder.setNeutralButton(R.string.quest_source_dialog_button_on_site, new DialogInterface.OnClickListener()
{
// nothing
}
});
@Override public void onClick(DialogInterface dialog, int which)
{
listener.onFindQuestSourceResult(SURVEY);
}
});
}
else
{
alertDialogBuilder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener()
{
@Override public void onClick(DialogInterface dialog, int which)
{
// nothing
}
});
}
}
alertDialogBuilder.show();
}
}

private Double getDistanceToElement(long questId, QuestGroup group, Location location)
private int roundToFifties(Double distance)
{
return (distance.intValue() / 50) * 50;
}

private Double getDistanceToElementInMeters(long questId, QuestGroup group, Location[] locations)
{
if(location == null) return null;
try
{
Geometry locationGeometry = geometryFactory.createPoint(new Coordinate(location.getLongitude(), location.getLatitude()));
Geometry questGeometry = JTSConst.toGeometry(getQuestGeometry(questId, group));
List<Location> locationsList = asListWithoutNullsAndDuplicates(locations);
Geometry locationGeometry = createLocationsGeometry(locationsList);
if(locationGeometry == null) return null;
double accuracy = getMeanAccuracy(locationsList);

double degreeDistance = questGeometry.distance(locationGeometry);
double distanceInMeters = SphericalEarthMath.EARTH_RADIUS * Math.PI * degreeDistance / 180;
return Math.max(0, distanceInMeters - location.getAccuracy());
Geometry questGeometry = JTSConst.toGeometry(getQuestGeometry(questId, group));
return Math.max(0, getDistanceInMeters(locationGeometry, questGeometry) - accuracy);
}
catch (RuntimeException e)
{
Expand All @@ -117,6 +163,67 @@ private Double getDistanceToElement(long questId, QuestGroup group, Location loc
}
}

private double getMeanAccuracy(List<Location> locations)
{
double accuracySum = 0;

for(Location location : locations)
{
accuracySum += location.getAccuracy();
}
return locations.isEmpty() ? 0 : accuracySum / locations.size();
}

private Geometry createLocationsGeometry(List<Location> locations)
{
if(locations == null || locations.isEmpty()) return null;
if(locations.size() == 1)
{
Location location = locations.get(0);
return geometryFactory.createPoint(createCoordinate(location));
}
Coordinate[] coordinates = new Coordinate[locations.size()];
for(int i=0; i<locations.size(); ++i)
{
Location location = locations.get(i);
coordinates[i] = createCoordinate(location);
}
return geometryFactory.createLineString(coordinates);
}

private static Coordinate createCoordinate(Location location)
{
return new Coordinate(location.getLongitude(), location.getLatitude());
}

private static List<Location> asListWithoutNullsAndDuplicates(Location[] locations)
{
List<Location> result = new ArrayList<>(locations.length);
Location previous = null;
for (int i = 0; i < locations.length; i++)
{
Location current = locations[i];
if(current == null) continue;
if(previous != null)
{
if( previous.getLatitude() == current.getLatitude() &&
previous.getLongitude() == current.getLongitude()) continue;
}

result.add(current);

previous = current;
}
return result;
}

private static double getDistanceInMeters(Geometry one, Geometry two)
{
Coordinate[] nearest = DistanceOp.nearestPoints(one, two);
return SphericalEarthMath.distance(
JTSConst.toLatLon(nearest[0]), JTSConst.toLatLon(nearest[1]));
}

private ElementGeometry getQuestGeometry(long questId, QuestGroup group)
{
switch (group)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="64dp"
android:height="64dp"
android:width="32dp"
android:height="32dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:strokeColor="#222"
android:strokeWidth="1"
android:strokeColor="#666"
android:strokeWidth="2"
android:strokeLineJoin="round"
android:pathData="M12,17.27L18.18,21l-1.64,-7.03L22,9.24l-7.19,-0.61L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21z"/>
</vector>
41 changes: 27 additions & 14 deletions app/src/main/res/layout/quest_source_dialog_layout.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,33 @@
android:layout_height="wrap_content"
android:padding="@dimen/dialog_horizontal_margin">

<ImageView
android:id="@+id/imageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_hollow_star_gray_64dp"/>
<TextView
android:id="@id/textView"
android:layout_width="wrap_content"
android:id="@+id/distanceMessage"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:textAppearance="@android:style/TextAppearance.Theme.Dialog"/>

<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_toEndOf="@+id/imageView"
android:layout_marginStart="8dp"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:textAppearance="@android:style/TextAppearance.Theme.Dialog"
android:text="@string/quest_source_dialog_message"/>
android:layout_below="@id/distanceMessage"
android:layout_marginTop="@dimen/dialog_vertical_margin">

<ImageView
android:id="@+id/imageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:src="@drawable/ic_hollow_star_gray_32dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toEndOf="@+id/imageView"
android:layout_marginStart="8dp"
android:layout_centerVertical="true"
android:layout_alignParentEnd="true"
android:text="@string/quest_source_dialog_note"/>

</RelativeLayout>
</RelativeLayout>
</RelativeLayout>
3 changes: 2 additions & 1 deletion app/src/main/res/values/dimens.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
<dimen name="activity_horizontal_margin">16dp</dimen>
<dimen name="activity_vertical_margin">16dp</dimen>

<dimen name="dialog_horizontal_margin">16dp</dimen>
<dimen name="dialog_horizontal_margin">24dp</dimen>
<dimen name="dialog_vertical_margin">16dp</dimen>

<!-- Form content -->
<dimen name="quest_form_vertical_margin">8dp</dimen>
Expand Down
4 changes: 3 additions & 1 deletion app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -269,9 +269,11 @@ The info you enter is then directly added to the OpenStreetMap in your name, wit
<string name="quest_tactilePaving_title_bus">"Does this bus stop have tactile pavings?"</string>
<string name="quest_tactilePaving_title_crosswalk">"Does this crosswalk have tactile pavings?"</string>
<string name="quest_source_dialog_title">How do you know?</string>
<string name="quest_source_dialog_message">Note: You can only collect survey stars for quests you solve on-site.</string>
<string name="quest_source_dialog_note">Note: You can only collect stars for quests you solve on-site.</string>
<string name="quest_tactilePaving_title_name_bus">"Does the bus stop \"%s\" have tactile pavings?"</string>

<string name="quest_source_dialog_button_visited_before">I was there before</string>
<string name="quest_source_dialog_button_on_site">I am there now</string>
<string name="quest_source_dialog_unknown_location">Your position cannot be determined.</string>
<string name="quest_source_dialog_far_away_location">You appear to be more than %dm away.</string>
</resources>

0 comments on commit 1bc1e98

Please sign in to comment.