implement route information draw some route icons, small bug fix
git-svn-id: https://osmand.googlecode.com/svn/trunk@205 e29c36b1-1cfa-d876-8d93-3434fc2bb7b8
This commit is contained in:
parent
497bd6bf3e
commit
451f061330
16 changed files with 542 additions and 85 deletions
|
@ -13,14 +13,22 @@ public class ToDoConstants {
|
||||||
*/
|
*/
|
||||||
public int DESCRIBE_ABOUT_AUTHORS = 8;
|
public int DESCRIBE_ABOUT_AUTHORS = 8;
|
||||||
|
|
||||||
|
|
||||||
// TODO ANDROID
|
// TODO ANDROID
|
||||||
// 42. Revise UI (icons/layouts). Support different devices. Add inactive/focus(!) icon versions.
|
// 42. Revise UI (icons/layouts). Support different devices. Add inactive/focus(!) icon versions.
|
||||||
// Some icons are not fine (as back menu from map - it is blured).
|
// Some icons are not fine (as back menu from map - it is blured).
|
||||||
|
|
||||||
|
// TODO: draw EXIT!!!, YOURS (calc turn/angle/time). [other done]
|
||||||
// 56. Add usage of CloudMade API for calculating route (show next turn & distance to it instead of mini map).
|
// 56. Add usage of CloudMade API for calculating route (show next turn & distance to it instead of mini map).
|
||||||
// 57. Implement routing information about expected time arriving
|
// 57. Implement routing information about expected time arriving
|
||||||
// 58. Implement difference about show route/follow route (show travel time/arrival time, show mini map/next turn, etc)
|
// 58. Implement difference about show route/follow route (show travel time/arrival time, show mini map/next turn, etc)
|
||||||
|
// 59. Show route information (directions/time, ....). Could be shown in context menu route.
|
||||||
|
|
||||||
// 46. Implement downloading strategy for tiles : select max zoom to download [16,15,14,...]
|
// 46. Implement downloading strategy for tiles : select max zoom to download [16,15,14,...]
|
||||||
// That means you can save internet because from [16 -> zoom -> 18], [14 -> zoom -> 16 - suitable for speed > 40], ...
|
// That means you can save internet because from [16 -> zoom -> 18], [14 -> zoom -> 16 - suitable for speed > 40], ...
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 58. Upload/Download zip-index from site & unzip them on phone
|
// 58. Upload/Download zip-index from site & unzip them on phone
|
||||||
// 50. Invent opening hours editor in order to edit POI hours better on device
|
// 50. Invent opening hours editor in order to edit POI hours better on device
|
||||||
// 53. Add progress bars : to internet communication activities [editing/commiting/deleting poi], do not hide edit poi dialog if operation failed
|
// 53. Add progress bars : to internet communication activities [editing/commiting/deleting poi], do not hide edit poi dialog if operation failed
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
<activity android:name=".activities.search.SearchActivity" android:label="@string/search_activity" ></activity>
|
<activity android:name=".activities.search.SearchActivity" android:label="@string/search_activity" ></activity>
|
||||||
<activity android:name=".activities.NavigatePointActivity"></activity>
|
<activity android:name=".activities.NavigatePointActivity"></activity>
|
||||||
<activity android:name=".activities.DownloadIndexActivity"></activity>
|
<activity android:name=".activities.DownloadIndexActivity"></activity>
|
||||||
|
<activity android:name=".activities.ShowRouteInfoActivity"></activity>
|
||||||
<activity android:name=".activities.FavouritesActivity" android:label="@string/favourites_activity"></activity>
|
<activity android:name=".activities.FavouritesActivity" android:label="@string/favourites_activity"></activity>
|
||||||
|
|
||||||
<activity android:name=".activities.search.SearchPOIActivity" android:label="@string/searchpoi_activity"></activity>
|
<activity android:name=".activities.search.SearchPOIActivity" android:label="@string/searchpoi_activity"></activity>
|
||||||
|
@ -26,6 +27,7 @@
|
||||||
<activity android:name=".activities.search.SearchStreet2ByNameActivity"></activity>
|
<activity android:name=".activities.search.SearchStreet2ByNameActivity"></activity>
|
||||||
<activity android:name=".activities.search.SearchBuildingByNameActivity"></activity>
|
<activity android:name=".activities.search.SearchBuildingByNameActivity"></activity>
|
||||||
|
|
||||||
|
|
||||||
<activity android:name=".activities.EditPOIFilterActivity"></activity>
|
<activity android:name=".activities.EditPOIFilterActivity"></activity>
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
|
|
17
OsmAnd/res/layout/route_info_list_item.xml
Normal file
17
OsmAnd/res/layout/route_info_list_item.xml
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<ImageView android:id="@+id/direction" android:layout_width="32dp" android:layout_height="32dp"></ImageView>
|
||||||
|
<TextView android:text="@+id/TextView01" android:id="@+id/distance" android:layout_marginLeft ="5dp"
|
||||||
|
android:layout_width="50dp" android:layout_height="wrap_content"></TextView>
|
||||||
|
<TextView android:text="@+id/TextView02" android:id="@+id/description" android:layout_weight="1"
|
||||||
|
android:layout_width="wrap_content" android:layout_height="wrap_content"></TextView>
|
||||||
|
<TextView android:text="@+id/TextView02" android:id="@+id/time" android:layout_marginLeft ="5dp"
|
||||||
|
android:layout_width="45dp" android:layout_height="wrap_content"></TextView>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</LinearLayout>
|
|
@ -1,5 +1,9 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
|
<string name="route_about">О маршруте</string>
|
||||||
|
<string name="route_general_information">Общая протяженность = {0}, время в пути = ''{1}''.</string>
|
||||||
|
<string name="router_service_descr">Выберите сервис для прокладки маршрута</string>
|
||||||
|
<string name="router_service">Прокладка маршрута</string>
|
||||||
<string name="download_sd_dir_not_accessible">Директория на SD карточка не доступна для сохранения</string>
|
<string name="download_sd_dir_not_accessible">Директория на SD карточка не доступна для сохранения</string>
|
||||||
<string name="download_question">Вы хотите загрузить {0} - {1} ?</string>
|
<string name="download_question">Вы хотите загрузить {0} - {1} ?</string>
|
||||||
<string name="address">Адрес</string>
|
<string name="address">Адрес</string>
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
|
<string name="route_about">About route</string>
|
||||||
|
<string name="route_general_information">Overall distance = {0}, travelling time = ''{1}''.</string>
|
||||||
|
<string name="router_service_descr">Choose routing service</string>
|
||||||
|
<string name="router_service">Routing</string>
|
||||||
<string name="download_sd_dir_not_accessible">Directory on SD card to save index is not accessible</string>
|
<string name="download_sd_dir_not_accessible">Directory on SD card to save index is not accessible</string>
|
||||||
<string name="download_question">Do you want to download {0} - {1} ?</string>
|
<string name="download_question">Do you want to download {0} - {1} ?</string>
|
||||||
<string name="address">Address</string>
|
<string name="address">Address</string>
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
<CheckBoxPreference android:summary="@string/use_english_names_descr" android:title="@string/use_english_names" android:key="use_english_names"></CheckBoxPreference>
|
<CheckBoxPreference android:summary="@string/use_english_names_descr" android:title="@string/use_english_names" android:key="use_english_names"></CheckBoxPreference>
|
||||||
<Preference android:title="@string/reload_indexes" android:key="reload_indexes" android:summary="@string/reload_indexes_descr"></Preference>
|
<Preference android:title="@string/reload_indexes" android:key="reload_indexes" android:summary="@string/reload_indexes_descr"></Preference>
|
||||||
<Preference android:title="@string/download_indexes" android:key="download_indexes" android:summary="@string/download_indexes_descr"></Preference>
|
<Preference android:title="@string/download_indexes" android:key="download_indexes" android:summary="@string/download_indexes_descr"></Preference>
|
||||||
|
<ListPreference android:title="@string/router_service" android:key="router_service" android:summary="@string/router_service_descr"></ListPreference>
|
||||||
</PreferenceScreen>
|
</PreferenceScreen>
|
||||||
|
|
||||||
<PreferenceScreen android:title="@string/monitor_preferences" android:summary="@string/monitor_preferences_descr">
|
<PreferenceScreen android:title="@string/monitor_preferences" android:summary="@string/monitor_preferences_descr">
|
||||||
|
|
|
@ -6,6 +6,7 @@ import android.content.Context;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.content.SharedPreferences.Editor;
|
import android.content.SharedPreferences.Editor;
|
||||||
|
|
||||||
|
import com.osmand.activities.RouteProvider.RouteService;
|
||||||
import com.osmand.map.ITileSource;
|
import com.osmand.map.ITileSource;
|
||||||
import com.osmand.map.TileSourceManager;
|
import com.osmand.map.TileSourceManager;
|
||||||
import com.osmand.map.TileSourceManager.TileSourceTemplate;
|
import com.osmand.map.TileSourceManager.TileSourceTemplate;
|
||||||
|
@ -111,6 +112,24 @@ public class OsmandSettings {
|
||||||
return prefs.edit().putString(APPLICATION_MODE, p.name()).commit();
|
return prefs.edit().putString(APPLICATION_MODE, p.name()).commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// this value string is synchronized with settings_pref.xml preference name
|
||||||
|
public static final String ROUTER_SERVICE = "router_service"; //$NON-NLS-1$
|
||||||
|
|
||||||
|
public static RouteService getRouterService(Context ctx) {
|
||||||
|
SharedPreferences prefs = ctx.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_WORLD_READABLE);
|
||||||
|
int ord = prefs.getInt(ROUTER_SERVICE, RouteService.CLOUDMADE.ordinal());
|
||||||
|
if(ord < RouteService.values().length){
|
||||||
|
return RouteService.values()[ord];
|
||||||
|
} else {
|
||||||
|
return RouteService.CLOUDMADE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean setRouterService(Context ctx, RouteService p) {
|
||||||
|
SharedPreferences prefs = ctx.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_WORLD_READABLE);
|
||||||
|
return prefs.edit().putInt(ROUTER_SERVICE, p.ordinal()).commit();
|
||||||
|
}
|
||||||
|
|
||||||
// this value string is synchronized with settings_pref.xml preference name
|
// this value string is synchronized with settings_pref.xml preference name
|
||||||
public static final String SAVE_CURRENT_TRACK = "save_current_track"; //$NON-NLS-1$
|
public static final String SAVE_CURRENT_TRACK = "save_current_track"; //$NON-NLS-1$
|
||||||
public static final String RELOAD_INDEXES = "reload_indexes"; //$NON-NLS-1$
|
public static final String RELOAD_INDEXES = "reload_indexes"; //$NON-NLS-1$
|
||||||
|
|
|
@ -176,6 +176,8 @@ public class EditPOIFilterActivity extends ListActivity {
|
||||||
}
|
}
|
||||||
if (subCategories.size() == accepted.size()) {
|
if (subCategories.size() == accepted.size()) {
|
||||||
filter.selectSubTypesToAccept(amenity, null);
|
filter.selectSubTypesToAccept(amenity, null);
|
||||||
|
} else if(accepted.size() == 0){
|
||||||
|
filter.setTypeToAccept(amenity, false);
|
||||||
} else {
|
} else {
|
||||||
filter.selectSubTypesToAccept(amenity, accepted);
|
filter.selectSubTypesToAccept(amenity, accepted);
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,7 +38,6 @@ import com.osmand.osm.LatLon;
|
||||||
import com.osmand.osm.MapUtils;
|
import com.osmand.osm.MapUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Maxim Frolov
|
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public class FavouritesActivity extends ListActivity {
|
public class FavouritesActivity extends ListActivity {
|
||||||
|
|
|
@ -94,7 +94,6 @@ public class MapActivity extends Activity implements LocationListener, IMapLocat
|
||||||
|
|
||||||
private SavingTrackHelper savingTrackHelper;
|
private SavingTrackHelper savingTrackHelper;
|
||||||
private RoutingHelper routingHelper;
|
private RoutingHelper routingHelper;
|
||||||
private boolean calculateRouteOnGps = false;
|
|
||||||
|
|
||||||
|
|
||||||
private WakeLock wakeLock;
|
private WakeLock wakeLock;
|
||||||
|
@ -171,7 +170,11 @@ public class MapActivity extends Activity implements LocationListener, IMapLocat
|
||||||
|
|
||||||
routingHelper.setAppMode(OsmandSettings.getApplicationMode(this));
|
routingHelper.setAppMode(OsmandSettings.getApplicationMode(this));
|
||||||
if(!Algoritms.objectEquals(routingHelper.getFinalLocation(), pointToNavigate)){
|
if(!Algoritms.objectEquals(routingHelper.getFinalLocation(), pointToNavigate)){
|
||||||
|
// there is no way how to clear mode. Only user can do : clear point to navigate, exit from app & set up new point.
|
||||||
|
// that case help to not calculate route at all.
|
||||||
|
routingHelper.setFollowingMode(false);
|
||||||
routingHelper.setFinalAndCurrentLocation(pointToNavigate, null);
|
routingHelper.setFinalAndCurrentLocation(pointToNavigate, null);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
navigationLayer.setPointToNavigate(pointToNavigate);
|
navigationLayer.setPointToNavigate(pointToNavigate);
|
||||||
|
@ -354,7 +357,7 @@ public class MapActivity extends Activity implements LocationListener, IMapLocat
|
||||||
updateSpeedBearing(location);
|
updateSpeedBearing(location);
|
||||||
|
|
||||||
locationLayer.setLastKnownLocation(location);
|
locationLayer.setLastKnownLocation(location);
|
||||||
if(calculateRouteOnGps){
|
if(routingHelper.isFollowingMode()){
|
||||||
routingHelper.setCurrentLocation(location);
|
routingHelper.setCurrentLocation(location);
|
||||||
}
|
}
|
||||||
if (location != null) {
|
if (location != null) {
|
||||||
|
@ -405,7 +408,7 @@ public class MapActivity extends Activity implements LocationListener, IMapLocat
|
||||||
}
|
}
|
||||||
routingHelper.setFinalAndCurrentLocation(point, null);
|
routingHelper.setFinalAndCurrentLocation(point, null);
|
||||||
if(point == null){
|
if(point == null){
|
||||||
calculateRouteOnGps = false;
|
routingHelper.setFollowingMode(false);
|
||||||
}
|
}
|
||||||
navigationLayer.setPointToNavigate(point);
|
navigationLayer.setPointToNavigate(point);
|
||||||
updateNavigateToPointMenu();
|
updateNavigateToPointMenu();
|
||||||
|
@ -507,6 +510,8 @@ public class MapActivity extends Activity implements LocationListener, IMapLocat
|
||||||
@Override
|
@Override
|
||||||
protected void onResume() {
|
protected void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
|
// routing helper with current activity
|
||||||
|
routingHelper = RoutingHelper.getInstance(this);
|
||||||
if(mapView.getMap() != OsmandSettings.getMapTileSource(this)){
|
if(mapView.getMap() != OsmandSettings.getMapTileSource(this)){
|
||||||
mapView.setMap(OsmandSettings.getMapTileSource(this));
|
mapView.setMap(OsmandSettings.getMapTileSource(this));
|
||||||
}
|
}
|
||||||
|
@ -672,10 +677,18 @@ public class MapActivity extends Activity implements LocationListener, IMapLocat
|
||||||
Location map = new Location("map"); //$NON-NLS-1$
|
Location map = new Location("map"); //$NON-NLS-1$
|
||||||
map.setLatitude(lat);
|
map.setLatitude(lat);
|
||||||
map.setLongitude(lon);
|
map.setLongitude(lon);
|
||||||
calculateRouteOnGps = true;
|
routingHelper.setFollowingMode(true);
|
||||||
routingHelper.setFinalAndCurrentLocation(navigationLayer.getPointToNavigate(), map);
|
routingHelper.setFinalAndCurrentLocation(navigationLayer.getPointToNavigate(), map);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
if(routingHelper.isRouterEnabled()){
|
||||||
|
builder.setNeutralButton(R.string.route_about, new DialogInterface.OnClickListener(){
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
startActivity(new Intent(MapActivity.this, ShowRouteInfoActivity.class));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
builder.setNegativeButton(R.string.only_show, new DialogInterface.OnClickListener(){
|
builder.setNegativeButton(R.string.only_show, new DialogInterface.OnClickListener(){
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -684,11 +697,12 @@ public class MapActivity extends Activity implements LocationListener, IMapLocat
|
||||||
Location map = new Location("map"); //$NON-NLS-1$
|
Location map = new Location("map"); //$NON-NLS-1$
|
||||||
map.setLatitude(lat);
|
map.setLatitude(lat);
|
||||||
map.setLongitude(lon);
|
map.setLongitude(lon);
|
||||||
calculateRouteOnGps = false;
|
routingHelper.setFollowingMode(false);
|
||||||
routingHelper.setFinalAndCurrentLocation(navigationLayer.getPointToNavigate(), map);
|
routingHelper.setFinalAndCurrentLocation(navigationLayer.getPointToNavigate(), map);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
builder.show();
|
builder.show();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void reloadTile(final int zoom, final double latitude, final double longitude){
|
protected void reloadTile(final int zoom, final double latitude, final double longitude){
|
||||||
|
|
|
@ -32,6 +32,17 @@ import com.osmand.osm.LatLon;
|
||||||
public class RouteProvider {
|
public class RouteProvider {
|
||||||
private static final org.apache.commons.logging.Log log = LogUtil.getLog(RouteProvider.class);
|
private static final org.apache.commons.logging.Log log = LogUtil.getLog(RouteProvider.class);
|
||||||
|
|
||||||
|
public enum RouteService {
|
||||||
|
CLOUDMADE("CloudMade"), YOURS("YOURS"); //$NON-NLS-1$ //$NON-NLS-2$
|
||||||
|
private final String name;
|
||||||
|
private RouteService(String name){
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public RouteProvider(){
|
public RouteProvider(){
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,6 +91,10 @@ public class RouteProvider {
|
||||||
for (int i = directions.size() - 1; i >= 0; i--) {
|
for (int i = directions.size() - 1; i >= 0; i--) {
|
||||||
directions.get(i).afterLeftTime = sum;
|
directions.get(i).afterLeftTime = sum;
|
||||||
sum += directions.get(i).expectedTime;
|
sum += directions.get(i).expectedTime;
|
||||||
|
directions.get(i).distance = listDistance[directions.get(i).routePointOffset];
|
||||||
|
if(i < directions.size() - 1){
|
||||||
|
directions.get(i).distance -=listDistance[directions.get(i + 1).routePointOffset];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -93,15 +108,19 @@ public class RouteProvider {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public RouteCalculationResult calculateRouteImpl(Location start, LatLon end, ApplicationMode mode){
|
public RouteCalculationResult calculateRouteImpl(Location start, LatLon end, ApplicationMode mode, RouteService type){
|
||||||
long time = System.currentTimeMillis();
|
long time = System.currentTimeMillis();
|
||||||
if (start != null && end != null) {
|
if (start != null && end != null) {
|
||||||
if(log.isInfoEnabled()){
|
if(log.isInfoEnabled()){
|
||||||
log.info("Start finding route from " + start + " to " + end); //$NON-NLS-1$ //$NON-NLS-2$
|
log.info("Start finding route from " + start + " to " + end +" using " + type.getName()); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
// RouteCalculationResult res = findYOURSRoute(start, end, mode);
|
RouteCalculationResult res;
|
||||||
RouteCalculationResult res = findCloudMadeRoute(start, end, mode);
|
if (type == RouteService.YOURS) {
|
||||||
|
res = findYOURSRoute(start, end, mode);
|
||||||
|
} else {
|
||||||
|
res = findCloudMadeRoute(start, end, mode);
|
||||||
|
}
|
||||||
if(log.isInfoEnabled() && res.locations != null){
|
if(log.isInfoEnabled() && res.locations != null){
|
||||||
log.info("Finding route contained " + res.locations.size() + " points for " + (System.currentTimeMillis() - time) + " ms"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
|
log.info("Finding route contained " + res.locations.size() + " points for " + (System.currentTimeMillis() - time) + " ms"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
|
||||||
}
|
}
|
||||||
|
@ -221,6 +240,7 @@ public class RouteProvider {
|
||||||
if(list.getLength() > 0){
|
if(list.getLength() > 0){
|
||||||
directions = new ArrayList<RouteDirectionInfo>();
|
directions = new ArrayList<RouteDirectionInfo>();
|
||||||
}
|
}
|
||||||
|
RouteDirectionInfo previous = null;
|
||||||
for (int i = 0; i < list.getLength(); i++) {
|
for (int i = 0; i < list.getLength(); i++) {
|
||||||
Element item = (Element) list.item(i);
|
Element item = (Element) list.item(i);
|
||||||
try {
|
try {
|
||||||
|
@ -242,13 +262,50 @@ public class RouteProvider {
|
||||||
}
|
}
|
||||||
int offset = Integer.parseInt(getContentFromNode(item, "offset")); //$NON-NLS-1$
|
int offset = Integer.parseInt(getContentFromNode(item, "offset")); //$NON-NLS-1$
|
||||||
dirInfo.routePointOffset = offset;
|
dirInfo.routePointOffset = offset;
|
||||||
|
|
||||||
|
if(previous != null && previous.turnType != TurnType.C && previous.turnType != null){
|
||||||
|
// calculate angle
|
||||||
|
if(previous.routePointOffset > 0){
|
||||||
|
float paz = res.get(previous.routePointOffset - 1).bearingTo(res.get(previous.routePointOffset));
|
||||||
|
float caz;
|
||||||
|
if(previous.turnType.isExit() && dirInfo.routePointOffset < res.size() - 1){
|
||||||
|
caz = res.get(dirInfo.routePointOffset).bearingTo(res.get(dirInfo.routePointOffset + 1));
|
||||||
|
} else {
|
||||||
|
caz = res.get(dirInfo.routePointOffset - 1).bearingTo(res.get(dirInfo.routePointOffset));
|
||||||
|
}
|
||||||
|
float angle = caz - paz;
|
||||||
|
if(angle < 0){
|
||||||
|
angle += 360;
|
||||||
|
}
|
||||||
|
if(previous.turnAngle == 0f){
|
||||||
|
previous.turnAngle = angle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
directions.add(dirInfo);
|
directions.add(dirInfo);
|
||||||
|
|
||||||
|
previous = dirInfo;
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
log.info("Exception", e); //$NON-NLS-1$
|
log.info("Exception", e); //$NON-NLS-1$
|
||||||
} catch (IllegalArgumentException e) {
|
} catch (IllegalArgumentException e) {
|
||||||
log.info("Exception", e); //$NON-NLS-1$
|
log.info("Exception", e); //$NON-NLS-1$
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if(previous != null && previous.turnType != TurnType.C && previous.turnType != null){
|
||||||
|
// calculate angle
|
||||||
|
if(previous.routePointOffset > 0 && previous.routePointOffset < res.size() - 1){
|
||||||
|
float paz = res.get(previous.routePointOffset - 1).bearingTo(res.get(previous.routePointOffset));
|
||||||
|
float caz = res.get(previous.routePointOffset).bearingTo(res.get(res.size() -1));
|
||||||
|
float angle = caz - paz;
|
||||||
|
if(angle < 0){
|
||||||
|
angle += 360;
|
||||||
|
}
|
||||||
|
if(previous.turnAngle == 0f){
|
||||||
|
previous.turnAngle = angle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,16 +1,20 @@
|
||||||
package com.osmand.activities;
|
package com.osmand.activities;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
import android.location.Location;
|
import android.location.Location;
|
||||||
import android.util.FloatMath;
|
import android.util.FloatMath;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import com.osmand.LogUtil;
|
import com.osmand.LogUtil;
|
||||||
|
import com.osmand.OsmandSettings;
|
||||||
import com.osmand.R;
|
import com.osmand.R;
|
||||||
import com.osmand.OsmandSettings.ApplicationMode;
|
import com.osmand.OsmandSettings.ApplicationMode;
|
||||||
import com.osmand.activities.RouteProvider.RouteCalculationResult;
|
import com.osmand.activities.RouteProvider.RouteCalculationResult;
|
||||||
|
import com.osmand.activities.RouteProvider.RouteService;
|
||||||
import com.osmand.osm.LatLon;
|
import com.osmand.osm.LatLon;
|
||||||
import com.osmand.osm.MapUtils;
|
import com.osmand.osm.MapUtils;
|
||||||
|
|
||||||
|
@ -19,7 +23,9 @@ public class RoutingHelper {
|
||||||
private static final org.apache.commons.logging.Log log = LogUtil.getLog(RoutingHelper.class);
|
private static final org.apache.commons.logging.Log log = LogUtil.getLog(RoutingHelper.class);
|
||||||
|
|
||||||
// activity to show messages & refresh map when route is calculated
|
// activity to show messages & refresh map when route is calculated
|
||||||
private MapActivity activity;
|
private Activity activity;
|
||||||
|
|
||||||
|
private boolean isFollowingMode = false;
|
||||||
|
|
||||||
// instead of this properties RouteCalculationResult could be used
|
// instead of this properties RouteCalculationResult could be used
|
||||||
private List<Location> routeNodes = new ArrayList<Location>();
|
private List<Location> routeNodes = new ArrayList<Location>();
|
||||||
|
@ -63,13 +69,20 @@ public class RoutingHelper {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static RoutingHelper INSTANCE = new RoutingHelper();
|
private static RoutingHelper INSTANCE = new RoutingHelper();
|
||||||
public static RoutingHelper getInstance(MapActivity activity){
|
public static RoutingHelper getInstance(Activity ctx){
|
||||||
INSTANCE.activity = activity;
|
INSTANCE.activity = ctx;
|
||||||
return INSTANCE;
|
return INSTANCE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public boolean isFollowingMode() {
|
||||||
|
return isFollowingMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFollowingMode(boolean isFollowingMode) {
|
||||||
|
this.isFollowingMode = isFollowingMode;
|
||||||
|
}
|
||||||
|
|
||||||
public synchronized void setFinalAndCurrentLocation(LatLon finalLocation, Location currentLocation){
|
public synchronized void setFinalAndCurrentLocation(LatLon finalLocation, Location currentLocation){
|
||||||
this.finalLocation = finalLocation;
|
this.finalLocation = finalLocation;
|
||||||
|
@ -107,9 +120,7 @@ public class RoutingHelper {
|
||||||
Location lastPoint = routeNodes.get(routeNodes.size() - 1);
|
Location lastPoint = routeNodes.get(routeNodes.size() - 1);
|
||||||
if(currentRoute > routeNodes.size() - 3 && currentLocation.distanceTo(lastPoint) < 60){
|
if(currentRoute > routeNodes.size() - 3 && currentLocation.distanceTo(lastPoint) < 60){
|
||||||
if(lastFixedLocation != null && lastFixedLocation.distanceTo(lastPoint) < 60){
|
if(lastFixedLocation != null && lastFixedLocation.distanceTo(lastPoint) < 60){
|
||||||
if(activity != null){
|
|
||||||
showMessage(activity.getString(R.string.arrived_at_destination));
|
showMessage(activity.getString(R.string.arrived_at_destination));
|
||||||
}
|
|
||||||
updateCurrentRoute(routeNodes.size() - 1);
|
updateCurrentRoute(routeNodes.size() - 1);
|
||||||
// clear final location to prevent all time showing message
|
// clear final location to prevent all time showing message
|
||||||
finalLocation = null;
|
finalLocation = null;
|
||||||
|
@ -231,16 +242,26 @@ public class RoutingHelper {
|
||||||
currentRoute = 0;
|
currentRoute = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized int getDistance(double lat, double lon){
|
public synchronized int getLeftDistance(){
|
||||||
if(listDistance != null && currentRoute < listDistance.length){
|
if(listDistance != null && currentRoute < listDistance.length){
|
||||||
int dist = listDistance[currentRoute];
|
int dist = listDistance[currentRoute];
|
||||||
Location l = routeNodes.get(currentRoute);
|
Location l = routeNodes.get(currentRoute);
|
||||||
dist += MapUtils.getDistance(lat, lon, l.getLatitude(), l.getLongitude());
|
if(lastFixedLocation != null){
|
||||||
|
dist += lastFixedLocation.distanceTo(l);
|
||||||
|
}
|
||||||
return dist;
|
return dist;
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Location getLocationFromRouteDirection(RouteDirectionInfo i){
|
||||||
|
if(i.routePointOffset < routeNodes.size()){
|
||||||
|
return routeNodes.get(i.routePointOffset);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public RouteDirectionInfo getNextRouteDirectionInfo(){
|
public RouteDirectionInfo getNextRouteDirectionInfo(){
|
||||||
if(directionInfo != null && currentDirectionInfo < directionInfo.size() - 1){
|
if(directionInfo != null && currentDirectionInfo < directionInfo.size() - 1){
|
||||||
return directionInfo.get(currentDirectionInfo + 1);
|
return directionInfo.get(currentDirectionInfo + 1);
|
||||||
|
@ -248,6 +269,13 @@ public class RoutingHelper {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<RouteDirectionInfo> getRouteDirections(){
|
||||||
|
if(directionInfo != null && currentDirectionInfo < directionInfo.size()){
|
||||||
|
return directionInfo.subList(currentDirectionInfo, directionInfo.size());
|
||||||
|
}
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
public int getDistanceToNextRouteDirection() {
|
public int getDistanceToNextRouteDirection() {
|
||||||
if (directionInfo != null && currentDirectionInfo < directionInfo.size()) {
|
if (directionInfo != null && currentDirectionInfo < directionInfo.size()) {
|
||||||
int dist = listDistance[currentRoute];
|
int dist = listDistance[currentRoute];
|
||||||
|
@ -282,6 +310,7 @@ public class RoutingHelper {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void calculateRoute(final Location start, final LatLon end){
|
public void calculateRoute(final Location start, final LatLon end){
|
||||||
|
final RouteService service = OsmandSettings.getRouterService(activity);
|
||||||
if(currentRunningJob == null){
|
if(currentRunningJob == null){
|
||||||
// do not evaluate very often
|
// do not evaluate very often
|
||||||
if (System.currentTimeMillis() - lastTimeEvaluatedRoute > evalWaitInterval) {
|
if (System.currentTimeMillis() - lastTimeEvaluatedRoute > evalWaitInterval) {
|
||||||
|
@ -289,7 +318,7 @@ public class RoutingHelper {
|
||||||
currentRunningJob = new Thread(new Runnable() {
|
currentRunningJob = new Thread(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
RouteCalculationResult res = provider.calculateRouteImpl(start, end, mode);
|
RouteCalculationResult res = provider.calculateRouteImpl(start, end, mode, service);
|
||||||
synchronized (RoutingHelper.this) {
|
synchronized (RoutingHelper.this) {
|
||||||
if (res.isCalculated()) {
|
if (res.isCalculated()) {
|
||||||
setNewRoute(res);
|
setNewRoute(res);
|
||||||
|
@ -303,13 +332,15 @@ public class RoutingHelper {
|
||||||
}
|
}
|
||||||
currentRunningJob = null;
|
currentRunningJob = null;
|
||||||
}
|
}
|
||||||
if (activity != null) {
|
|
||||||
if (res.isCalculated()) {
|
if (res.isCalculated()) {
|
||||||
int[] dist = res.getListDistance();
|
int[] dist = res.getListDistance();
|
||||||
int l = dist != null && dist.length > 0 ? dist[0] : 0;
|
int l = dist != null && dist.length > 0 ? dist[0] : 0;
|
||||||
showMessage(activity.getString(R.string.new_route_calculated_dist) + MapUtils.getFormattedDistance(l));
|
showMessage(activity.getString(R.string.new_route_calculated_dist) + MapUtils.getFormattedDistance(l));
|
||||||
|
if (activity instanceof MapActivity) {
|
||||||
// be aware that is non ui thread
|
// be aware that is non ui thread
|
||||||
activity.getMapView().refreshMap();
|
((MapActivity) activity).getMapView().refreshMap();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if (res.getErrorMessage() != null) {
|
if (res.getErrorMessage() != null) {
|
||||||
showMessage(activity.getString(R.string.error_calculating_route) + res.getErrorMessage());
|
showMessage(activity.getString(R.string.error_calculating_route) + res.getErrorMessage());
|
||||||
|
@ -319,7 +350,6 @@ public class RoutingHelper {
|
||||||
showMessage(activity.getString(R.string.empty_route_calculated));
|
showMessage(activity.getString(R.string.empty_route_calculated));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
lastTimeEvaluatedRoute = System.currentTimeMillis();
|
lastTimeEvaluatedRoute = System.currentTimeMillis();
|
||||||
}
|
}
|
||||||
}, "Calculating route"); //$NON-NLS-1$
|
}, "Calculating route"); //$NON-NLS-1$
|
||||||
|
@ -382,26 +412,63 @@ public class RoutingHelper {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public static enum TurnType {
|
public static class TurnType {
|
||||||
C , // continue (go straight)
|
public static final TurnType C = new TurnType("C"); // continue (go straight) //$NON-NLS-1$
|
||||||
TL, // turn left
|
public static final TurnType TL = new TurnType("TL"); // turn left //$NON-NLS-1$
|
||||||
TSLL, // turn slight left
|
public static final TurnType TSLL = new TurnType("TSLL"); // turn slight left //$NON-NLS-1$
|
||||||
TSHL, // turn sharp left
|
public static final TurnType TSHL = new TurnType("TSHL"); // turn sharp left //$NON-NLS-1$
|
||||||
TR, // turn right
|
public static final TurnType TR = new TurnType("TR"); // turn right //$NON-NLS-1$
|
||||||
TSLR, // turn slight right
|
public static final TurnType TSLR = new TurnType("TSLR"); // turn slight right //$NON-NLS-1$
|
||||||
TSHR, // turn sharp right
|
public static final TurnType TSHR = new TurnType("TSHR"); // turn sharp right //$NON-NLS-1$
|
||||||
TU, // U-turn
|
public static final TurnType TU = new TurnType("TU"); // U-turn //$NON-NLS-1$
|
||||||
|
public static TurnType[] vals = new TurnType[] {C, TL, TSLL, TSHL, TR, TSLR, TSHR, TU};
|
||||||
|
|
||||||
// TODO Exit3...
|
|
||||||
|
public static TurnType valueOf(String s){
|
||||||
|
for(TurnType v : vals){
|
||||||
|
if(v.getValue().equals(s)){
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(s!= null && s.startsWith("EXIT")){ //$NON-NLS-1$
|
||||||
|
return getExitTurn(Integer.parseInt(s.substring(4)));
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final String value;
|
||||||
|
private int exitOut;
|
||||||
|
|
||||||
|
public static TurnType getExitTurn(int out){
|
||||||
|
return new TurnType("EXIT", out); //$NON-NLS-1$
|
||||||
|
}
|
||||||
|
private TurnType(String value, int exitOut){
|
||||||
|
this.value = value;
|
||||||
|
this.exitOut = exitOut;
|
||||||
|
}
|
||||||
|
private TurnType(String value){
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
public String getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
public int getExitOut() {
|
||||||
|
return exitOut;
|
||||||
|
}
|
||||||
|
public boolean isExit(){
|
||||||
|
return value.equals("EXIT"); //$NON-NLS-1$
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class RouteDirectionInfo {
|
public static class RouteDirectionInfo {
|
||||||
public String descriptionRoute;
|
public String descriptionRoute = ""; //$NON-NLS-1$
|
||||||
public int expectedTime;
|
public int expectedTime;
|
||||||
public float turnAngle;
|
public float turnAngle; // calculated CW head rotation if previous direction to NORTH
|
||||||
public TurnType turnType;
|
public TurnType turnType;
|
||||||
public int routePointOffset;
|
public int routePointOffset;
|
||||||
|
// calculated vars
|
||||||
public int afterLeftTime;
|
public int afterLeftTime;
|
||||||
|
public int distance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,7 @@ import com.osmand.ProgressDialogImplementation;
|
||||||
import com.osmand.R;
|
import com.osmand.R;
|
||||||
import com.osmand.ResourceManager;
|
import com.osmand.ResourceManager;
|
||||||
import com.osmand.OsmandSettings.ApplicationMode;
|
import com.osmand.OsmandSettings.ApplicationMode;
|
||||||
|
import com.osmand.activities.RouteProvider.RouteService;
|
||||||
import com.osmand.map.TileSourceManager;
|
import com.osmand.map.TileSourceManager;
|
||||||
import com.osmand.map.TileSourceManager.TileSourceTemplate;
|
import com.osmand.map.TileSourceManager.TileSourceTemplate;
|
||||||
|
|
||||||
|
@ -46,6 +47,7 @@ public class SettingsActivity extends PreferenceActivity implements OnPreference
|
||||||
private EditTextPreference userPassword;
|
private EditTextPreference userPassword;
|
||||||
private Preference reloadIndexes;
|
private Preference reloadIndexes;
|
||||||
private Preference downloadIndexes;
|
private Preference downloadIndexes;
|
||||||
|
private ListPreference routerPreference;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
@ -93,6 +95,8 @@ public class SettingsActivity extends PreferenceActivity implements OnPreference
|
||||||
positionOnMap.setOnPreferenceChangeListener(this);
|
positionOnMap.setOnPreferenceChangeListener(this);
|
||||||
tileSourcePreference =(ListPreference) screen.findPreference(OsmandSettings.MAP_TILE_SOURCES);
|
tileSourcePreference =(ListPreference) screen.findPreference(OsmandSettings.MAP_TILE_SOURCES);
|
||||||
tileSourcePreference.setOnPreferenceChangeListener(this);
|
tileSourcePreference.setOnPreferenceChangeListener(this);
|
||||||
|
routerPreference =(ListPreference) screen.findPreference(OsmandSettings.ROUTER_SERVICE);
|
||||||
|
routerPreference.setOnPreferenceChangeListener(this);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -143,8 +147,18 @@ public class SettingsActivity extends PreferenceActivity implements OnPreference
|
||||||
applicationMode.setValue(OsmandSettings.getApplicationMode(this).name());
|
applicationMode.setValue(OsmandSettings.getApplicationMode(this).name());
|
||||||
|
|
||||||
|
|
||||||
|
String[] entries = new String[RouteService.values().length];
|
||||||
|
String entry = OsmandSettings.getRouterService(this).getName();
|
||||||
|
for(int i=0; i<entries.length; i++){
|
||||||
|
entries[i] = RouteService.values()[i].getName();
|
||||||
|
}
|
||||||
|
routerPreference.setEntries(entries);
|
||||||
|
routerPreference.setEntryValues(entries);
|
||||||
|
routerPreference.setValue(entry);
|
||||||
|
|
||||||
|
|
||||||
List<TileSourceTemplate> list = TileSourceManager.getKnownSourceTemplates();
|
List<TileSourceTemplate> list = TileSourceManager.getKnownSourceTemplates();
|
||||||
String[] entries = new String[list.size()];
|
entries = new String[list.size()];
|
||||||
for (int i = 0; i < list.size(); i++) {
|
for (int i = 0; i < list.size(); i++) {
|
||||||
entries[i] = list.get(i).getName();
|
entries[i] = list.get(i).getName();
|
||||||
}
|
}
|
||||||
|
@ -206,6 +220,18 @@ public class SettingsActivity extends PreferenceActivity implements OnPreference
|
||||||
} else if(preference == positionOnMap){
|
} else if(preference == positionOnMap){
|
||||||
edit.putInt(OsmandSettings.POSITION_ON_MAP, positionOnMap.findIndexOfValue((String) newValue));
|
edit.putInt(OsmandSettings.POSITION_ON_MAP, positionOnMap.findIndexOfValue((String) newValue));
|
||||||
edit.commit();
|
edit.commit();
|
||||||
|
} else if (preference == routerPreference) {
|
||||||
|
RouteService s = null;
|
||||||
|
for(RouteService r : RouteService.values()){
|
||||||
|
if(r.getName().equals(newValue)){
|
||||||
|
s = r;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(s != null){
|
||||||
|
edit.putInt(OsmandSettings.ROUTER_SERVICE, s.ordinal());
|
||||||
|
}
|
||||||
|
edit.commit();
|
||||||
} else if (preference == tileSourcePreference) {
|
} else if (preference == tileSourcePreference) {
|
||||||
edit.putString(OsmandSettings.MAP_TILE_SOURCES, (String) newValue);
|
edit.putString(OsmandSettings.MAP_TILE_SOURCES, (String) newValue);
|
||||||
edit.commit();
|
edit.commit();
|
||||||
|
|
148
OsmAnd/src/com/osmand/activities/ShowRouteInfoActivity.java
Normal file
148
OsmAnd/src/com/osmand/activities/ShowRouteInfoActivity.java
Normal file
|
@ -0,0 +1,148 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package com.osmand.activities;
|
||||||
|
|
||||||
|
import java.text.MessageFormat;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import android.app.ListActivity;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.graphics.ColorFilter;
|
||||||
|
import android.graphics.Matrix;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
import android.graphics.Path;
|
||||||
|
import android.graphics.Paint.Style;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.location.Location;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.text.format.DateFormat;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.ArrayAdapter;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.ListView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import com.osmand.OsmandSettings;
|
||||||
|
import com.osmand.R;
|
||||||
|
import com.osmand.activities.RoutingHelper.RouteDirectionInfo;
|
||||||
|
import com.osmand.activities.RoutingHelper.TurnType;
|
||||||
|
import com.osmand.osm.MapUtils;
|
||||||
|
import com.osmand.views.MapInfoLayer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class ShowRouteInfoActivity extends ListActivity {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle icicle) {
|
||||||
|
super.onCreate(icicle);
|
||||||
|
ListView lv = new ListView(this);
|
||||||
|
lv.setId(android.R.id.list);
|
||||||
|
TextView header = new TextView(this);
|
||||||
|
RoutingHelper helper = RoutingHelper.getInstance(this);
|
||||||
|
int time = helper.getLeftTime()* 1000;
|
||||||
|
int dist = helper.getLeftDistance();
|
||||||
|
header.setText(MessageFormat.format(getString(R.string.route_general_information), MapUtils.getFormattedDistance(dist),
|
||||||
|
DateFormat.format("kk:mm", time))); //$NON-NLS-1$
|
||||||
|
lv.addHeaderView(header);
|
||||||
|
setContentView(lv);
|
||||||
|
setListAdapter(new RouteInfoAdapter(RoutingHelper.getInstance(this).getRouteDirections()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onListItemClick(ListView parent, View v, int position, long id) {
|
||||||
|
RouteDirectionInfo item = ((RouteInfoAdapter)getListAdapter()).getItem(position);
|
||||||
|
RoutingHelper inst = RoutingHelper.getInstance(this);
|
||||||
|
Location loc = inst.getLocationFromRouteDirection(item);
|
||||||
|
if(loc != null){
|
||||||
|
OsmandSettings.setMapLocationToShow(this, loc.getLatitude(),loc.getLongitude());
|
||||||
|
startActivity(new Intent(this, MapActivity.class));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class RouteDrawable extends Drawable {
|
||||||
|
Paint paintRouteDirection;
|
||||||
|
Path p = new Path();
|
||||||
|
Matrix m = new Matrix();
|
||||||
|
public RouteDrawable(){
|
||||||
|
m.setScale(0.33f, 0.33f);
|
||||||
|
paintRouteDirection = new Paint();
|
||||||
|
paintRouteDirection.setStyle(Style.FILL_AND_STROKE);
|
||||||
|
paintRouteDirection.setColor(Color.rgb(100, 0, 255));
|
||||||
|
paintRouteDirection.setAntiAlias(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void setRouteType(TurnType t){
|
||||||
|
MapInfoLayer.calcTurnPath(p, t, m);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void draw(Canvas canvas) {
|
||||||
|
canvas.drawPath(p, paintRouteDirection);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getOpacity() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setAlpha(int alpha) {
|
||||||
|
paintRouteDirection.setAlpha(alpha);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setColorFilter(ColorFilter cf) {
|
||||||
|
paintRouteDirection.setColorFilter(cf);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class RouteInfoAdapter extends ArrayAdapter<RouteDirectionInfo> {
|
||||||
|
RouteInfoAdapter(List<RouteDirectionInfo> list) {
|
||||||
|
super(ShowRouteInfoActivity.this, R.layout.route_info_list_item, list);
|
||||||
|
this.setNotifyOnChange(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public View getView(int position, View convertView, ViewGroup parent) {
|
||||||
|
View row = convertView;
|
||||||
|
if (row == null) {
|
||||||
|
LayoutInflater inflater = getLayoutInflater();
|
||||||
|
row = inflater.inflate(R.layout.route_info_list_item, parent, false);
|
||||||
|
}
|
||||||
|
RouteDirectionInfo model = (RouteDirectionInfo) getItem(position);
|
||||||
|
TextView label = (TextView) row.findViewById(R.id.description);
|
||||||
|
TextView distanceLabel = (TextView) row.findViewById(R.id.distance);
|
||||||
|
TextView timeLabel = (TextView) row.findViewById(R.id.time);
|
||||||
|
ImageView icon = (ImageView) row.findViewById(R.id.direction);
|
||||||
|
|
||||||
|
if(!(icon.getDrawable() instanceof RouteDrawable)){
|
||||||
|
icon.setImageDrawable(new RouteDrawable());
|
||||||
|
}
|
||||||
|
((RouteDrawable) icon.getDrawable()).setRouteType(model.turnType);
|
||||||
|
distanceLabel.setText(MapUtils.getFormattedDistance(model.distance));
|
||||||
|
label.setText(model.descriptionRoute);
|
||||||
|
if(model.expectedTime < 3600){
|
||||||
|
timeLabel.setText(DateFormat.format("mm:ss", model.expectedTime * 1000)); //$NON-NLS-1$
|
||||||
|
} else {
|
||||||
|
timeLabel.setText(DateFormat.format("k:mm:ss", model.expectedTime * 1000)); //$NON-NLS-1$
|
||||||
|
}
|
||||||
|
return row;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package com.osmand.views;
|
||||||
|
|
||||||
import android.graphics.Canvas;
|
import android.graphics.Canvas;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
|
import android.graphics.Matrix;
|
||||||
import android.graphics.Paint;
|
import android.graphics.Paint;
|
||||||
import android.graphics.Path;
|
import android.graphics.Path;
|
||||||
import android.graphics.PointF;
|
import android.graphics.PointF;
|
||||||
|
@ -10,6 +11,7 @@ import android.graphics.Paint.Style;
|
||||||
import android.location.Location;
|
import android.location.Location;
|
||||||
import android.text.format.DateFormat;
|
import android.text.format.DateFormat;
|
||||||
|
|
||||||
|
import com.osmand.Algoritms;
|
||||||
import com.osmand.Messages;
|
import com.osmand.Messages;
|
||||||
import com.osmand.activities.MapActivity;
|
import com.osmand.activities.MapActivity;
|
||||||
import com.osmand.activities.RoutingHelper.RouteDirectionInfo;
|
import com.osmand.activities.RoutingHelper.RouteDirectionInfo;
|
||||||
|
@ -58,6 +60,8 @@ public class MapInfoLayer implements OsmandMapLayer {
|
||||||
private int centerMiniRouteY;
|
private int centerMiniRouteY;
|
||||||
private int centerMiniRouteX;
|
private int centerMiniRouteX;
|
||||||
private float scaleMiniRoute;
|
private float scaleMiniRoute;
|
||||||
|
private Matrix pathTransform;
|
||||||
|
private TurnType cachedTurnType;
|
||||||
|
|
||||||
|
|
||||||
public MapInfoLayer(MapActivity map, RouteLayer layer){
|
public MapInfoLayer(MapActivity map, RouteLayer layer){
|
||||||
|
@ -105,6 +109,7 @@ public class MapInfoLayer implements OsmandMapLayer {
|
||||||
boundsForZoom = new RectF(0, 32, 35, 64);
|
boundsForZoom = new RectF(0, 32, 35, 64);
|
||||||
boundsForSpeed = new RectF(35, 32, 110, 64);
|
boundsForSpeed = new RectF(35, 32, 110, 64);
|
||||||
boundsForMiniRoute = new RectF(0, 64, 96, 196);
|
boundsForMiniRoute = new RectF(0, 64, 96, 196);
|
||||||
|
|
||||||
boundsForLeftTime = new RectF(0, 0, 75, 32);
|
boundsForLeftTime = new RectF(0, 0, 75, 32);
|
||||||
|
|
||||||
|
|
||||||
|
@ -125,6 +130,8 @@ public class MapInfoLayer implements OsmandMapLayer {
|
||||||
pathForCompass2.lineTo(9, 15);
|
pathForCompass2.lineTo(9, 15);
|
||||||
|
|
||||||
pathForTurn = new Path();
|
pathForTurn = new Path();
|
||||||
|
pathTransform = new Matrix();
|
||||||
|
pathTransform.setTranslate(boundsForMiniRoute.left, boundsForMiniRoute.top);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean distChanged(int oldDist, int dist){
|
public boolean distChanged(int oldDist, int dist){
|
||||||
|
@ -139,7 +146,7 @@ public class MapInfoLayer implements OsmandMapLayer {
|
||||||
if(map.getPointToNavigate() != null){
|
if(map.getPointToNavigate() != null){
|
||||||
int d = 0;
|
int d = 0;
|
||||||
if(map.getRoutingHelper().isRouterEnabled()){
|
if(map.getRoutingHelper().isRouterEnabled()){
|
||||||
d = map.getRoutingHelper().getDistance(view.getLatitude(), view.getLongitude());
|
d = map.getRoutingHelper().getLeftDistance();
|
||||||
}
|
}
|
||||||
if (d == 0) {
|
if (d == 0) {
|
||||||
Location.distanceBetween(view.getLatitude(), view.getLongitude(), map.getPointToNavigate().getLatitude(), map
|
Location.distanceBetween(view.getLatitude(), view.getLongitude(), map.getPointToNavigate().getLatitude(), map
|
||||||
|
@ -209,24 +216,10 @@ public class MapInfoLayer implements OsmandMapLayer {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void calcTurnPath(TurnType turnType){
|
|
||||||
pathForTurn.reset();
|
|
||||||
// if(turnType == TurnType.C){
|
|
||||||
int c = (int) ((boundsForMiniRoute.right - boundsForMiniRoute.left) /2 + boundsForMiniRoute.left);
|
|
||||||
pathForTurn.moveTo(c + 8, boundsForMiniRoute.bottom - 32);
|
|
||||||
pathForTurn.lineTo(c + 8, boundsForMiniRoute.top + 32);
|
|
||||||
pathForTurn.lineTo(c + 20, boundsForMiniRoute.top + 32);
|
|
||||||
pathForTurn.lineTo(c, boundsForMiniRoute.top + 5);
|
|
||||||
pathForTurn.lineTo(c - 20, boundsForMiniRoute.top + 32);
|
|
||||||
pathForTurn.lineTo(c - 8, boundsForMiniRoute.top + 32);
|
|
||||||
pathForTurn.lineTo(c - 8, boundsForMiniRoute.bottom - 32);
|
|
||||||
pathForTurn.close();
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
private void drawRouteInfo(Canvas canvas) {
|
private void drawRouteInfo(Canvas canvas) {
|
||||||
if(routeLayer != null && routeLayer.getHelper().isRouterEnabled()){
|
if(routeLayer != null && routeLayer.getHelper().isRouterEnabled() && !routeLayer.getHelper().isFollowingMode() ){
|
||||||
if (showMiniMap) {
|
int d = routeLayer.getHelper().getDistanceToNextRouteDirection();
|
||||||
|
if (showMiniMap || d == 0) {
|
||||||
if (!routeLayer.getPath().isEmpty()) {
|
if (!routeLayer.getPath().isEmpty()) {
|
||||||
canvas.save();
|
canvas.save();
|
||||||
canvas.clipRect(boundsForMiniRoute);
|
canvas.clipRect(boundsForMiniRoute);
|
||||||
|
@ -240,39 +233,34 @@ public class MapInfoLayer implements OsmandMapLayer {
|
||||||
canvas.restore();
|
canvas.restore();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
int d = routeLayer.getHelper().getDistanceToNextRouteDirection();
|
|
||||||
if(d > 0){
|
|
||||||
canvas.drawRoundRect(boundsForMiniRoute, 3, 3, paintAlphaGray);
|
canvas.drawRoundRect(boundsForMiniRoute, 3, 3, paintAlphaGray);
|
||||||
canvas.drawRoundRect(boundsForMiniRoute, 3, 3, paintBlack);
|
canvas.drawRoundRect(boundsForMiniRoute, 3, 3, paintBlack);
|
||||||
RouteDirectionInfo next = routeLayer.getHelper().getNextRouteDirectionInfo();
|
RouteDirectionInfo next = routeLayer.getHelper().getNextRouteDirectionInfo();
|
||||||
if (next != null) {
|
if (next != null) {
|
||||||
calcTurnPath(next.turnType);
|
if (!Algoritms.objectEquals(cachedTurnType, next.turnType)) {
|
||||||
|
cachedTurnType = next.turnType;
|
||||||
|
calcTurnPath(pathForTurn, cachedTurnType, pathTransform);
|
||||||
|
}
|
||||||
canvas.drawPath(pathForTurn, paintRouteDirection);
|
canvas.drawPath(pathForTurn, paintRouteDirection);
|
||||||
canvas.drawPath(pathForTurn, paintBlack);
|
canvas.drawPath(pathForTurn, paintBlack);
|
||||||
canvas.drawText(MapUtils.getFormattedDistance(d),
|
canvas.drawText(MapUtils.getFormattedDistance(d), boundsForMiniRoute.left + 10, boundsForMiniRoute.bottom - 9,
|
||||||
boundsForMiniRoute.left + 10, boundsForMiniRoute.bottom - 9, paintBlack);
|
paintBlack);
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// TEST
|
|
||||||
// canvas.drawRoundRect(boundsForMiniRoute, 3, 3, paintAlphaGray);
|
|
||||||
// canvas.drawRoundRect(boundsForMiniRoute, 3, 3, paintBlack);
|
|
||||||
// calcTurnPath(TurnType.C);
|
|
||||||
// canvas.drawPath(pathForTurn, paintRouteDirection);
|
|
||||||
// canvas.drawPath(pathForTurn, paintBlack);
|
|
||||||
// canvas.drawText(MapUtils.getFormattedDistance(300),
|
|
||||||
// boundsForMiniRoute.left + 10, boundsForMiniRoute.bottom - 9, paintBlack);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean followingMode = routeLayer.getHelper().isFollowingMode();
|
||||||
int time = routeLayer.getHelper().getLeftTime() * 1000;
|
int time = routeLayer.getHelper().getLeftTime() * 1000;
|
||||||
if(time == 0){
|
if(time == 0){
|
||||||
cachedLeftTime = 0;
|
cachedLeftTime = 0;
|
||||||
cachedLeftTimeString = null;
|
cachedLeftTimeString = null;
|
||||||
} else {
|
} else {
|
||||||
if(Math.abs(System.currentTimeMillis() + time - cachedLeftTime) > 30000){
|
long toFindTime = time;
|
||||||
cachedLeftTime = System.currentTimeMillis() + time;
|
if(followingMode){
|
||||||
cachedLeftTimeString = DateFormat.format("k:mm", cachedLeftTime).toString(); //$NON-NLS-1$
|
toFindTime += System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
if(Math.abs(toFindTime - cachedLeftTime) > 30000){
|
||||||
|
cachedLeftTime = toFindTime;
|
||||||
|
cachedLeftTimeString = DateFormat.format("kk:mm", cachedLeftTime).toString(); //$NON-NLS-1$
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(cachedLeftTimeString != null) {
|
if(cachedLeftTimeString != null) {
|
||||||
|
@ -284,9 +272,110 @@ public class MapInfoLayer implements OsmandMapLayer {
|
||||||
canvas.drawText(cachedLeftTimeString, boundsForLeftTime.left + 5, boundsForLeftTime.bottom - 9, paintBlack);
|
canvas.drawText(cachedLeftTimeString, boundsForLeftTime.left + 5, boundsForLeftTime.bottom - 9, paintBlack);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// TEST
|
||||||
|
// TODO remove
|
||||||
|
// canvas.drawRoundRect(boundsForMiniRoute, 3, 3, paintAlphaGray);
|
||||||
|
// canvas.drawRoundRect(boundsForMiniRoute, 3, 3, paintBlack);
|
||||||
|
// canvas.drawPath(pathForTurn, paintRouteDirection);
|
||||||
|
// canvas.drawPath(pathForTurn, paintBlack);
|
||||||
|
// canvas.drawText(MapUtils.getFormattedDistance(300),
|
||||||
|
// boundsForMiniRoute.left + 15, boundsForMiniRoute.bottom - 9, paintBlack);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void calcTurnPath(Path pathForTurn, TurnType turnType, Matrix transform) {
|
||||||
|
pathForTurn.reset();
|
||||||
|
// draw path 96x96
|
||||||
|
int c = 48;
|
||||||
|
int w = 16;
|
||||||
|
pathForTurn.moveTo(c, 94);
|
||||||
|
float sarrowL = 30; // side of arrow
|
||||||
|
float harrowL = (float) Math.sqrt(2) * sarrowL; // hypotenuse of arrow
|
||||||
|
float spartArrowL = (float) ((sarrowL - w / Math.sqrt(2)) / 2);
|
||||||
|
float hpartArrowL = (float) (harrowL - w) / 2;
|
||||||
|
|
||||||
|
if (turnType == TurnType.C) {
|
||||||
|
int h = 65;
|
||||||
|
|
||||||
|
pathForTurn.rMoveTo(w / 2, 0);
|
||||||
|
pathForTurn.rLineTo(0, -h);
|
||||||
|
pathForTurn.rLineTo(hpartArrowL, 0);
|
||||||
|
pathForTurn.rLineTo(-harrowL / 2, -harrowL / 2); // center
|
||||||
|
pathForTurn.rLineTo(-harrowL / 2, harrowL / 2);
|
||||||
|
pathForTurn.rLineTo(hpartArrowL, 0);
|
||||||
|
pathForTurn.rLineTo(0, h);
|
||||||
|
} else if (turnType == TurnType.TR || turnType == TurnType.TL) {
|
||||||
|
int b = turnType == TurnType.TR ? 1 : -1;
|
||||||
|
int h = 36;
|
||||||
|
float quadShiftX = 22;
|
||||||
|
float quadShiftY = 22;
|
||||||
|
|
||||||
|
pathForTurn.rMoveTo(-b * 8, 0);
|
||||||
|
pathForTurn.rLineTo(0, -h);
|
||||||
|
pathForTurn.rQuadTo(0, -quadShiftY, b * quadShiftX, -quadShiftY);
|
||||||
|
pathForTurn.rLineTo(0, hpartArrowL);
|
||||||
|
pathForTurn.rLineTo(b * harrowL / 2, -harrowL / 2); // center
|
||||||
|
pathForTurn.rLineTo(-b * harrowL / 2, -harrowL / 2);
|
||||||
|
pathForTurn.rLineTo(0, hpartArrowL);
|
||||||
|
pathForTurn.rQuadTo(-b * (quadShiftX + w), 0, -b * (quadShiftX + w), quadShiftY + w);
|
||||||
|
pathForTurn.rLineTo(0, h);
|
||||||
|
} else if (turnType == TurnType.TSLR || turnType == TurnType.TSLL) {
|
||||||
|
int b = turnType == TurnType.TSLR ? 1 : -1;
|
||||||
|
int h = 40;
|
||||||
|
int quadShiftY = 22;
|
||||||
|
float quadShiftX = (float) (quadShiftY / (1 + Math.sqrt(2)));
|
||||||
|
float nQuadShiftX = (sarrowL - 2 * spartArrowL) - quadShiftX - w;
|
||||||
|
float nQuadShifty = quadShiftY + (sarrowL - 2 * spartArrowL);
|
||||||
|
|
||||||
|
pathForTurn.rMoveTo(-b * 4, 0);
|
||||||
|
pathForTurn.rLineTo(0, -h /* + partArrowL */);
|
||||||
|
pathForTurn.rQuadTo(0, -quadShiftY + quadShiftX /*- partArrowL*/, b * quadShiftX, -quadShiftY /*- partArrowL*/);
|
||||||
|
pathForTurn.rLineTo(b * spartArrowL, spartArrowL);
|
||||||
|
pathForTurn.rLineTo(0, -sarrowL); // center
|
||||||
|
pathForTurn.rLineTo(-b * sarrowL, 0);
|
||||||
|
pathForTurn.rLineTo(b * spartArrowL, spartArrowL);
|
||||||
|
pathForTurn.rQuadTo(b * nQuadShiftX, -nQuadShiftX, b * nQuadShiftX, nQuadShifty);
|
||||||
|
pathForTurn.rLineTo(0, h);
|
||||||
|
} else if (turnType == TurnType.TSHR || turnType == TurnType.TSHL) {
|
||||||
|
int b = turnType == TurnType.TSHR ? 1 : -1;
|
||||||
|
int h = 45;
|
||||||
|
float quadShiftX = 22;
|
||||||
|
float quadShiftY = -(float) (quadShiftX / (1 + Math.sqrt(2)));
|
||||||
|
float nQuadShiftX = -(sarrowL - 2 * spartArrowL) - quadShiftX - w;
|
||||||
|
float nQuadShiftY = -quadShiftY + (sarrowL - 2 * spartArrowL);
|
||||||
|
|
||||||
|
pathForTurn.rMoveTo(-b * 8, 0);
|
||||||
|
pathForTurn.rLineTo(0, -h);
|
||||||
|
pathForTurn.rQuadTo(0, -(quadShiftX - quadShiftY), b * quadShiftX, quadShiftY);
|
||||||
|
pathForTurn.rLineTo(-b * spartArrowL, spartArrowL);
|
||||||
|
pathForTurn.rLineTo(b * sarrowL, 0); // center
|
||||||
|
pathForTurn.rLineTo(0, -sarrowL);
|
||||||
|
pathForTurn.rLineTo(-b * spartArrowL, spartArrowL);
|
||||||
|
pathForTurn.rCubicTo(b * nQuadShiftX / 2, nQuadShiftX / 2, b * nQuadShiftX, nQuadShiftX / 2, b * nQuadShiftX, nQuadShiftY);
|
||||||
|
pathForTurn.rLineTo(0, h);
|
||||||
|
} else if(turnType == TurnType.TU){
|
||||||
|
int h = 54;
|
||||||
|
float quadShiftX = 13;
|
||||||
|
float quadShiftY = 13;
|
||||||
|
|
||||||
|
pathForTurn.rMoveTo(28, 0);
|
||||||
|
pathForTurn.rLineTo(0, -h);
|
||||||
|
pathForTurn.rQuadTo(0, -(quadShiftY+w), -(quadShiftX+w), -(quadShiftY+w));
|
||||||
|
pathForTurn.rQuadTo(-(quadShiftX+w), 0, -(quadShiftX+w), (quadShiftY+w));
|
||||||
|
pathForTurn.rLineTo(-hpartArrowL, 0);
|
||||||
|
pathForTurn.rLineTo(harrowL/2, harrowL/2); // center
|
||||||
|
pathForTurn.rLineTo(harrowL/2, -harrowL/2);
|
||||||
|
pathForTurn.rLineTo(-hpartArrowL, 0);
|
||||||
|
pathForTurn.rQuadTo(0, -quadShiftX, quadShiftX, -quadShiftY);
|
||||||
|
pathForTurn.rQuadTo(quadShiftX, 0, quadShiftX, quadShiftY);
|
||||||
|
pathForTurn.rLineTo(0, h);
|
||||||
|
} else if(turnType != null && turnType.isExit()){
|
||||||
|
// TODO !!! & check how it works
|
||||||
|
}
|
||||||
|
pathForTurn.close();
|
||||||
|
pathForTurn.transform(transform);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void destroyLayer() {
|
public void destroyLayer() {
|
||||||
|
|
|
@ -66,7 +66,7 @@ public class RouteLayer implements OsmandMapLayer {
|
||||||
view.isPointOnTheRotatedMap(helper.getCurrentLocation().getLatitude(), helper.getCurrentLocation().getLongitude())){
|
view.isPointOnTheRotatedMap(helper.getCurrentLocation().getLatitude(), helper.getCurrentLocation().getLongitude())){
|
||||||
boundsRect = new Rect(-w / 2, -h, 3 * w / 2, h);
|
boundsRect = new Rect(-w / 2, -h, 3 * w / 2, h);
|
||||||
} else {
|
} else {
|
||||||
boundsRect = new Rect(0, -h, w, h);
|
boundsRect = new Rect(0, 0, w, h);
|
||||||
}
|
}
|
||||||
view.calculateTileRectangle(boundsRect, view.getCenterPointX(), view.getCenterPointY(), view.getXTile(), view.getYTile(),
|
view.calculateTileRectangle(boundsRect, view.getCenterPointX(), view.getCenterPointY(), view.getXTile(), view.getYTile(),
|
||||||
tileRect);
|
tileRect);
|
||||||
|
|
Loading…
Reference in a new issue