initial commit of BRouter Version 0.98
This commit is contained in:
parent
e4ae2b37d3
commit
91e62f1164
120 changed files with 15382 additions and 0 deletions
|
|
@ -0,0 +1,375 @@
|
|||
package btools.routingapp;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.hardware.Sensor;
|
||||
import android.hardware.SensorEvent;
|
||||
import android.hardware.SensorManager;
|
||||
import android.os.Bundle;
|
||||
import android.os.PowerManager;
|
||||
import android.os.PowerManager.WakeLock;
|
||||
import android.speech.tts.TextToSpeech.OnInitListener;
|
||||
import android.view.Display;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.EditText;
|
||||
import btools.router.OsmNodeNamed;
|
||||
|
||||
public class BRouterActivity extends Activity implements OnInitListener {
|
||||
|
||||
private static final int DIALOG_SELECTPROFILE_ID = 1;
|
||||
private static final int DIALOG_EXCEPTION_ID = 2;
|
||||
private static final int DIALOG_WARNEXPIRY_ID = 3;
|
||||
private static final int DIALOG_TEXTENTRY_ID = 4;
|
||||
private static final int DIALOG_VIASELECT_ID = 5;
|
||||
private static final int DIALOG_NOGOSELECT_ID = 6;
|
||||
private static final int DIALOG_SHOWRESULT_ID = 7;
|
||||
private static final int DIALOG_ROUTINGMODES_ID = 8;
|
||||
private static final int DIALOG_MODECONFIGOVERVIEW_ID = 9;
|
||||
private static final int DIALOG_PICKWAYPOINT_ID = 10;
|
||||
|
||||
private BRouterView mBRouterView;
|
||||
private PowerManager mPowerManager;
|
||||
private WindowManager mWindowManager;
|
||||
private Display mDisplay;
|
||||
private WakeLock mWakeLock;
|
||||
|
||||
/** Called when the activity is first created. */
|
||||
@Override
|
||||
@SuppressWarnings("deprecation")
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
// Get an instance of the PowerManager
|
||||
mPowerManager = (PowerManager) getSystemService(POWER_SERVICE);
|
||||
|
||||
// Get an instance of the WindowManager
|
||||
mWindowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
|
||||
mDisplay = mWindowManager.getDefaultDisplay();
|
||||
|
||||
// Create a bright wake lock
|
||||
mWakeLock = mPowerManager.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, getClass()
|
||||
.getName());
|
||||
|
||||
// instantiate our simulation view and set it as the activity's content
|
||||
mBRouterView = new BRouterView(this);
|
||||
setContentView(mBRouterView);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("deprecation")
|
||||
protected Dialog onCreateDialog(int id)
|
||||
{
|
||||
AlertDialog.Builder builder;
|
||||
switch(id)
|
||||
{
|
||||
case DIALOG_SELECTPROFILE_ID:
|
||||
builder = new AlertDialog.Builder(this);
|
||||
builder.setTitle("Select a routing profile");
|
||||
builder.setItems(availableProfiles, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int item) {
|
||||
selectedProfile = availableProfiles[item];
|
||||
mBRouterView.startProcessing(selectedProfile);
|
||||
}
|
||||
});
|
||||
return builder.create();
|
||||
case DIALOG_ROUTINGMODES_ID:
|
||||
builder = new AlertDialog.Builder(this);
|
||||
builder.setTitle( message );
|
||||
builder.setMultiChoiceItems(routingModes, routingModesChecked, new DialogInterface.OnMultiChoiceClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which,
|
||||
boolean isChecked) {
|
||||
routingModesChecked[which] = isChecked;
|
||||
}
|
||||
});
|
||||
builder.setPositiveButton("Ok", new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int whichButton) {
|
||||
mBRouterView.configureService(routingModes,routingModesChecked);
|
||||
}
|
||||
});
|
||||
return builder.create();
|
||||
case DIALOG_EXCEPTION_ID:
|
||||
builder = new AlertDialog.Builder(this);
|
||||
builder.setTitle( "An Error occured" )
|
||||
.setMessage( errorMessage )
|
||||
.setPositiveButton( "OK", new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
mBRouterView.continueProcessing();
|
||||
}
|
||||
});
|
||||
return builder.create();
|
||||
case DIALOG_WARNEXPIRY_ID:
|
||||
builder = new AlertDialog.Builder(this);
|
||||
builder.setMessage( errorMessage )
|
||||
.setPositiveButton( "OK", new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
mBRouterView.startProcessing(selectedProfile);
|
||||
}
|
||||
});
|
||||
return builder.create();
|
||||
case DIALOG_TEXTENTRY_ID:
|
||||
builder = new AlertDialog.Builder(this);
|
||||
builder.setTitle("Enter SDCARD base dir:");
|
||||
builder.setMessage(message);
|
||||
final EditText input = new EditText(this);
|
||||
input.setText( defaultbasedir );
|
||||
builder.setView(input);
|
||||
builder.setPositiveButton("Ok", new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int whichButton) {
|
||||
String basedir = input.getText().toString();
|
||||
mBRouterView.startSetup(basedir, true );
|
||||
}
|
||||
});
|
||||
return builder.create();
|
||||
case DIALOG_VIASELECT_ID:
|
||||
builder = new AlertDialog.Builder(this);
|
||||
builder.setTitle("Check VIA Selection:");
|
||||
builder.setMultiChoiceItems(availableVias, getCheckedBooleanArray( availableVias.length ),
|
||||
new DialogInterface.OnMultiChoiceClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which,
|
||||
boolean isChecked) {
|
||||
if (isChecked)
|
||||
{
|
||||
selectedVias.add(availableVias[which]);
|
||||
}
|
||||
else
|
||||
{
|
||||
selectedVias.remove(availableVias[which]);
|
||||
}
|
||||
}
|
||||
});
|
||||
builder.setPositiveButton("Ok", new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int whichButton) {
|
||||
mBRouterView.updateViaList( selectedVias );
|
||||
mBRouterView.startProcessing(selectedProfile);
|
||||
}
|
||||
});
|
||||
return builder.create();
|
||||
case DIALOG_NOGOSELECT_ID:
|
||||
builder = new AlertDialog.Builder(this);
|
||||
builder.setTitle("Check NoGo Selection:");
|
||||
String[] nogoNames = new String[nogoList.size()];
|
||||
for( int i=0; i<nogoList.size(); i++ ) nogoNames[i] = nogoList.get(i).name;
|
||||
final boolean[] nogoEnabled = getCheckedBooleanArray(nogoList.size());
|
||||
builder.setMultiChoiceItems(nogoNames, getCheckedBooleanArray( nogoNames.length ),
|
||||
new DialogInterface.OnMultiChoiceClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which, boolean isChecked) {
|
||||
nogoEnabled[which] = isChecked;
|
||||
}
|
||||
});
|
||||
builder.setPositiveButton("Ok", new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int whichButton) {
|
||||
mBRouterView.updateNogoList( nogoEnabled );
|
||||
mBRouterView.startProcessing(selectedProfile);
|
||||
}
|
||||
});
|
||||
return builder.create();
|
||||
case DIALOG_SHOWRESULT_ID:
|
||||
String leftLabel = wpCount < 0 ? "Exit" : ( wpCount == 0 ? "Select from" : "Select to/via" );
|
||||
String rightLabel = wpCount < 2 ? "Server-Mode" : "Calc Route";
|
||||
builder = new AlertDialog.Builder(this);
|
||||
builder.setTitle( title )
|
||||
.setMessage( errorMessage )
|
||||
.setPositiveButton( leftLabel, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
if ( wpCount < 0 ) finish();
|
||||
else mBRouterView.pickWaypoints();
|
||||
}
|
||||
})
|
||||
.setNegativeButton( rightLabel, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
if ( wpCount < 2 ) mBRouterView.startConfigureService();
|
||||
else
|
||||
{
|
||||
mBRouterView.finishWaypointSelection();
|
||||
mBRouterView.startProcessing(selectedProfile);
|
||||
}
|
||||
}
|
||||
});
|
||||
return builder.create();
|
||||
case DIALOG_MODECONFIGOVERVIEW_ID:
|
||||
builder = new AlertDialog.Builder(this);
|
||||
builder.setTitle( "Success" )
|
||||
.setMessage( message )
|
||||
.setPositiveButton( "Exit", new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
finish();
|
||||
}
|
||||
});
|
||||
return builder.create();
|
||||
case DIALOG_PICKWAYPOINT_ID:
|
||||
builder = new AlertDialog.Builder(this);
|
||||
builder.setTitle( wpCount > 0 ? "Select to/via" : "Select from" );
|
||||
builder.setItems(availableWaypoints, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int item) {
|
||||
mBRouterView.updateWaypointList( availableWaypoints[item] );
|
||||
mBRouterView.startProcessing(selectedProfile);
|
||||
}
|
||||
});
|
||||
return builder.create();
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private boolean[] getCheckedBooleanArray( int size )
|
||||
{
|
||||
boolean[] checked = new boolean[size];
|
||||
for( int i=0; i<checked.length; i++ ) checked[i] = true;
|
||||
return checked;
|
||||
}
|
||||
|
||||
private String[] availableProfiles;
|
||||
private String selectedProfile = null;
|
||||
|
||||
private String[] availableWaypoints;
|
||||
private String selectedWaypoint = null;
|
||||
|
||||
private String[] routingModes;
|
||||
private boolean[] routingModesChecked;
|
||||
|
||||
private String defaultbasedir = null;
|
||||
private String message = null;
|
||||
|
||||
private String[] availableVias;
|
||||
private Set<String> selectedVias;
|
||||
|
||||
private List<OsmNodeNamed> nogoList;
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public void selectProfile( String[] items )
|
||||
{
|
||||
availableProfiles = items;
|
||||
showDialog( DIALOG_SELECTPROFILE_ID );
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public void selectRoutingModes( String[] modes, boolean[] modesChecked, String message )
|
||||
{
|
||||
routingModes = modes;
|
||||
routingModesChecked = modesChecked;
|
||||
this.message = message;
|
||||
showDialog( DIALOG_ROUTINGMODES_ID );
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public void showModeConfigOverview( String message )
|
||||
{
|
||||
this.message = message;
|
||||
showDialog( DIALOG_MODECONFIGOVERVIEW_ID );
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public void selectBasedir( String defaultBasedir, String message )
|
||||
{
|
||||
this.defaultbasedir = defaultBasedir;
|
||||
this.message = message;
|
||||
showDialog( DIALOG_TEXTENTRY_ID );
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public void selectVias( String[] items )
|
||||
{
|
||||
availableVias = items;
|
||||
selectedVias = new HashSet<String>(availableVias.length);
|
||||
for( String via : items ) selectedVias.add( via );
|
||||
showDialog( DIALOG_VIASELECT_ID );
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public void selectWaypoint( String[] items )
|
||||
{
|
||||
availableWaypoints = items;
|
||||
showNewDialog( DIALOG_PICKWAYPOINT_ID );
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public void selectNogos( List<OsmNodeNamed> nogoList )
|
||||
{
|
||||
this.nogoList = nogoList;
|
||||
showDialog( DIALOG_NOGOSELECT_ID );
|
||||
}
|
||||
|
||||
private Set<Integer> dialogIds = new HashSet<Integer>();
|
||||
|
||||
private void showNewDialog( int id )
|
||||
{
|
||||
if ( dialogIds.contains( new Integer( id ) ) )
|
||||
{
|
||||
removeDialog( id );
|
||||
}
|
||||
dialogIds.add( new Integer( id ) );
|
||||
showDialog( id );
|
||||
}
|
||||
|
||||
private String errorMessage;
|
||||
private String title;
|
||||
private int wpCount;
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public void showErrorMessage( String msg )
|
||||
{
|
||||
errorMessage = msg;
|
||||
showNewDialog( DIALOG_EXCEPTION_ID );
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public void showResultMessage( String title, String msg, int wpCount )
|
||||
{
|
||||
errorMessage = msg;
|
||||
this.title = title;
|
||||
this.wpCount = wpCount;
|
||||
showNewDialog( DIALOG_SHOWRESULT_ID );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
/*
|
||||
* when the activity is resumed, we acquire a wake-lock so that the
|
||||
* screen stays on, since the user will likely not be fiddling with the
|
||||
* screen or buttons.
|
||||
*/
|
||||
mWakeLock.acquire();
|
||||
|
||||
// Start the simulation
|
||||
mBRouterView.startSimulation();
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
/*
|
||||
* When the activity is paused, we make sure to stop the simulation,
|
||||
* release our sensor resources and wake locks
|
||||
*/
|
||||
|
||||
// Stop the simulation
|
||||
mBRouterView.stopSimulation();
|
||||
|
||||
// and release our wake-lock
|
||||
mWakeLock.release();
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInit(int i)
|
||||
{
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,146 @@
|
|||
package btools.routingapp;
|
||||
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.FileReader;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import android.app.Service;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
import btools.router.OsmNodeNamed;
|
||||
|
||||
public class BRouterService extends Service
|
||||
{
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent arg0) {
|
||||
Log.d(getClass().getSimpleName(), "onBind()");
|
||||
return myBRouterServiceStub;
|
||||
}
|
||||
|
||||
private IBRouterService.Stub myBRouterServiceStub = new IBRouterService.Stub()
|
||||
{
|
||||
@Override
|
||||
public String getTrackFromParams(Bundle params) throws RemoteException
|
||||
{
|
||||
BRouterWorker worker = new BRouterWorker();
|
||||
|
||||
// get base dir from private file
|
||||
String baseDir = null;
|
||||
InputStream configInput = null;
|
||||
try
|
||||
{
|
||||
configInput = openFileInput( "config.dat" );
|
||||
BufferedReader br = new BufferedReader( new InputStreamReader (configInput ) );
|
||||
baseDir = br.readLine();
|
||||
}
|
||||
catch( Exception e ) {}
|
||||
finally
|
||||
{
|
||||
if ( configInput != null ) try { configInput.close(); } catch( Exception ee ) {}
|
||||
}
|
||||
|
||||
String fast = params.getString( "fast" );
|
||||
boolean isFast = "1".equals( fast ) || "true".equals( fast ) || "yes".equals( fast );
|
||||
String mode_key = params.getString( "v" ) + "_" + (isFast ? "fast" : "short");
|
||||
|
||||
boolean configFound = false;
|
||||
|
||||
BufferedReader br = null;
|
||||
try
|
||||
{
|
||||
String modesFile = baseDir + "/brouter/modes/serviceconfig.dat";
|
||||
br = new BufferedReader( new FileReader (modesFile ) );
|
||||
worker.segmentDir = baseDir + "/brouter/segments2";
|
||||
for(;;)
|
||||
{
|
||||
String line = br.readLine();
|
||||
if ( line == null ) break;
|
||||
ServiceModeConfig smc = new ServiceModeConfig( line );
|
||||
if ( !smc.mode.equals( mode_key ) ) continue;
|
||||
worker.profilePath = baseDir + "/brouter/profiles2/" + smc.profile + ".brf";
|
||||
worker.rawTrackPath = baseDir + "/brouter/modes/" + mode_key + "_rawtrack.dat";
|
||||
|
||||
CoordinateReader cor = CoordinateReader.obtainValidReader( baseDir );
|
||||
worker.nogoList = new ArrayList<OsmNodeNamed>();
|
||||
// veto nogos by profiles veto list
|
||||
for(OsmNodeNamed nogo : cor.nogopoints )
|
||||
{
|
||||
if ( !smc.nogoVetos.contains( nogo.ilon + "," + nogo.ilat ) )
|
||||
{
|
||||
worker.nogoList.add( nogo );
|
||||
}
|
||||
}
|
||||
configFound = true;
|
||||
}
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
return "no brouter service config found, mode " + mode_key;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if ( br != null ) try { br.close(); } catch( Exception ee ) {}
|
||||
}
|
||||
|
||||
if ( !configFound )
|
||||
{
|
||||
return "no brouter service config found for mode " + mode_key;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return worker.getTrackFromParams(params);
|
||||
}
|
||||
catch( IllegalArgumentException iae )
|
||||
{
|
||||
return iae.getMessage();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public void onCreate()
|
||||
{
|
||||
super.onCreate();
|
||||
Log.d(getClass().getSimpleName(),"onCreate()");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy()
|
||||
{
|
||||
super.onDestroy();
|
||||
Log.d(getClass().getSimpleName(),"onDestroy()");
|
||||
}
|
||||
|
||||
|
||||
// This is the old onStart method that will be called on the pre-2.0
|
||||
// platform. On 2.0 or later we override onStartCommand() so this
|
||||
// method will not be called.
|
||||
@Override
|
||||
@SuppressWarnings("deprecation")
|
||||
public void onStart(Intent intent, int startId)
|
||||
{
|
||||
Log.d(getClass().getSimpleName(), "onStart()");
|
||||
handleStart(intent, startId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId)
|
||||
{
|
||||
handleStart(intent, startId);
|
||||
return START_STICKY;
|
||||
}
|
||||
|
||||
void handleStart(Intent intent, int startId)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,760 @@
|
|||
package btools.routingapp;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileReader;
|
||||
import java.io.FileWriter;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.os.Environment;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.view.View;
|
||||
import android.widget.Toast;
|
||||
import btools.expressions.BExpressionContext;
|
||||
import btools.mapaccess.OsmNode;
|
||||
import btools.router.OsmNodeNamed;
|
||||
import btools.router.OsmTrack;
|
||||
import btools.router.RoutingContext;
|
||||
import btools.router.RoutingEngine;
|
||||
|
||||
public class BRouterView extends View
|
||||
{
|
||||
RoutingEngine cr;
|
||||
private int imgw;
|
||||
private int imgh;
|
||||
|
||||
private int centerLon;
|
||||
private int centerLat;
|
||||
private double scaleLon;
|
||||
private double scaleLat;
|
||||
private List<OsmNodeNamed> wpList;
|
||||
private List<OsmNodeNamed> nogoList;
|
||||
private List<OsmNodeNamed> nogoVetoList;
|
||||
private OsmTrack rawTrack;
|
||||
|
||||
private String modesDir;
|
||||
private String tracksDir;
|
||||
private String segmentDir;
|
||||
private String profileDir;
|
||||
private String profilePath;
|
||||
private String profileName;
|
||||
private String sourceHint;
|
||||
private boolean waitingForSelection = false;
|
||||
|
||||
private boolean needsViaSelection;
|
||||
private boolean needsNogoSelection;
|
||||
private boolean needsWaypointSelection;
|
||||
|
||||
private long lastDataTime = System.currentTimeMillis();
|
||||
|
||||
private CoordinateReader cor;
|
||||
|
||||
private int[] imgPixels;
|
||||
|
||||
public void startSimulation() {
|
||||
}
|
||||
|
||||
public void stopSimulation() {
|
||||
if ( cr != null ) cr.terminate();
|
||||
}
|
||||
|
||||
public BRouterView(Context context) {
|
||||
super(context);
|
||||
|
||||
DisplayMetrics metrics = new DisplayMetrics();
|
||||
((Activity)getContext()).getWindowManager().getDefaultDisplay().getMetrics(metrics);
|
||||
imgw = metrics.widthPixels;
|
||||
imgh = metrics.heightPixels;
|
||||
|
||||
// get base dir from private file
|
||||
String baseDir = null;
|
||||
InputStream configInput = null;
|
||||
try
|
||||
{
|
||||
configInput = getContext().openFileInput( "config.dat" );
|
||||
BufferedReader br = new BufferedReader( new InputStreamReader (configInput ) );
|
||||
baseDir = br.readLine();
|
||||
}
|
||||
catch( Exception e ) {}
|
||||
finally
|
||||
{
|
||||
if ( configInput != null ) try { configInput.close(); } catch( Exception ee ) {}
|
||||
}
|
||||
// check if valid
|
||||
boolean bdValid = false;
|
||||
if ( baseDir != null )
|
||||
{
|
||||
File bd = new File( baseDir );
|
||||
bdValid = bd.isDirectory();
|
||||
File brd = new File( bd, "brouter" );
|
||||
if ( brd.isDirectory() )
|
||||
{
|
||||
startSetup( baseDir, false );
|
||||
return;
|
||||
}
|
||||
}
|
||||
String message = baseDir == null ?
|
||||
"(no basedir configured previously)" :
|
||||
"(previous basedir " + baseDir +
|
||||
( bdValid ? " does not contain 'brouter' subfolder)"
|
||||
: " is not valid)" );
|
||||
|
||||
((BRouterActivity)getContext()).selectBasedir( guessBaseDir(), message );
|
||||
waitingForSelection = true;
|
||||
}
|
||||
|
||||
public void startSetup( String baseDir, boolean storeBasedir )
|
||||
{
|
||||
File fbd = new File( baseDir );
|
||||
if ( !fbd.isDirectory() )
|
||||
{
|
||||
throw new IllegalArgumentException( "Base-directory " + baseDir + " is not a directory " );
|
||||
}
|
||||
String basedir = fbd.getAbsolutePath();
|
||||
|
||||
if ( storeBasedir )
|
||||
{
|
||||
BufferedWriter bw = null;
|
||||
try
|
||||
{
|
||||
OutputStream configOutput = getContext().openFileOutput( "config.dat", Context.MODE_PRIVATE );
|
||||
bw = new BufferedWriter( new OutputStreamWriter (configOutput ) );
|
||||
bw.write( baseDir );
|
||||
bw.write( '\n' );
|
||||
}
|
||||
catch( Exception e ) {}
|
||||
finally
|
||||
{
|
||||
if ( bw != null ) try { bw.close(); } catch( Exception ee ) {}
|
||||
}
|
||||
}
|
||||
|
||||
cor = null;
|
||||
try
|
||||
{
|
||||
// create missing directories
|
||||
assertDirectoryExists( "project directory", basedir + "/brouter" );
|
||||
segmentDir = basedir + "/brouter/segments2";
|
||||
assertDirectoryExists( "map directory", segmentDir );
|
||||
profileDir = basedir + "/brouter/profiles2";
|
||||
assertDirectoryExists( "profile directory", profileDir );
|
||||
modesDir = basedir + "/brouter/modes";
|
||||
assertDirectoryExists( "modes directory", modesDir );
|
||||
|
||||
cor = CoordinateReader.obtainValidReader( basedir );
|
||||
wpList = cor.waypoints;
|
||||
nogoList = cor.nogopoints;
|
||||
nogoVetoList = new ArrayList<OsmNodeNamed>();
|
||||
|
||||
sourceHint = "(coordinate-source: " + cor.basedir + cor.rootdir + ")";
|
||||
|
||||
needsViaSelection = wpList.size() > 2;
|
||||
needsNogoSelection = nogoList.size() > 0;
|
||||
needsWaypointSelection = wpList.size() == 0;
|
||||
|
||||
if ( cor.tracksdir != null )
|
||||
{
|
||||
tracksDir = cor.basedir + cor.tracksdir;
|
||||
assertDirectoryExists( "track directory", tracksDir );
|
||||
|
||||
// output redirect: look for a pointerfile in tracksdir
|
||||
File tracksDirPointer = new File( tracksDir + "/brouter.redirect" );
|
||||
if ( tracksDirPointer.isFile() )
|
||||
{
|
||||
tracksDir = readSingleLineFile( tracksDirPointer );
|
||||
if ( tracksDir == null ) throw new IllegalArgumentException( "redirect pointer file is empty: " + tracksDirPointer );
|
||||
if ( !(new File( tracksDir ).isDirectory()) ) throw new IllegalArgumentException(
|
||||
"redirect pointer file " + tracksDirPointer + " does not point to a directory: " + tracksDir );
|
||||
}
|
||||
}
|
||||
|
||||
boolean segmentFound = false;
|
||||
String[] fileNames = new File( segmentDir ).list();
|
||||
for( String fileName : fileNames )
|
||||
{
|
||||
if ( fileName.endsWith( ".rd5" ) ) segmentFound = true;
|
||||
}
|
||||
File carSubset = new File( segmentDir, "carsubset" );
|
||||
if ( carSubset.isDirectory() )
|
||||
{
|
||||
fileNames = carSubset.list();
|
||||
for( String fileName : fileNames )
|
||||
{
|
||||
if ( fileName.endsWith( ".cd5" ) ) segmentFound = true;
|
||||
}
|
||||
}
|
||||
if ( !segmentFound )
|
||||
{
|
||||
throw new IllegalArgumentException( "The segments-directory " + segmentDir
|
||||
+ " contains no routing data files (*.rd5)."
|
||||
+ " see www.dr-brenschede.de/brouter for setup instructions." );
|
||||
}
|
||||
|
||||
fileNames = new File( profileDir ).list();
|
||||
ArrayList<String> profiles = new ArrayList<String>();
|
||||
|
||||
boolean lookupsFound = false;
|
||||
for( String fileName : fileNames )
|
||||
{
|
||||
if ( fileName.endsWith( ".brf" ) )
|
||||
{
|
||||
profiles.add( fileName.substring( 0, fileName.length()-4 ) );
|
||||
}
|
||||
if ( fileName.equals( "lookups.dat" ) ) lookupsFound = true;
|
||||
}
|
||||
if ( !lookupsFound )
|
||||
{
|
||||
throw new IllegalArgumentException( "The profile-directory " + profileDir
|
||||
+ " does not contain the lookups.dat file."
|
||||
+ " see www.dr-brenschede.de/brouter for setup instructions." );
|
||||
}
|
||||
if ( profiles.size() == 0 )
|
||||
{
|
||||
throw new IllegalArgumentException( "The profile-directory " + profileDir
|
||||
+ " contains no routing profiles (*.brf)."
|
||||
+ " see www.dr-brenschede.de/brouter for setup instructions." );
|
||||
}
|
||||
((BRouterActivity)getContext()).selectProfile( profiles.toArray( new String[0]) );
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
String msg = e instanceof IllegalArgumentException
|
||||
? e.getMessage() + ( cor == null ? "" : " (coordinate-source: " + cor.basedir + cor.rootdir + ")" )
|
||||
: e.toString();
|
||||
((BRouterActivity)getContext()).showErrorMessage( msg );
|
||||
}
|
||||
waitingForSelection = true;
|
||||
}
|
||||
|
||||
public void continueProcessing()
|
||||
{
|
||||
waitingForSelection = false;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public void updateViaList( Set<String> selectedVias )
|
||||
{
|
||||
ArrayList<OsmNodeNamed> filtered = new ArrayList<OsmNodeNamed>(wpList.size());
|
||||
for( OsmNodeNamed n : wpList )
|
||||
{
|
||||
String name = n.name;
|
||||
if ( "from".equals( name ) || "to".equals(name) || selectedVias.contains( name ) )
|
||||
filtered.add( n );
|
||||
}
|
||||
wpList = filtered;
|
||||
}
|
||||
|
||||
public void updateNogoList( boolean[] enabled )
|
||||
{
|
||||
for( int i=nogoList.size()-1; i >= 0; i-- )
|
||||
{
|
||||
if ( !enabled[i] )
|
||||
{
|
||||
nogoVetoList.add( nogoList.get(i) );
|
||||
nogoList.remove( i );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void pickWaypoints()
|
||||
{
|
||||
String msg = null;
|
||||
|
||||
Map<String,OsmNodeNamed> allpoints = cor.allpoints;
|
||||
if ( allpoints == null )
|
||||
{
|
||||
allpoints = new TreeMap<String,OsmNodeNamed>();
|
||||
cor.allpoints = allpoints;
|
||||
try { cor.readFromTo(); } catch ( Exception e ) { msg = "Error reading waypoints: " + e.toString(); }
|
||||
if ( allpoints.size() < 2 ) msg = "coordinate source does not contain enough waypoints: " + allpoints.size();
|
||||
if ( allpoints.size() > 100 ) msg = "coordinate source contains too much waypoints: " + allpoints.size() + "(please use from/to/via names)";
|
||||
}
|
||||
if ( allpoints.size() < 1 ) msg = "no more wayoints available!";
|
||||
|
||||
if ( msg != null )
|
||||
{
|
||||
((BRouterActivity)getContext()).showErrorMessage( msg );
|
||||
}
|
||||
else
|
||||
{
|
||||
String[] wpts = new String[allpoints.size()];
|
||||
int i = 0;
|
||||
for( OsmNodeNamed wp : allpoints.values() ) wpts[i++] = wp.name;
|
||||
System.out.println( "calling selectWaypoint..." );
|
||||
((BRouterActivity)getContext()).selectWaypoint( wpts );
|
||||
}
|
||||
}
|
||||
|
||||
public void updateWaypointList( String waypoint )
|
||||
{
|
||||
wpList.add( cor.allpoints.get( waypoint ) );
|
||||
cor.allpoints.remove( waypoint );
|
||||
System.out.println( "updateWaypointList: " + waypoint + " wpList.size()=" + wpList.size() );
|
||||
}
|
||||
|
||||
public void finishWaypointSelection()
|
||||
{
|
||||
needsWaypointSelection = false;
|
||||
}
|
||||
|
||||
public void startProcessing( String profile )
|
||||
{
|
||||
profilePath = profileDir + "/" + profile + ".brf";
|
||||
profileName = profile;
|
||||
|
||||
if ( needsViaSelection )
|
||||
{
|
||||
needsViaSelection = false;
|
||||
String[] availableVias = new String[wpList.size()-2];
|
||||
for( int viaidx=0; viaidx<wpList.size()-2; viaidx++ )
|
||||
availableVias[viaidx] = wpList.get( viaidx+1 ).name;
|
||||
((BRouterActivity)getContext()).selectVias( availableVias );
|
||||
return;
|
||||
}
|
||||
|
||||
if ( needsNogoSelection )
|
||||
{
|
||||
needsNogoSelection = false;
|
||||
((BRouterActivity)getContext()).selectNogos( nogoList );
|
||||
return;
|
||||
}
|
||||
|
||||
if ( needsWaypointSelection )
|
||||
{
|
||||
String msg;
|
||||
if ( wpList.size() == 0 )
|
||||
{
|
||||
msg = "no from/to found\n" + sourceHint;
|
||||
}
|
||||
else
|
||||
{
|
||||
msg = "current waypoint selection:\n";
|
||||
for ( int i=0; i< wpList.size(); i++ ) msg += (i>0?"->" : "") + wpList.get(i).name;
|
||||
}
|
||||
((BRouterActivity)getContext()).showResultMessage( "Select Action", msg, wpList.size() );
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
waitingForSelection = false;
|
||||
|
||||
RoutingContext rc = new RoutingContext();
|
||||
|
||||
// TODO: TEST!
|
||||
// rc.rawTrackPath = "/mnt/sdcard/brouter/modes/bicycle_fast_rawtrack.dat";
|
||||
|
||||
rc.localFunction = profilePath;
|
||||
|
||||
int plain_distance = 0;
|
||||
int maxlon = Integer.MIN_VALUE;
|
||||
int minlon = Integer.MAX_VALUE;
|
||||
int maxlat = Integer.MIN_VALUE;
|
||||
int minlat = Integer.MAX_VALUE;
|
||||
|
||||
OsmNode prev = null;
|
||||
for( OsmNode n : wpList )
|
||||
{
|
||||
maxlon = n.ilon > maxlon ? n.ilon : maxlon;
|
||||
minlon = n.ilon < minlon ? n.ilon : minlon;
|
||||
maxlat = n.ilat > maxlat ? n.ilat : maxlat;
|
||||
minlat = n.ilat < minlat ? n.ilat : minlat;
|
||||
if ( prev != null )
|
||||
{
|
||||
plain_distance += n.calcDistance( prev );
|
||||
}
|
||||
prev = n;
|
||||
}
|
||||
toast( "Plain distance = " + plain_distance/1000. + " km" );
|
||||
|
||||
centerLon = (maxlon + minlon)/2;
|
||||
centerLat = (maxlat + minlat)/2;
|
||||
|
||||
double coslat = Math.cos( ((centerLat / 1000000.) - 90.) / 57.3 ) ;
|
||||
double difflon = maxlon - minlon;
|
||||
double difflat = maxlat - minlat;
|
||||
|
||||
scaleLon = imgw / (difflon*1.5);
|
||||
scaleLat = imgh / (difflat*1.5);
|
||||
if ( scaleLon < scaleLat*coslat ) scaleLat = scaleLon/coslat;
|
||||
else scaleLon = scaleLat*coslat;
|
||||
|
||||
startTime = System.currentTimeMillis();
|
||||
rc.prepareNogoPoints( nogoList );
|
||||
rc.nogopoints = nogoList;
|
||||
|
||||
cr = new RoutingEngine( tracksDir + "/brouter", null, segmentDir, wpList, rc );
|
||||
cr.start();
|
||||
invalidate();
|
||||
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
String msg = e instanceof IllegalArgumentException ? e.getMessage() : e.toString();
|
||||
toast( msg );
|
||||
}
|
||||
}
|
||||
|
||||
private void assertDirectoryExists( String message, String path )
|
||||
{
|
||||
File f = new File( path );
|
||||
f.mkdirs();
|
||||
if ( !f.exists() || !f.isDirectory() ) throw new IllegalArgumentException( message + ": " + path + " cannot be created" );
|
||||
}
|
||||
|
||||
private void paintPosition( int ilon, int ilat, int color, int with )
|
||||
{
|
||||
int lon = ilon - centerLon;
|
||||
int lat = ilat - centerLat;
|
||||
int x = imgw/2 + (int)(scaleLon*lon);
|
||||
int y = imgh/2 - (int)(scaleLat*lat);
|
||||
for( int nx=x-with; nx<=x+with; nx++)
|
||||
for( int ny=y-with; ny<=y+with; ny++)
|
||||
{
|
||||
if ( nx >= 0 && nx < imgw && ny >= 0 && ny < imgh )
|
||||
{
|
||||
imgPixels[ nx+imgw*ny] = color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void paintCircle( Canvas canvas, OsmNodeNamed n, int color, int minradius )
|
||||
{
|
||||
int lon = n.ilon - centerLon;
|
||||
int lat = n.ilat - centerLat;
|
||||
int x = imgw/2 + (int)(scaleLon*lon);
|
||||
int y = imgh/2 - (int)(scaleLat*lat);
|
||||
int ir = (int)(n.radius * 1000000. * scaleLat);
|
||||
if ( ir > minradius )
|
||||
{
|
||||
Paint paint = new Paint();
|
||||
paint.setColor( Color.RED );
|
||||
paint.setStyle( Paint.Style.STROKE );
|
||||
canvas.drawCircle( (float)x, (float)y, (float)ir, paint );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
|
||||
}
|
||||
|
||||
private void toast( String msg )
|
||||
{
|
||||
Toast.makeText(getContext(), msg, Toast.LENGTH_LONG ).show();
|
||||
lastDataTime += 4000; // give time for the toast before exiting
|
||||
}
|
||||
|
||||
|
||||
private long lastTs = System.currentTimeMillis();
|
||||
private long startTime = 0L;
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
try
|
||||
{
|
||||
_onDraw( canvas );
|
||||
}
|
||||
catch( Throwable t )
|
||||
{
|
||||
// on out of mem, try to stop the show
|
||||
String hint = "";
|
||||
if ( cr != null ) hint = cr.cleanOnOOM();
|
||||
cr = null;
|
||||
try { Thread.sleep( 2000 ); } catch( InterruptedException ie ) {}
|
||||
((BRouterActivity)getContext()).showErrorMessage( t.toString() + hint );
|
||||
waitingForSelection = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void _onDraw(Canvas canvas) {
|
||||
|
||||
if ( waitingForSelection ) return;
|
||||
|
||||
long currentTs = System.currentTimeMillis();
|
||||
long diffTs = currentTs - lastTs;
|
||||
long sleeptime = 500 - diffTs;
|
||||
while ( sleeptime < 200 ) sleeptime += 500;
|
||||
|
||||
try { Thread.sleep( sleeptime ); } catch ( InterruptedException ie ) {}
|
||||
lastTs = System.currentTimeMillis();
|
||||
|
||||
if ( cr == null || cr.isFinished() )
|
||||
{
|
||||
if ( cr != null )
|
||||
{
|
||||
if ( cr.getErrorMessage() != null )
|
||||
{
|
||||
((BRouterActivity)getContext()).showErrorMessage( cr.getErrorMessage() );
|
||||
cr = null;
|
||||
waitingForSelection = true;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
String result = "version = BRouter-0.98\n"
|
||||
+ "distance = " + cr.getDistance()/1000. + " km\n"
|
||||
+ "filtered ascend = " + cr.getAscend() + " m\n"
|
||||
+ "plain ascend = " + cr.getPlainAscend();
|
||||
|
||||
rawTrack = cr.getFoundRawTrack();
|
||||
|
||||
String title = "Success";
|
||||
if ( cr.getAlternativeIndex() > 0 ) title += " / " + cr.getAlternativeIndex() + ". Alternative";
|
||||
|
||||
((BRouterActivity)getContext()).showResultMessage( title, result, -1 );
|
||||
cr = null;
|
||||
waitingForSelection = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if ( System.currentTimeMillis() > lastDataTime )
|
||||
{
|
||||
System.exit(0);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
lastDataTime = System.currentTimeMillis();
|
||||
imgPixels = new int[imgw*imgh];
|
||||
|
||||
int[] openSet = cr.getOpenSet();
|
||||
for( int si = 0; si < openSet.length; si += 2 )
|
||||
{
|
||||
paintPosition( openSet[si], openSet[si+1], 0xffffff, 1 );
|
||||
}
|
||||
// paint nogos on top (red)
|
||||
for( int ngi=0; ngi<nogoList.size(); ngi++ )
|
||||
{
|
||||
OsmNodeNamed n = nogoList.get(ngi);
|
||||
int color = 0xff0000;
|
||||
paintPosition( n.ilon, n.ilat, color, 4 );
|
||||
}
|
||||
|
||||
// paint start/end/vias on top (yellow/green/blue)
|
||||
for( int wpi=0; wpi<wpList.size(); wpi++ )
|
||||
{
|
||||
OsmNodeNamed n = wpList.get(wpi);
|
||||
int color = wpi == 0 ? 0xffff00 : wpi < wpList.size()-1 ? 0xff : 0xff00;
|
||||
paintPosition( n.ilon, n.ilat, color, 4 );
|
||||
}
|
||||
|
||||
canvas.drawBitmap(imgPixels, 0, imgw, (float)0., (float)0., imgw, imgh, false, null);
|
||||
|
||||
// nogo circles if any
|
||||
for( int ngi=0; ngi<nogoList.size(); ngi++ )
|
||||
{
|
||||
OsmNodeNamed n = nogoList.get(ngi);
|
||||
int color = 0xff0000;
|
||||
paintCircle( canvas, n, color, 4 );
|
||||
}
|
||||
|
||||
|
||||
Paint paint = new Paint();
|
||||
paint.setColor(Color.WHITE);
|
||||
paint.setTextSize(20);
|
||||
|
||||
long mseconds = System.currentTimeMillis() - startTime;
|
||||
long links = cr.getLinksProcessed();
|
||||
long perS = (1000*links)/mseconds;
|
||||
String msg = "Links: " + cr.getLinksProcessed() + " in " + (mseconds/1000) + "s (" + perS + " l/s)";
|
||||
|
||||
canvas.drawText( msg, 10, 25, paint);
|
||||
}
|
||||
// and make sure to redraw asap
|
||||
invalidate();
|
||||
}
|
||||
|
||||
private String guessBaseDir()
|
||||
{
|
||||
File basedir = Environment.getExternalStorageDirectory();
|
||||
try
|
||||
{
|
||||
File bd2 = new File( basedir, "external_sd" );
|
||||
ArrayList<String> basedirGuesses = new ArrayList<String>();
|
||||
basedirGuesses.add( basedir.getAbsolutePath() );
|
||||
|
||||
if ( bd2.exists() )
|
||||
{
|
||||
basedir = bd2;
|
||||
basedirGuesses.add( basedir.getAbsolutePath() );
|
||||
}
|
||||
|
||||
ArrayList<CoordinateReader> rl = new ArrayList<CoordinateReader>();
|
||||
for( String bdg : basedirGuesses )
|
||||
{
|
||||
rl.add( new CoordinateReaderOsmAnd(bdg) );
|
||||
rl.add( new CoordinateReaderLocus(bdg) );
|
||||
rl.add( new CoordinateReaderOrux(bdg) );
|
||||
}
|
||||
long tmax = 0;
|
||||
CoordinateReader cor = null;
|
||||
for( CoordinateReader r : rl )
|
||||
{
|
||||
long t = r.getTimeStamp();
|
||||
if ( t > tmax )
|
||||
{
|
||||
tmax = t;
|
||||
cor = r;
|
||||
}
|
||||
}
|
||||
if ( cor != null )
|
||||
{
|
||||
return cor.basedir;
|
||||
}
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
System.out.println( "guessBaseDir:" + e );
|
||||
}
|
||||
return basedir.getAbsolutePath();
|
||||
}
|
||||
|
||||
public void writeRawTrackToMode( String mode )
|
||||
{
|
||||
// plus eventually the raw track for re-use
|
||||
String rawTrackPath = modesDir + "/" + mode + "_rawtrack.dat";
|
||||
if ( rawTrack != null )
|
||||
{
|
||||
try
|
||||
{
|
||||
rawTrack.writeBinary( rawTrackPath );
|
||||
}
|
||||
catch( Exception e ) {}
|
||||
}
|
||||
else
|
||||
{
|
||||
new File( rawTrackPath ).delete();
|
||||
}
|
||||
}
|
||||
|
||||
public void startConfigureService()
|
||||
{
|
||||
String[] modes = new String[] {
|
||||
"foot_short", "foot_fast",
|
||||
"bicycle_short", "bicycle_fast",
|
||||
"motorcar_short", "motorcar_fast"
|
||||
};
|
||||
boolean[] modesChecked = new boolean[6];
|
||||
|
||||
// parse global section of profile for mode preselection
|
||||
BExpressionContext expctxGlobal = new BExpressionContext( "global" );
|
||||
expctxGlobal.readMetaData( new File( profileDir, "lookups.dat" ) );
|
||||
expctxGlobal.parseFile( new File( profilePath ), null );
|
||||
expctxGlobal.evaluate( 1L, null );
|
||||
boolean isFoot = 0.f != expctxGlobal.getVariableValue( "validForFoot" );
|
||||
boolean isBike = 0.f != expctxGlobal.getVariableValue( "validForBikes" );
|
||||
boolean isCar = 0.f != expctxGlobal.getVariableValue( "validForCars" );
|
||||
|
||||
if ( isFoot || isBike || isCar )
|
||||
{
|
||||
modesChecked[ 0 ] = isFoot;
|
||||
modesChecked[ 1 ] = isFoot;
|
||||
modesChecked[ 2 ] = isBike;
|
||||
modesChecked[ 3 ] = isBike;
|
||||
modesChecked[ 4 ] = isCar;
|
||||
modesChecked[ 5 ] = isCar;
|
||||
}
|
||||
else
|
||||
{
|
||||
for( int i=0; i<6; i++)
|
||||
{
|
||||
modesChecked[i] = true;
|
||||
}
|
||||
}
|
||||
String msg = "Choose service-modes to configure (" + profileName + " [" + nogoVetoList.size() + "])";
|
||||
|
||||
((BRouterActivity)getContext()).selectRoutingModes( modes, modesChecked, msg );
|
||||
}
|
||||
|
||||
public void configureService(String[] routingModes, boolean[] checkedModes)
|
||||
{
|
||||
// read in current config
|
||||
TreeMap<String,ServiceModeConfig> map = new TreeMap<String,ServiceModeConfig>();
|
||||
BufferedReader br = null;
|
||||
String modesFile = modesDir + "/serviceconfig.dat";
|
||||
try
|
||||
{
|
||||
br = new BufferedReader( new FileReader (modesFile ) );
|
||||
for(;;)
|
||||
{
|
||||
String line = br.readLine();
|
||||
if ( line == null ) break;
|
||||
ServiceModeConfig smc = new ServiceModeConfig( line );
|
||||
map.put( smc.mode, smc );
|
||||
}
|
||||
}
|
||||
catch( Exception e ) {}
|
||||
finally
|
||||
{
|
||||
if ( br != null ) try { br.close(); } catch( Exception ee ) {}
|
||||
}
|
||||
|
||||
// replace selected modes
|
||||
for( int i=0; i<6; i++)
|
||||
{
|
||||
if ( checkedModes[i] )
|
||||
{
|
||||
writeRawTrackToMode( routingModes[i] );
|
||||
ServiceModeConfig smc = new ServiceModeConfig( routingModes[i], profileName);
|
||||
for( OsmNodeNamed nogo : nogoVetoList)
|
||||
{
|
||||
smc.nogoVetos.add( nogo.ilon + "," + nogo.ilat );
|
||||
}
|
||||
map.put( smc.mode, smc );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// no write new config
|
||||
BufferedWriter bw = null;
|
||||
StringBuilder msg = new StringBuilder( "Mode mapping is now:\n" );
|
||||
msg.append( "( [..] counts nogo-vetos)\n" );
|
||||
try
|
||||
{
|
||||
bw = new BufferedWriter( new FileWriter ( modesFile ) );
|
||||
for( ServiceModeConfig smc : map.values() )
|
||||
{
|
||||
bw.write( smc.toLine() );
|
||||
bw.write( '\n' );
|
||||
msg.append( smc.toString() ).append( '\n' );
|
||||
}
|
||||
}
|
||||
catch( Exception e ) {}
|
||||
finally
|
||||
{
|
||||
if ( bw != null ) try { bw.close(); } catch( Exception ee ) {}
|
||||
}
|
||||
((BRouterActivity)getContext()).showModeConfigOverview( msg.toString() );
|
||||
}
|
||||
|
||||
private String readSingleLineFile( File f )
|
||||
{
|
||||
BufferedReader br = null;
|
||||
try
|
||||
{
|
||||
br = new BufferedReader( new InputStreamReader ( new FileInputStream( f ) ) );
|
||||
return br.readLine();
|
||||
}
|
||||
catch( Exception e ) { return null; }
|
||||
finally
|
||||
{
|
||||
if ( br != null ) try { br.close(); } catch( Exception ee ) {}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,139 @@
|
|||
package btools.routingapp;
|
||||
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import android.os.Bundle;
|
||||
import btools.router.RoutingEngine;
|
||||
import btools.router.OsmNodeNamed;
|
||||
import btools.router.OsmTrack;
|
||||
import btools.router.RoutingContext;
|
||||
|
||||
public class BRouterWorker
|
||||
{
|
||||
public String segmentDir;
|
||||
public String profilePath;
|
||||
public String rawTrackPath;
|
||||
public List<OsmNodeNamed> nogoList;
|
||||
|
||||
public String getTrackFromParams(Bundle params)
|
||||
{
|
||||
String pathToFileResult = params.getString("pathToFileResult");
|
||||
|
||||
if (pathToFileResult != null)
|
||||
{
|
||||
File f = new File (pathToFileResult);
|
||||
File dir = f.getParentFile();
|
||||
if (!dir.exists() || !dir.canWrite()){
|
||||
return "file folder does not exists or can not be written!";
|
||||
}
|
||||
}
|
||||
|
||||
long maxRunningTime = 60000;
|
||||
String sMaxRunningTime = params.getString( "maxRunningTime" );
|
||||
if ( sMaxRunningTime != null )
|
||||
{
|
||||
maxRunningTime = Integer.parseInt( sMaxRunningTime ) * 1000;
|
||||
}
|
||||
|
||||
RoutingContext rc = new RoutingContext();
|
||||
rc.rawTrackPath = rawTrackPath;
|
||||
rc.localFunction = profilePath;
|
||||
if ( nogoList != null )
|
||||
{
|
||||
rc.prepareNogoPoints( nogoList );
|
||||
rc.nogopoints = nogoList;
|
||||
}
|
||||
|
||||
readNogos( params ); // add interface provides nogos
|
||||
|
||||
RoutingEngine cr = new RoutingEngine( null, null, segmentDir, readPositions(params), rc );
|
||||
cr.quite = true;
|
||||
cr.doRun( maxRunningTime );
|
||||
if ( cr.getErrorMessage() != null )
|
||||
{
|
||||
return cr.getErrorMessage();
|
||||
}
|
||||
|
||||
// store new reference track if any
|
||||
if ( cr.getFoundRawTrack() != null )
|
||||
{
|
||||
try
|
||||
{
|
||||
cr.getFoundRawTrack().writeBinary( rawTrackPath );
|
||||
}
|
||||
catch( Exception e ) {}
|
||||
}
|
||||
|
||||
|
||||
String format = params.getString("trackFormat");
|
||||
boolean writeKml = format != null && "kml".equals( format );
|
||||
|
||||
OsmTrack track = cr.getFoundTrack();
|
||||
if ( track != null )
|
||||
{
|
||||
if ( pathToFileResult == null )
|
||||
{
|
||||
if ( writeKml ) return track.formatAsKml();
|
||||
return track.formatAsGpx();
|
||||
}
|
||||
try
|
||||
{
|
||||
if ( writeKml ) track.writeKml(pathToFileResult);
|
||||
else track.writeGpx(pathToFileResult);
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
return "error writing file: " + e;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private List<OsmNodeNamed> readPositions( Bundle params )
|
||||
{
|
||||
List<OsmNodeNamed> wplist = new ArrayList<OsmNodeNamed>();
|
||||
|
||||
double[] lats = params.getDoubleArray("lats");
|
||||
double[] lons = params.getDoubleArray("lons");
|
||||
|
||||
if (lats == null || lats.length < 2 || lons == null || lons.length < 2)
|
||||
{
|
||||
throw new IllegalArgumentException( "we need two lat/lon points at least!" );
|
||||
}
|
||||
|
||||
for( int i=0; i<lats.length && i<lons.length; i++ )
|
||||
{
|
||||
OsmNodeNamed n = new OsmNodeNamed();
|
||||
n.name = "via" + i;
|
||||
n.ilon = (int)( ( lons[i] + 180. ) *1000000. + 0.5);
|
||||
n.ilat = (int)( ( lats[i] + 90. ) *1000000. + 0.5);
|
||||
wplist.add( n );
|
||||
}
|
||||
wplist.get(0).name = "from";
|
||||
wplist.get(wplist.size()-1).name = "to";
|
||||
|
||||
return wplist;
|
||||
}
|
||||
|
||||
private void readNogos( Bundle params )
|
||||
{
|
||||
double[] lats = params.getDoubleArray("nogoLats");
|
||||
double[] lons = params.getDoubleArray("nogoLons");
|
||||
double[] radi = params.getDoubleArray("nogoRadi");
|
||||
|
||||
if ( lats == null || lons == null || radi == null ) return;
|
||||
|
||||
for( int i=0; i<lats.length && i<lons.length && i<radi.length; i++ )
|
||||
{
|
||||
OsmNodeNamed n = new OsmNodeNamed();
|
||||
n.name = "nogo" + (int)radi[i];
|
||||
n.ilon = (int)( ( lons[i] + 180. ) *1000000. + 0.5);
|
||||
n.ilat = (int)( ( lats[i] + 90. ) *1000000. + 0.5);
|
||||
n.isNogo = true;
|
||||
nogoList.add( n );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,137 @@
|
|||
package btools.routingapp;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import android.os.Environment;
|
||||
import btools.router.OsmNodeNamed;
|
||||
|
||||
/**
|
||||
* Read coordinates from a gpx-file
|
||||
*/
|
||||
public abstract class CoordinateReader
|
||||
{
|
||||
public List<OsmNodeNamed> waypoints;
|
||||
public List<OsmNodeNamed> nogopoints;
|
||||
public String basedir;
|
||||
public String rootdir;
|
||||
public String tracksdir;
|
||||
|
||||
public Map<String,OsmNodeNamed> allpoints;
|
||||
private HashMap<String,OsmNodeNamed> pointmap;
|
||||
|
||||
protected static String[] posnames
|
||||
= new String[]{ "from", "via1", "via2", "via3", "via4", "via5", "via6", "via7", "via8", "via9", "to" };
|
||||
|
||||
public CoordinateReader( String basedir )
|
||||
{
|
||||
this.basedir = basedir;
|
||||
}
|
||||
|
||||
public abstract long getTimeStamp() throws Exception;
|
||||
|
||||
/*
|
||||
* read the from, to and via-positions from a gpx-file
|
||||
*/
|
||||
public void readFromTo() throws Exception
|
||||
{
|
||||
pointmap = new HashMap<String,OsmNodeNamed>();
|
||||
waypoints = new ArrayList<OsmNodeNamed>();
|
||||
nogopoints = new ArrayList<OsmNodeNamed>();
|
||||
readPointmap();
|
||||
boolean fromToMissing = false;
|
||||
for( int i=0; i<posnames.length; i++ )
|
||||
{
|
||||
String name = posnames[i];
|
||||
OsmNodeNamed n = pointmap.get(name);
|
||||
if ( n != null )
|
||||
{
|
||||
waypoints.add( n );
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( "from".equals( name ) ) fromToMissing = true;
|
||||
if ( "to".equals( name ) ) fromToMissing = true;
|
||||
}
|
||||
}
|
||||
if ( fromToMissing ) waypoints.clear();
|
||||
}
|
||||
|
||||
protected void checkAddPoint( OsmNodeNamed n )
|
||||
{
|
||||
if ( allpoints != null )
|
||||
{
|
||||
allpoints.put( n.name, n );
|
||||
return;
|
||||
}
|
||||
|
||||
boolean isKnown = false;
|
||||
for( int i=0; i<posnames.length; i++ )
|
||||
{
|
||||
if ( posnames[i].equals( n.name ) )
|
||||
{
|
||||
isKnown = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( isKnown )
|
||||
{
|
||||
if ( pointmap.put( n.name, n ) != null )
|
||||
{
|
||||
throw new IllegalArgumentException( "multiple " + n.name + "-positions!" );
|
||||
}
|
||||
}
|
||||
else if ( n.name != null && n.name.startsWith( "nogo" ) )
|
||||
{
|
||||
n.isNogo = true;
|
||||
nogopoints.add( n );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected abstract void readPointmap() throws Exception;
|
||||
|
||||
|
||||
public static CoordinateReader obtainValidReader( String basedir ) throws Exception
|
||||
{
|
||||
CoordinateReader cor = null;
|
||||
ArrayList<CoordinateReader> rl = new ArrayList<CoordinateReader>();
|
||||
rl.add( new CoordinateReaderOsmAnd(basedir) );
|
||||
rl.add( new CoordinateReaderLocus(basedir) );
|
||||
rl.add( new CoordinateReaderOrux(basedir) );
|
||||
|
||||
// eventually add standard-sd
|
||||
File standardbase = Environment.getExternalStorageDirectory();
|
||||
if ( standardbase != null )
|
||||
{
|
||||
String base2 = standardbase.getAbsolutePath();
|
||||
if ( !base2.equals( basedir ) )
|
||||
{
|
||||
rl.add( new CoordinateReaderOsmAnd(base2) );
|
||||
rl.add( new CoordinateReaderLocus(base2) );
|
||||
rl.add( new CoordinateReaderOrux(base2) );
|
||||
}
|
||||
}
|
||||
|
||||
long tmax = 0;
|
||||
for( CoordinateReader r : rl )
|
||||
{
|
||||
long t = r.getTimeStamp();
|
||||
if ( t > tmax )
|
||||
{
|
||||
tmax = t;
|
||||
cor = r;
|
||||
}
|
||||
}
|
||||
if ( cor == null )
|
||||
{
|
||||
cor = new CoordinateReaderNone();
|
||||
}
|
||||
cor.readFromTo();
|
||||
return cor;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
package btools.routingapp;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.util.Log;
|
||||
import btools.router.OsmNodeNamed;
|
||||
|
||||
/**
|
||||
* Read coordinates from a gpx-file
|
||||
*/
|
||||
public class CoordinateReaderLocus extends CoordinateReader
|
||||
{
|
||||
public CoordinateReaderLocus( String basedir )
|
||||
{
|
||||
super( basedir );
|
||||
tracksdir = "/Locus/mapItems";
|
||||
rootdir = "/Locus";
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getTimeStamp() throws Exception
|
||||
{
|
||||
long t1 = new File( basedir + "/Locus/data/database/waypoints.db" ).lastModified();
|
||||
return t1;
|
||||
}
|
||||
|
||||
/*
|
||||
* read the from and to position from a ggx-file
|
||||
* (with hardcoded name for now)
|
||||
*/
|
||||
@Override
|
||||
public void readPointmap() throws Exception
|
||||
{
|
||||
_readPointmap( basedir + "/Locus/data/database/waypoints.db" );
|
||||
}
|
||||
|
||||
private void _readPointmap( String filename ) throws Exception
|
||||
{
|
||||
SQLiteDatabase myDataBase = SQLiteDatabase.openDatabase( filename, null, SQLiteDatabase.OPEN_READONLY);
|
||||
Cursor c = myDataBase.rawQuery("SELECT name, longitude, latitude FROM waypoints", null);
|
||||
while (c.moveToNext())
|
||||
{
|
||||
OsmNodeNamed n = new OsmNodeNamed();
|
||||
n.name = c.getString(0);
|
||||
n.ilon = (int)( ( Double.parseDouble( c.getString(1) ) + 180. )*1000000. + 0.5);
|
||||
n.ilat = (int)( ( Double.parseDouble( c.getString(2) ) + 90. )*1000000. + 0.5);
|
||||
checkAddPoint( n );
|
||||
}
|
||||
myDataBase.close();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
package btools.routingapp;
|
||||
|
||||
|
||||
/**
|
||||
* Dummy coordinate reader if none found
|
||||
*/
|
||||
public class CoordinateReaderNone extends CoordinateReader
|
||||
{
|
||||
public CoordinateReaderNone()
|
||||
{
|
||||
super( "" );
|
||||
rootdir = "none";
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getTimeStamp() throws Exception
|
||||
{
|
||||
return 0L;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readPointmap() throws Exception
|
||||
{
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
package btools.routingapp;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import btools.router.OsmNodeNamed;
|
||||
|
||||
/**
|
||||
* Read coordinates from a gpx-file
|
||||
*/
|
||||
public class CoordinateReaderOrux extends CoordinateReader
|
||||
{
|
||||
public CoordinateReaderOrux( String basedir )
|
||||
{
|
||||
super( basedir );
|
||||
tracksdir = "/oruxmaps/tracklogs";
|
||||
rootdir = "/oruxmaps";
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getTimeStamp() throws Exception
|
||||
{
|
||||
long t1 = new File( basedir + "/oruxmaps/tracklogs/oruxmapstracks.db" ).lastModified();
|
||||
return t1;
|
||||
}
|
||||
|
||||
/*
|
||||
* read the from and to position from a ggx-file
|
||||
* (with hardcoded name for now)
|
||||
*/
|
||||
@Override
|
||||
public void readPointmap() throws Exception
|
||||
{
|
||||
_readPointmap( basedir + "/oruxmaps/tracklogs/oruxmapstracks.db" );
|
||||
}
|
||||
|
||||
private void _readPointmap( String filename ) throws Exception
|
||||
{
|
||||
SQLiteDatabase myDataBase = SQLiteDatabase.openDatabase( filename, null, SQLiteDatabase.OPEN_READONLY);
|
||||
Cursor c = myDataBase.rawQuery("SELECT poiname, poilon, poilat FROM pois", null);
|
||||
while (c.moveToNext())
|
||||
{
|
||||
OsmNodeNamed n = new OsmNodeNamed();
|
||||
n.name = c.getString(0);
|
||||
n.ilon = (int)( ( Double.parseDouble( c.getString(1) ) + 180. )*1000000. + 0.5);
|
||||
n.ilat = (int)( ( Double.parseDouble( c.getString(2) ) + 90. )*1000000. + 0.5);
|
||||
checkAddPoint( n );
|
||||
}
|
||||
myDataBase.close();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
package btools.routingapp;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.InputStreamReader;
|
||||
|
||||
import btools.router.OsmNodeNamed;
|
||||
|
||||
/**
|
||||
* Read coordinates from a gpx-file
|
||||
*/
|
||||
public class CoordinateReaderOsmAnd extends CoordinateReader
|
||||
{
|
||||
public CoordinateReaderOsmAnd( String basedir )
|
||||
{
|
||||
super( basedir );
|
||||
tracksdir = "/osmand/tracks";
|
||||
rootdir = "/osmand";
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getTimeStamp() throws Exception
|
||||
{
|
||||
long t1 = new File( basedir + "/osmand/favourites_bak.gpx" ).lastModified();
|
||||
long t2 = new File( basedir + "/osmand/favourites.gpx" ).lastModified();
|
||||
return t1 > t2 ? t1 : t2;
|
||||
}
|
||||
|
||||
/*
|
||||
* read the from and to position from a gpx-file
|
||||
* (with hardcoded name for now)
|
||||
*/
|
||||
@Override
|
||||
public void readPointmap() throws Exception
|
||||
{
|
||||
try
|
||||
{
|
||||
_readPointmap( basedir + "/osmand/favourites_bak.gpx" );
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
_readPointmap( basedir + "/osmand/favourites.gpx" );
|
||||
}
|
||||
}
|
||||
|
||||
private void _readPointmap( String filename ) throws Exception
|
||||
{
|
||||
BufferedReader br = new BufferedReader(
|
||||
new InputStreamReader(
|
||||
new FileInputStream( filename ) ) );
|
||||
OsmNodeNamed n = null;
|
||||
|
||||
for(;;)
|
||||
{
|
||||
String line = br.readLine();
|
||||
if ( line == null ) break;
|
||||
|
||||
int idx0 = line.indexOf( "<wpt lat=\"" );
|
||||
int idx10 = line.indexOf( "<name>" );
|
||||
if ( idx0 >= 0 )
|
||||
{
|
||||
n = new OsmNodeNamed();
|
||||
idx0 += 10;
|
||||
int idx1 = line.indexOf( '"', idx0 );
|
||||
n.ilat = (int)( (Double.parseDouble( line.substring( idx0, idx1 ) ) + 90. )*1000000. + 0.5);
|
||||
int idx2 = line.indexOf( " lon=\"" );
|
||||
if ( idx2 < 0 ) continue;
|
||||
idx2 += 6;
|
||||
int idx3 = line.indexOf( '"', idx2 );
|
||||
n.ilon = (int)( ( Double.parseDouble( line.substring( idx2, idx3 ) ) + 180. )*1000000. + 0.5);
|
||||
continue;
|
||||
}
|
||||
if ( n != null && idx10 >= 0 )
|
||||
{
|
||||
idx10 += 6;
|
||||
int idx11 = line.indexOf( "</name>", idx10 );
|
||||
if ( idx11 >= 0 )
|
||||
{
|
||||
n.name = line.substring( idx10, idx11 ).trim();
|
||||
checkAddPoint( n );
|
||||
}
|
||||
}
|
||||
}
|
||||
br.close();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package btools.routingapp;
|
||||
|
||||
|
||||
interface IBRouterService {
|
||||
|
||||
|
||||
//param params--> Map of params:
|
||||
// "pathToFileResult"-->String with the path to where the result must be saved, including file name and extension
|
||||
// -->if null, the track is passed via the return argument
|
||||
// "maxRunningTime"-->String with a number of seconds for the routing timeout, default = 60
|
||||
// "trackFormat"-->[kml|gpx] default = gpx
|
||||
// "lats"-->double[] array of latitudes; 2 values at least.
|
||||
// "lons"-->double[] array of longitudes; 2 values at least.
|
||||
// "nogoLats"-->double[] array of nogo latitudes; may be null.
|
||||
// "nogoLons"-->double[] array of nogo longitudes; may be null.
|
||||
// "nogoRadi"-->double[] array of nogo radius in meters; may be null.
|
||||
// "fast"-->[0|1]
|
||||
// "v"-->[motorcar|bicycle|foot]
|
||||
//return null if all ok and no path given, the track if ok and path given, an error message if it was wrong
|
||||
//call in a background thread, heavy task!
|
||||
|
||||
String getTrackFromParams(in Bundle params);
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
package btools.routingapp;
|
||||
|
||||
import java.util.StringTokenizer;
|
||||
import java.util.TreeSet;
|
||||
|
||||
|
||||
/**
|
||||
* Decsription of a service config
|
||||
*/
|
||||
public class ServiceModeConfig
|
||||
{
|
||||
public String mode;
|
||||
public String profile;
|
||||
public TreeSet<String> nogoVetos;
|
||||
|
||||
public ServiceModeConfig( String line )
|
||||
{
|
||||
StringTokenizer tk = new StringTokenizer( line );
|
||||
mode = tk.nextToken();
|
||||
profile = tk.nextToken();
|
||||
nogoVetos = new TreeSet<String>();
|
||||
while( tk.hasMoreTokens() )
|
||||
{
|
||||
nogoVetos.add( tk.nextToken() );
|
||||
}
|
||||
}
|
||||
|
||||
public ServiceModeConfig( String mode, String profile )
|
||||
{
|
||||
this.mode = mode;
|
||||
this.profile = profile;
|
||||
nogoVetos = new TreeSet<String>();
|
||||
}
|
||||
|
||||
public String toLine()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder( 100 );
|
||||
sb.append( mode ).append( ' ' ).append( profile );
|
||||
for( String veto: nogoVetos ) sb.append( ' ' ).append( veto );
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder( 100 );
|
||||
sb.append( mode ).append( "->" ).append( profile );
|
||||
sb.append ( " [" + nogoVetos.size() + "]" );
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue