From 9d549a7f34745ad905d73ce32e67a86df02fe835 Mon Sep 17 00:00:00 2001
From: alexrodriguez <alejandro.rodriguez@tecnalia.com>
Date: Mon, 28 Nov 2022 16:04:11 +0100
Subject: [PATCH] Added method to generate GPS Routes from vehicle points
 datamodel, and anonymice then deleting the first and last 50 meters of the
 route AND hidding the name with a hash code.

---
 .../anonymize/AnonymizeApplication.java       |   2 +-
 .../anonymize/rest/AnonymizerAPI.java         |  20 +-
 .../anonymize/service/AnonymizerService.java  | 102 +++++
 .../anonymize/service/DataService.java        |  20 +-
 .../anonymize/utils/Configuration.java        |   2 +-
 .../urbanite/anonymize/utils/DataUtils.java   | 247 ++++++++++
 src/main/java/sml/geoUtils/Calculations.java  | 170 +++++++
 .../GreatCircleDistanceCalculator.java        |  21 +
 .../sml/geoUtils/IDistanceCalculator.java     |   8 +
 src/main/java/sml/geoUtils/Topology.java      | 132 ++++++
 src/main/java/sml/geoUtils/Vector3.java       |  49 ++
 src/main/java/sml/geoUtils/geometry/BBox.java |  80 ++++
 .../java/sml/geoUtils/geometry/IPointGeo.java |  11 +
 .../java/sml/geoUtils/geometry/IPolyline.java |  12 +
 .../java/sml/geoUtils/geometry/PointGeo.java  |  34 ++
 .../java/sml/geoUtils/geometry/Polyline.java  |  66 +++
 .../java/sml/geoUtils/geometry/Segment.java   |  39 ++
 src/main/java/sml/gps/datamodel/GPSPoint.java | 222 +++++++++
 .../java/sml/gps/datamodel/GPSPoints.java     |  99 ++++
 src/main/java/sml/gps/datamodel/GPSRoute.java | 191 ++++++++
 .../java/sml/gps/datamodel/GPSSegment.java    |  21 +
 src/main/java/sml/gps/datamodel/GPSTrack.java |  20 +
 .../sml/gps/datamodel/GPSTrackSegment.java    |  17 +
 .../java/sml/stops/datamodel/StopData.java    | 427 ++++++++++++++++++
 src/main/java/sml/utils/Calculations.java     |  71 +++
 src/main/java/sml/utils/GeoDistance.java      |  30 ++
 src/main/java/sml/utils/PolyLineEncoder.java  |  89 ++++
 src/main/java/sml/utils/RawData.java          |  54 +++
 .../sml/visualizer/datamodel/Register.java    |  29 ++
 .../gui/functions/DivideInTraysCover.java     |  70 +++
 .../visualizer/gui/functions/FindStops.java   | 364 +++++++++++++++
 31 files changed, 2713 insertions(+), 6 deletions(-)
 create mode 100644 src/main/java/com/tecnalia/urbanite/anonymize/utils/DataUtils.java
 create mode 100644 src/main/java/sml/geoUtils/Calculations.java
 create mode 100644 src/main/java/sml/geoUtils/GreatCircleDistanceCalculator.java
 create mode 100644 src/main/java/sml/geoUtils/IDistanceCalculator.java
 create mode 100644 src/main/java/sml/geoUtils/Topology.java
 create mode 100644 src/main/java/sml/geoUtils/Vector3.java
 create mode 100644 src/main/java/sml/geoUtils/geometry/BBox.java
 create mode 100644 src/main/java/sml/geoUtils/geometry/IPointGeo.java
 create mode 100644 src/main/java/sml/geoUtils/geometry/IPolyline.java
 create mode 100644 src/main/java/sml/geoUtils/geometry/PointGeo.java
 create mode 100644 src/main/java/sml/geoUtils/geometry/Polyline.java
 create mode 100644 src/main/java/sml/geoUtils/geometry/Segment.java
 create mode 100644 src/main/java/sml/gps/datamodel/GPSPoint.java
 create mode 100644 src/main/java/sml/gps/datamodel/GPSPoints.java
 create mode 100644 src/main/java/sml/gps/datamodel/GPSRoute.java
 create mode 100644 src/main/java/sml/gps/datamodel/GPSSegment.java
 create mode 100644 src/main/java/sml/gps/datamodel/GPSTrack.java
 create mode 100644 src/main/java/sml/gps/datamodel/GPSTrackSegment.java
 create mode 100644 src/main/java/sml/stops/datamodel/StopData.java
 create mode 100644 src/main/java/sml/utils/Calculations.java
 create mode 100644 src/main/java/sml/utils/GeoDistance.java
 create mode 100644 src/main/java/sml/utils/PolyLineEncoder.java
 create mode 100644 src/main/java/sml/utils/RawData.java
 create mode 100644 src/main/java/sml/visualizer/datamodel/Register.java
 create mode 100644 src/main/java/sml/visualizer/gui/functions/DivideInTraysCover.java
 create mode 100644 src/main/java/sml/visualizer/gui/functions/FindStops.java

diff --git a/src/main/java/com/tecnalia/urbanite/anonymize/AnonymizeApplication.java b/src/main/java/com/tecnalia/urbanite/anonymize/AnonymizeApplication.java
index 4f4a2bd..da04d10 100644
--- a/src/main/java/com/tecnalia/urbanite/anonymize/AnonymizeApplication.java
+++ b/src/main/java/com/tecnalia/urbanite/anonymize/AnonymizeApplication.java
@@ -39,7 +39,7 @@ public class AnonymizeApplication {
 	@Bean
     public OpenAPI customOpenAPI(@Value("${application-description}") String appDesciption, @Value("${application-version}") String appVersion) {
 	 return new OpenAPI()
-			 .addServersItem(new Server().url("/data/"))
+			 .addServersItem(new Server().url("/anonymize/"))
 			 .info(new Info()
         		  .title("Anonymize API")
         		  .version(appVersion)
diff --git a/src/main/java/com/tecnalia/urbanite/anonymize/rest/AnonymizerAPI.java b/src/main/java/com/tecnalia/urbanite/anonymize/rest/AnonymizerAPI.java
index 7b5c7f3..a7ecca3 100644
--- a/src/main/java/com/tecnalia/urbanite/anonymize/rest/AnonymizerAPI.java
+++ b/src/main/java/com/tecnalia/urbanite/anonymize/rest/AnonymizerAPI.java
@@ -15,7 +15,6 @@
 */
 package com.tecnalia.urbanite.anonymize.rest;
 
-import org.codehaus.jettison.json.JSONObject;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.ResponseEntity;
 import org.springframework.web.bind.annotation.PathVariable;
@@ -40,7 +39,7 @@ public class AnonymizerAPI {
 	@Autowired
 	AnonymizerService anonymizerService;
 
-    @RequestMapping(method = RequestMethod.POST, value = "/anonymize/{city}/{model}/{property}/{value}")
+   /* @RequestMapping(method = RequestMethod.POST, value = "/anonymize/{city}/{model}/{property}/{value}")
     @ApiResponses(value = { 
     		@ApiResponse(responseCode = "200", description = "Successful operation."),
     		@ApiResponse(responseCode = "400", description = "Bad request. (User unknow)")})  
@@ -53,7 +52,7 @@ public class AnonymizerAPI {
        
     	return anonymizerService.anonymizeProperty(city,model,property,value);
             
-    }    
+    }   */ 
     
     @RequestMapping(method = RequestMethod.POST, value = "/removePointsOutOfArea/{city}/{model}/{property}/{value}")
     @ApiResponses(value = { 
@@ -78,4 +77,19 @@ public class AnonymizerAPI {
     	return anonymizerService.removePointsOutOfArea(location,city,model,property,value);
             
     }    	
+    
+    
+    @RequestMapping(method = RequestMethod.POST, value = "/generateAnonymousVehicleTripFromVehiclePointsString/{city}/{model}/{property}")
+    @ApiResponses(value = { 
+    		@ApiResponse(responseCode = "200", description = "Successful operation."),
+    		@ApiResponse(responseCode = "400", description = "Bad request. ")})    
+    public ResponseEntity<String> generateAnonymousVehicleTripFromVehiclePointsString(
+    		
+	   		 @Parameter(description = "City where is the model to be anonymized", example = "messina") @PathVariable String city,
+			 @Parameter(description = "Model that is going to be anonymized.", example = "vehicle") @PathVariable String model,
+    		 @Parameter(description = "Property of the model that is going to be anonymized.", example = "name") @PathVariable String property) throws Exception {
+    		
+    	return anonymizerService.generateAnonymousVehicleTripFromVehiclePointsString(city,model,property, 50);
+            
+    }    	
 }
diff --git a/src/main/java/com/tecnalia/urbanite/anonymize/service/AnonymizerService.java b/src/main/java/com/tecnalia/urbanite/anonymize/service/AnonymizerService.java
index 3f05c8b..2860577 100644
--- a/src/main/java/com/tecnalia/urbanite/anonymize/service/AnonymizerService.java
+++ b/src/main/java/com/tecnalia/urbanite/anonymize/service/AnonymizerService.java
@@ -4,6 +4,7 @@ import java.awt.geom.Path2D;
 import java.io.IOException;
 import java.net.URISyntaxException;
 import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
 import java.util.List;
 
 import org.codehaus.jettison.json.JSONArray;
@@ -14,9 +15,15 @@ import org.springframework.http.HttpStatus;
 import org.springframework.http.ResponseEntity;
 import org.springframework.stereotype.Service;
 
+import com.tecnalia.urbanite.anonymize.utils.Configuration;
 import com.tecnalia.urbanite.anonymize.utils.Crypto;
+import com.tecnalia.urbanite.anonymize.utils.DataUtils;
 
 import model.Location;
+import sml.gps.datamodel.GPSPoint;
+import sml.gps.datamodel.GPSRoute;
+import sml.utils.GeoDistance;
+import sml.visualizer.gui.functions.DivideInTraysCover;
 
 @Service
 public class AnonymizerService {
@@ -70,7 +77,102 @@ public class AnonymizerService {
 		}
 		
 	}
+	
+	public ResponseEntity<String> generateAnonymousVehicleTripFromVehiclePointsString (String city,String model, String property, double distanceInMeters)  {
+		try {
+			JSONObject object  = dataService.getDistinctObject(model, city, property);//messina,vehicle,name
+			JSONArray objects = (JSONArray) object.get("values");
+			String respuesta ="";
+			if (objects.length() > 0) {
+				for (int i=0;i<objects.length();i++) {
+					
+					String vehicleName = objects.getString(i);
+					//if (!vehicleName.equals("352503093621149")) continue;
+					//respuesta= respuesta+vehicleName+"-->";
+					
+					JSONArray vehiclePoints  = dataService.getObjectsByName(property,vehicleName, model, city);			
+					vehicleName = Crypto.generateHash(vehicleName);
+					//respuesta= respuesta+vehicleName+"\n";
+					GPSRoute route = new GPSRoute();
+					ArrayList<GPSPoint> puntos = new ArrayList<GPSPoint>();
+					route.setName(vehicleName);
+					route.setNodes(puntos);
+					for (int ipoint = 0 ; ipoint< vehiclePoints.length();ipoint++) {
+						boolean isvalid = DataUtils.isValidVehiclePoint(vehiclePoints.getJSONObject(ipoint));
+						if (isvalid) {
+							puntos.add(DataUtils.getGpsPointFromVehiclePoint(vehiclePoints.getJSONObject(ipoint)));
+						}
+					}
+					DivideInTraysCover ditc = new DivideInTraysCover(route);
+					ArrayList<GPSRoute> rutasAntesdeAnonimizar = ditc.doFindingTrays();
+					int borrados = 0;
+					//int b=0;
+					JSONArray arrObs = new JSONArray();
+					for (GPSRoute rutaaAnonymizar:rutasAntesdeAnonimizar) {
+						
+						ArrayList<GPSPoint> nodos = rutaaAnonymizar.getArrayNodes();
+						if (nodos.size() < 3) {
+							System.out.println("Ruta with few points. removed");
+							borrados++;
+							continue;
+						}
+						double distanciaruta = GeoDistance.CalcDistance(nodos.get(0), nodos.get(nodos.size()-1))*1000;
+						if (distanciaruta < distanceInMeters*2) {
+							System.out.println("Ruta too short "+distanciaruta+". removed");
+							borrados++;
+							continue;
+						}
+						else {
+														
+							//DataUtils.printGPSRoute(rutaaAnonymizar);
+							ArrayList<GPSPoint> puntosAnonymizadosBegin = DataUtils.removeDistanceFromBegin(nodos, distanceInMeters);
+							ArrayList<GPSPoint> puntosAnonymizados = DataUtils.removeDistanceFromEnd(puntosAnonymizadosBegin, distanceInMeters);
 
+							if (puntosAnonymizados.size() < 3) {
+								System.out.println("Ruta borrada al anonymizar");
+								borrados++;
+								continue;
+							}
+							//b++;
+							GPSRoute rutaAnonymizada = new GPSRoute();
+							rutaAnonymizada.setName(vehicleName);
+							rutaAnonymizada.setNodes(puntosAnonymizados);
+							arrObs.put(DataUtils.gpsRouteToNGSINode(rutaAnonymizada).toString());
+							//DataUtils.printGPSRoute(rutaAnonymizada);
+							//if (b==2 )break;
+						}
+						
+					}
+					respuesta =DataUtils.storeDataFlow("gtfsShape",arrObs.toString(),city,Configuration.getExporterUrl());
+					
+					System.out.println("Total rutas bici:"+vehicleName+"="+rutasAntesdeAnonimizar.size()+" Eliminadas:"+borrados);
+					
+				}
+				return new ResponseEntity<>(respuesta, HttpStatus.OK);
+			}
+			else {
+				return new ResponseEntity<>("Error generation anonymous routes fromvehicles !!", HttpStatus.BAD_REQUEST);	
+			}
+			
+					
+		} catch (JSONException e) {
+			e.printStackTrace();
+			return new ResponseEntity<>("Error while generateAnonymousVehicleTripFromVehiclePointsString:" + property + ". Please try again !! " + e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
+		} catch (URISyntaxException e) {
+			e.printStackTrace();
+			return new ResponseEntity<>("Error while generateAnonymousVehicleTripFromVehiclePointsString:" + property + ". Please try again !! " + e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
+		} catch (IOException e) {
+			e.printStackTrace();
+			return new ResponseEntity<>("Error while generateAnonymousVehicleTripFromVehiclePointsString:" + property + ". Please try again !! " + e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
+		} catch (InterruptedException e) {
+			e.printStackTrace();
+			return new ResponseEntity<>("Error while generateAnonymousVehicleTripFromVehiclePointsString:" + property + ". Please try again !! " + e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
+		} catch (NoSuchAlgorithmException e) {
+			e.printStackTrace();
+			return new ResponseEntity<>("Error while generateAnonymousVehicleTripFromVehiclePointsString:" + property + ". Please try again !! " + e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
+		}
+	}
+	
 	public ResponseEntity<String> removePointsOutOfArea(Location location, String city,String model, String property,String value) {
 		try {
 			JSONArray objects  = dataService.getObjectsByName(property,value, model, city);
diff --git a/src/main/java/com/tecnalia/urbanite/anonymize/service/DataService.java b/src/main/java/com/tecnalia/urbanite/anonymize/service/DataService.java
index 91ace8a..be63f95 100644
--- a/src/main/java/com/tecnalia/urbanite/anonymize/service/DataService.java
+++ b/src/main/java/com/tecnalia/urbanite/anonymize/service/DataService.java
@@ -28,7 +28,7 @@ public class DataService {
 		
 		//https://bilbao.urbanite.esilab.org/data/getTData/mapLayer/bilbao?filters=%7B%22alternateName%22%3A%20%22test.geojson%22%7D
 		HttpRequest request = HttpRequest.newBuilder()
-				  .uri(new URI(URL+"/data/getTData/"+model+"/"+city+"?filters="+filter))
+				  .uri(new URI(URL+"/data/getTData/"+model+"/"+city+"?filters="+filter+"&sort=ASC"))
 				  .GET()
 				  .build();
 		
@@ -73,6 +73,24 @@ public class DataService {
 		System.out.println(response.body());
 		return  new JSONObject(response.body());		
 	}	
+	public JSONObject getDistinctObject(String model, String city, String field) throws JSONException, URISyntaxException, IOException, InterruptedException {
+		String URL =  Configuration.getExporterUrl();
+		
+		//http://localhost:8080/data/updateTData/vehicle/bilbao/vehicle%3AWasteManagement%3A1
+		HttpRequest request = HttpRequest.newBuilder()
+				  .uri(new URI(URL+"/data/getDistinct/"+model+"/"+city+"?field="+field))
+				  .GET()				  
+				  .build();
+		
+		HttpResponse<String> response = HttpClient
+				  .newBuilder()
+				  .build()
+				  .send(request, BodyHandlers.ofString());
+		System.out.println(response.body());
+		return  new JSONObject(response.body());		
+	}	
+	
+	
 	private String encode(String value) throws UnsupportedEncodingException {
 		
 	    return URLEncoder.encode(value, StandardCharsets.UTF_8.toString());
diff --git a/src/main/java/com/tecnalia/urbanite/anonymize/utils/Configuration.java b/src/main/java/com/tecnalia/urbanite/anonymize/utils/Configuration.java
index 8b2005e..65bc6c5 100644
--- a/src/main/java/com/tecnalia/urbanite/anonymize/utils/Configuration.java
+++ b/src/main/java/com/tecnalia/urbanite/anonymize/utils/Configuration.java
@@ -4,7 +4,7 @@ public class Configuration {
 	
 	public static   String getExporterUrl()
 	{
-		return  System.getenv("DATA_STORAGE_URL") != null ? System.getenv("OPENTSDB_URL") : "http://localhost:8080";
+		return  System.getenv("DATA_STORAGE_URL") != null ? System.getenv("DATA_STORAGE_URL") : "http://localhost:8080";
 	}
 
 }
diff --git a/src/main/java/com/tecnalia/urbanite/anonymize/utils/DataUtils.java b/src/main/java/com/tecnalia/urbanite/anonymize/utils/DataUtils.java
new file mode 100644
index 0000000..635e623
--- /dev/null
+++ b/src/main/java/com/tecnalia/urbanite/anonymize/utils/DataUtils.java
@@ -0,0 +1,247 @@
+package com.tecnalia.urbanite.anonymize.utils;
+
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.security.cert.X509Certificate;
+import java.time.OffsetDateTime;
+import java.time.ZonedDateTime;
+import java.util.ArrayList;
+import java.util.Collections;
+
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+
+import org.codehaus.jettison.json.JSONException;
+import org.codehaus.jettison.json.JSONObject;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+import sml.gps.datamodel.GPSPoint;
+import sml.gps.datamodel.GPSRoute;
+import sml.utils.GeoDistance;
+
+public class DataUtils {
+	public static boolean isValidVehiclePoint(JSONObject vehicle) throws JSONException {
+		String observationDateTime = vehicle.getString("observationDateTime");
+        ZonedDateTime dateTime = getZonedDateTimeFromString(observationDateTime);
+        
+       if (dateTime ==null) {
+            System.out.println ("Descatartado:"+observationDateTime);
+            return false;            
+        }		
+        if (dateTime.getYear() < 2020 || dateTime.getYear() > 2021){
+            System.out.println ("Descatartado:"+dateTime.getYear());
+            return false; 
+        }
+        else if (dateTime.getMonthValue() < 8 && dateTime.getYear() == 2020  ) {
+            System.out.println ("Descatartado:"+dateTime.getYear()+"-"+dateTime.getMonthValue());
+            return false; 
+            
+        }
+        else{
+        	return true;
+        }        
+		
+	}
+	public static GPSPoint getGpsPointFromVehiclePoint(JSONObject vehicle) throws JSONException {
+		
+		String observationDateTime = vehicle.getString("observationDateTime");
+		ZonedDateTime dateTime = getZonedDateTimeFromString(observationDateTime);
+		if (dateTime == null) {
+        	System.out.println ("Descatartado:"+observationDateTime);
+        	return null;            
+        }			
+        Long time =    dateTime.toEpochSecond() *1000;         
+    	double lon  = vehicle.getJSONObject("location").getJSONArray("coordinates").getDouble(0);
+        double lat =vehicle.getJSONObject("location").getJSONArray("coordinates").getDouble(1);
+        double speed = vehicle.getDouble("speed");
+        GPSPoint p =new GPSPoint(time,lat,lon,speed);
+        p.setTime(dateTime.toEpochSecond());
+        return p;
+ 		
+	}
+	
+	private static ZonedDateTime getZonedDateTimeFromString (String zonedDateTime) {
+		ZonedDateTime dateTime = null;
+        try {                  
+            dateTime =OffsetDateTime.parse(zonedDateTime).toZonedDateTime();
+            return dateTime;
+        }
+	    catch (java.time.format.DateTimeParseException e){
+	    	System.out.println ("Descatartado:"+zonedDateTime+ " -- "+e.getMessage());
+	        return null;            
+	    }		
+		
+	}
+	public static void printGPSRoute(GPSRoute route) {
+		String respuesta = "";
+		ArrayList<GPSPoint> nodos = route.getArrayNodes();
+		double distanciarutaanonimizada = GeoDistance.CalcDistance(nodos.get(0), nodos.get(nodos.size()-1))*1000;
+		System.out.println("Ruta con distancia: "+distanciarutaanonimizada+" Nuemro de puntos:"+nodos.size()+".");
+		
+		for(int j=0;j<nodos.size();j++) {
+			GPSPoint nodo = nodos.get(j);								
+			respuesta = respuesta + nodo.getTime()+","+nodo.getLatitude()+","+nodo.getLongitude()+","+nodo.getSpeed()+"\n";
+			
+		}
+		respuesta = respuesta +"-------\n";
+		System.out.println (respuesta);
+
+		
+	}
+	public static ArrayList<GPSPoint> removeDistanceFromBegin(ArrayList<GPSPoint> nodos,double distanceInMeters){
+		ArrayList<GPSPoint> puntosAnonymizados = new ArrayList<GPSPoint>();
+		double distanciaBorrada=0;
+		for(int j=0;j<nodos.size();j++) {
+			//if (j==0) continue;
+			if (distanciaBorrada >= distanceInMeters) {
+				puntosAnonymizados.add(nodos.get(j));
+			}							
+			else if  (j != nodos.size()-1) {
+				double parcial = GeoDistance.CalcDistance(nodos.get(j), nodos.get(j+1))*1000;
+				System.out.println("distanciaBorrada:"+parcial);
+				distanciaBorrada = distanciaBorrada + parcial;
+			}
+		}
+		System.out.println("Total distanciaBorrada:"+distanciaBorrada);
+		return puntosAnonymizados;
+	}
+	public static ArrayList<GPSPoint> removeDistanceFromEnd(ArrayList<GPSPoint> nodos,double distanceInMeters){
+		ArrayList<GPSPoint> puntosAnonymizados = new ArrayList<GPSPoint>();
+		double distanciaBorrada=0;
+		for(int j=nodos.size()-1 ;j>=0;j--) {
+			//if (j==0) continue;
+			if (distanciaBorrada >= distanceInMeters) {
+				puntosAnonymizados.add(nodos.get(j));
+			}							
+			else if  (j > 0) {
+				double parcial = GeoDistance.CalcDistance(nodos.get(j-1), nodos.get(j))*1000;
+				System.out.println("distanciaBorrada:"+parcial);
+				distanciaBorrada = distanciaBorrada + parcial;
+			}
+		}
+		Collections.reverse(puntosAnonymizados);
+		System.out.println("Total distanciaBorrada:"+distanciaBorrada);
+		return puntosAnonymizados;
+	}
+	public static ObjectNode gpsRouteToNGSINode(GPSRoute route) {
+		ArrayList<GPSPoint> nodos = route.getArrayNodes();
+		String id  = ""+nodos.get(0).getTime();
+		String name = route.getName();
+		ObjectNode node = new ObjectMapper().createObjectNode()
+                .put("id", "urn:ld:GtfsShape:messina:ebiketrajectories:" + name+":"+id)
+                .put("type", "GtfsShape")
+                .put("alternateName", "ebiketrajectories")
+                .put("name", name);
+                
+        ArrayNode arrayNodeContext = new ObjectMapper().createArrayNode()
+                .add("https://smartdatamodels.org/context.jsonld")
+                .add("https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld");
+
+        node.set("@context", arrayNodeContext);
+        ObjectNode location = new ObjectMapper().createObjectNode();
+        
+        ArrayNode coordinates = new ObjectMapper().createArrayNode();
+        ArrayNode poligons = new ObjectMapper().createArrayNode();
+        ArrayNode trozos = new ObjectMapper().createArrayNode();
+        for (GPSPoint nodo:nodos) {
+        	ArrayNode point = new ObjectMapper().createArrayNode();
+        	point.add(nodo.getLatitude());
+        	point.add(nodo.getLongitude());
+        	poligons.add(point);
+        }
+        trozos.add(poligons);
+        coordinates.add(trozos);
+        location.set("coordinates",coordinates);
+        location.put("type","MultiPolygon");
+        node.set("location",location);
+		
+		return node;
+		
+		
+	}
+	   public static String storeDataFlow(String model, String jsonInput, String piloto,String hurl) {
+	       HttpURLConnection conn = null;
+	        try {
+	            // Install the all-trusting trust manager
+	            SSLContext sc = SSLContext.getInstance("SSL");
+	            sc.init(null, trustAllCerts, new java.security.SecureRandom());
+	            HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
+
+	            // Install the all-trusting host verifier
+	            HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid);
+	//http://localhost/data/insertTData/touristTrip/bilbao
+	//https://helsinki.urbanite.esilab.org/data/insertTData/TransportStation/helsinki
+	//https://amsterdam.urbanite.esilab.org/data/insertTData/pointOfInterest/amsterdam
+	            URL url = new URL(hurl + "/data/insertTData/"+model+"/"+piloto);
+	            conn = (HttpURLConnection) url.openConnection();
+	            conn.setDoOutput(true);
+	            conn.setRequestMethod("POST");
+	            conn.setRequestProperty("Content-Type", "application/json");
+
+	            OutputStream os = conn.getOutputStream();
+	            os.write(jsonInput.getBytes());
+	            os.flush();
+	          
+	            BufferedReader br = new BufferedReader(new InputStreamReader((conn.getInputStream())));
+
+	            String output;
+	            
+	            while ((output = br.readLine()) != null) {
+
+	                if (!output.contains("\"notInserted\":[]")) {
+	                    JSONObject res = new JSONObject(output);
+	                    System.out.println(res.getJSONArray("notInserted").toString());
+	                    System.out.println("Error in storeDataFlow model= " + model );                    
+	                    return output;
+	                }
+	                else {
+	                    System.out.println("Correct!");
+	                }
+
+	            }
+
+	            
+	            return output;
+
+	        } catch (Exception e) {
+	            System.err.println("Error in storeTrafficFlow model= " + model+" "+e.getMessage());// + " \njsonInput " + jsonInput);
+	            System.err.println(e.getMessage());
+	            //System.exit(0);
+	            return "Error in storeTrafficFlow model= " + model+" "+e.getMessage();
+
+	        }
+	        finally {
+	            if (conn!=null)conn.disconnect();
+	        }
+
+	    }     
+    private static TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() {
+        public java.security.cert.X509Certificate[] getAcceptedIssuers() {
+            return null;
+        }
+
+        public void checkClientTrusted(X509Certificate[] certs, String authType) {
+        }
+
+        public void checkServerTrusted(X509Certificate[] certs, String authType) {
+        }
+    }
+    };
+
+    // Create all-trusting host name verifier
+    private static HostnameVerifier allHostsValid = new HostnameVerifier() {
+        public boolean verify(String hostname, SSLSession session) {
+            return true;
+        }
+    };
+}
diff --git a/src/main/java/sml/geoUtils/Calculations.java b/src/main/java/sml/geoUtils/Calculations.java
new file mode 100644
index 0000000..d610a7e
--- /dev/null
+++ b/src/main/java/sml/geoUtils/Calculations.java
@@ -0,0 +1,170 @@
+
+package sml.geoUtils;
+
+import sml.geoUtils.geometry.IPointGeo;
+import sml.geoUtils.geometry.IPolyline;
+import sml.geoUtils.geometry.PointGeo;
+import sml.geoUtils.geometry.Segment;
+import sml.gps.datamodel.GPSPoint;
+
+public class Calculations {
+    public static Double EARTH_RADIOUS = 6371010.0;
+    public static Double EPS_LENGTH    = 0.01;
+    public static int NUMBEROFPOINTS = 1000;
+    //static GreatCircleDistanceCalculator distanceCalculator;
+    static IDistanceCalculator distanceCalculator= new GreatCircleDistanceCalculator();
+    public Calculations(){
+        distanceCalculator = new GreatCircleDistanceCalculator();
+    }
+    
+    public static Double getBearing(IPointGeo pt1, IPointGeo pt2){
+        Double dLon = Calculations.toRadians(pt1.getLongitude()-pt2.getLongitude());
+        Double dLat = Calculations.toRadians(pt1.getLatitude()-pt2.getLatitude());
+        
+        Double y = Math.sin(dLon) * Math.cos(Calculations.toRadians(pt2.getLatitude()));
+        Double x = Math.cos(Calculations.toRadians(pt1.getLatitude())) * Math.sin(Calculations.toRadians(pt2.getLatitude()))
+                - Math.sin(Calculations.toRadians(pt1.getLatitude())) * Math.cos(Calculations.toRadians(pt2.getLatitude())) * Math.cos(dLon);
+        return Calculations.toDegrees(Math.atan2(y, x));
+    }
+    
+    public static Double getBearing(Segment<IPointGeo> segment){
+        return Calculations.getBearing(segment.getStartPoint(), segment.getEndPoint());
+    };
+    
+    public static Double getDistance2D(IPointGeo pt1, IPointGeo pt2){
+        //System.out.println("pt1=<"+pt1.getLatitude()+","+pt1.getLongitude()+">");
+        //System.out.println("pt1=<"+pt2.getLatitude()+","+pt2.getLongitude()+">");
+        if( pt1 == null ){ System.err.println("Calculations.getDistance:::pt1=null"); System.exit(1);}
+        if( pt2 == null ){ System.err.println("Calculations.getDistance:::pt2=null"); System.exit(1);}
+        return distanceCalculator.calculate2D(pt1,pt2);
+    }
+    
+    public static Double getDistance2D(IPointGeo point, Segment<IPointGeo> segment){
+          IPointGeo projectedPoint = Topology.projectPoint(point, segment);
+          return distanceCalculator.calculate2D(point, projectedPoint);
+    }
+    
+    public static Double getDistance2D(IPointGeo point, IPolyline<IPointGeo> line){
+          Double minDistance = Double.POSITIVE_INFINITY;
+          for(Segment segment : line.getSegments()){
+             IPointGeo projectedPoint = Topology.projectPoint(point,segment);
+             minDistance = Math.min(minDistance, distanceCalculator.calculate2D(point,projectedPoint));
+          }
+          return minDistance;
+    }
+    
+    public static Double getDistance2D(IPointGeo point, IPolyline<IPointGeo> line, int closestSegmentIndex){
+        Double minDistance = Double.POSITIVE_INFINITY;
+        for(int i=0;i<line.getSegments().size();i++){
+           IPointGeo projectedPoint = Topology.projectPoint(point,line.getSegments().get(i));
+           Double distance = distanceCalculator.calculate2D(point, projectedPoint);
+           if ( distance < minDistance ){
+               minDistance = distance;
+               closestSegmentIndex = i;
+           }
+        }
+        return minDistance;
+    }
+    public static Double getPathLength(IPointGeo from, IPointGeo to, Segment<IPointGeo> path){
+        return Calculations.getDistance2D(from, to);
+    }
+    
+    public static Double getPathLength(IPointGeo from, IPointGeo to, IPolyline<IPointGeo> path){
+       int pointFound = 0;
+       int pointFoundLast = 0;
+       boolean fromFound = false; 
+       boolean toFound   = false;
+       Double distance = 0.0;
+       for(Segment segment : path.getSegments()){
+          pointFoundLast = pointFound;
+          IPointGeo[] points = new IPointGeo[] {segment.getStartPoint(),segment.getEndPoint()};
+          if (!fromFound && (Calculations.getDistance2D(from, segment) < EPS_LENGTH)){
+             fromFound = true;
+             points[pointFound++] = from;
+          }
+          if(!toFound    && (Calculations.getDistance2D(to,   segment) < EPS_LENGTH)){
+             toFound = true;
+             points[pointFound++] = to;
+          }
+          if ( pointFound > 0 ){
+             if (pointFound == pointFoundLast)  distance = distance + segment.getLength();
+             else                               distance = distance + Calculations.getDistance2D(points[0],points[1]);
+             if (pointFound == 2) return distance;
+          
+          }
+       }
+       return distance;
+    }
+    public static Double getLength(IPolyline<IPointGeo> line){
+        Double length = 0.0;
+        for(Segment segment : line.getSegments()){
+           length = length + segment.getLength();
+        }
+        return length;
+    }
+    public static PointGeo Averaging(Iterable<IPointGeo> points){
+       int count = 0;
+       Double lat = 0.0;
+       Double lon = 0.0;
+       for(IPointGeo point : points){
+           lat = lat + point.getLatitude();
+           lon = lon + point.getLongitude();
+           count = count + 1;
+       }
+       return new PointGeo(lat/count, lon/count);
+    }
+    public static Double toRadians(Double angle){
+       return angle * Math.PI / 180.0;
+    }
+    public static double toDegrees(Double angle){
+       return angle * 180.0 / Math.PI;
+    }
+    
+    public static GPSPoint greatCirclePoint(GPSPoint pnt1, GPSPoint pnt2, Double f){
+        /*
+        https://math.stackexchange.com/questions/383711/parametric-equation-for-great-circle
+            d = acos(sin(lat1) * sin(lat2) + cos(lat1) * cos(lat2) * cos(lon1 - lon2))
+            A = sin((1 - f) * d) / sin(d)
+            B = sin(f * d) / sin(d)
+            x = A * cos(lat1) * cos(lon1) + B * cos(lat2) * cos(lon2)
+            y = A * cos(lat1) * sin(lon1) + B * cos(lat2) * sin(lon2)
+            z = A * sin(lat1) + B * sin(lat2)
+
+            lat_f = atan2(z, sqrt(x^2 + y^2))
+            lon_f = atan2(y,x)
+            } 
+        */  
+        
+        Double lat1 = Math.toRadians(pnt1.getLatitude());
+        Double lon1 = Math.toRadians(pnt1.getLongitude());
+        Double lat2 = Math.toRadians(pnt2.getLatitude());
+        Double lon2 = Math.toRadians(pnt2.getLongitude());
+        
+        Double d = Math.acos(Math.sin(lat1) * Math.sin(lat2) + Math.cos(lat1) * Math.cos(lat2) * Math.cos(lon1-lon2));
+        Double A = Math.sin((1.0-f) * d ) / Math.sin(d);
+        Double B = Math.sin(f*d) / Math.sin(d);
+        Double x = A * Math.cos(lat1) * Math.cos(lon1) + B * Math.cos(lat2) * Math.cos(lon2);
+        Double y = A * Math.cos(lat1) * Math.sin(lon1) + B * Math.cos(lat2) * Math.sin(lon2);
+        Double z = A * Math.sin(lat1) + B * Math.sin(lat2);
+        
+        Double lat_f = Math.toDegrees(Math.atan2(z, Math.sqrt(x*x+y*y)));
+        Double lon_f = Math.toDegrees(Math.atan2(y,x));
+        
+        GPSPoint pnt = new GPSPoint(lat_f,lon_f);
+        return pnt;
+    }
+    
+    public static Double distanceToGreatCircle(GPSPoint pnt, GPSPoint pnt1, GPSPoint pnt2){
+        Double minDistance = Double.MAX_VALUE;
+        int numPoints = NUMBEROFPOINTS;
+        for(int i=0;i<numPoints;i++){
+            Double f = (i*1.0) / ((numPoints-1)*1.0);
+            GPSPoint gcPnt = greatCirclePoint(pnt1, pnt2, f);
+            Double distance = Calculations.getDistance2D(pnt, gcPnt);
+            if ( distance < minDistance ){
+                minDistance = distance;
+            }
+        }
+        return minDistance;
+    }
+}
diff --git a/src/main/java/sml/geoUtils/GreatCircleDistanceCalculator.java b/src/main/java/sml/geoUtils/GreatCircleDistanceCalculator.java
new file mode 100644
index 0000000..78d12b9
--- /dev/null
+++ b/src/main/java/sml/geoUtils/GreatCircleDistanceCalculator.java
@@ -0,0 +1,21 @@
+
+package sml.geoUtils;
+
+import sml.geoUtils.geometry.IPointGeo;
+
+public class GreatCircleDistanceCalculator implements IDistanceCalculator {
+      Double EARTH_RADIOUS = 6371010.0;
+      public Double calculate2D(IPointGeo pt1, IPointGeo pt2)  {
+          //System.out.println("GreateCircleDistanceCalculator.calculate2D:::in routine");
+          Double dLat = Calculations.toRadians(pt2.getLatitude()-pt1.getLatitude());
+          Double dLon = Calculations.toRadians(pt2.getLongitude()-pt1.getLongitude());
+          Double a = Math.sin(dLat/2.0) * Math.sin(dLat/2.0) 
+                   + Math.cos(Calculations.toRadians(pt1.getLatitude())) * 
+                     Math.cos(Calculations.toRadians(pt2.getLatitude())) * 
+                     Math.sin(dLon/2.0) * Math.sin(dLon/2.0);
+          Double dAngle = 2 * Math.asin(Math.sqrt(a));
+          return dAngle * this.EARTH_RADIOUS;
+                  
+      
+      }
+}
diff --git a/src/main/java/sml/geoUtils/IDistanceCalculator.java b/src/main/java/sml/geoUtils/IDistanceCalculator.java
new file mode 100644
index 0000000..0ca93e7
--- /dev/null
+++ b/src/main/java/sml/geoUtils/IDistanceCalculator.java
@@ -0,0 +1,8 @@
+
+package sml.geoUtils;
+
+import sml.geoUtils.geometry.IPointGeo;
+
+public interface IDistanceCalculator {
+    Double calculate2D(IPointGeo pt1, IPointGeo pt2);   
+}
diff --git a/src/main/java/sml/geoUtils/Topology.java b/src/main/java/sml/geoUtils/Topology.java
new file mode 100644
index 0000000..1fc2e34
--- /dev/null
+++ b/src/main/java/sml/geoUtils/Topology.java
@@ -0,0 +1,132 @@
+package sml.geoUtils;
+
+import java.util.ArrayList;
+import sml.geoUtils.geometry.BBox;
+import sml.geoUtils.geometry.IPointGeo;
+import sml.geoUtils.geometry.IPolyline;
+import sml.geoUtils.geometry.PointGeo;
+import sml.geoUtils.geometry.Segment;
+
+public class Topology {
+
+
+
+     public IPointGeo projectPointSphere(IPointGeo toProject, Segment<IPointGeo> projectTo){
+         Vector3 a = new Vector3(projectTo.getStartPoint());
+         Vector3 b = new Vector3(projectTo.getEndPoint());
+         Vector3 c = new Vector3(toProject);
+         
+         Vector3 greatCircleN  = Vector3.crossProduct(a,b);
+         Vector3 greatCircleCN = Vector3.crossProduct(c,greatCircleN);
+         Vector3 projected     = Vector3.crossProduct(greatCircleN, greatCircleCN);
+         projected.normalize();
+         
+         PointGeo result = projected.toSpherical();
+         
+         Double apDistance = Calculations.getDistance2D(projectTo.getStartPoint(), result);
+         Double bpDistance = Calculations.getDistance2D(projectTo.getEndPoint(), result);
+         if ( apDistance + bpDistance - Calculations.getLength((IPolyline<IPointGeo>)projectTo) < 0.01 ){ return result; }
+         else{
+            if(Calculations.getDistance2D(projectTo.getStartPoint(),toProject) < Calculations.getDistance2D(projectTo.getEndPoint(),toProject)){
+                 return projectTo.getStartPoint();
+            }else{
+                 return projectTo.getEndPoint();
+            }
+         }
+     
+     }
+
+     public static IPointGeo projectPoint  (IPointGeo toProject, Segment<IPointGeo> projectTo){
+	    Double u = ((projectTo.getEndPoint().getLongitude() - projectTo.getStartPoint().getLongitude()) * 
+                        (toProject.getLongitude()               - projectTo.getStartPoint().getLongitude()) 
+		      + (projectTo.getEndPoint().getLatitude()  - projectTo.getStartPoint().getLatitude()) * 
+                        (toProject.getLatitude()                - projectTo.getStartPoint().getLatitude())
+                       ) /
+		        (  Math.pow(projectTo.getEndPoint().getLongitude() - projectTo.getStartPoint().getLongitude(), 2) 
+                         + Math.pow(projectTo.getEndPoint().getLatitude()  - projectTo.getStartPoint().getLatitude(),  2)
+                        );
+            
+            if ( u <= 0.0 ) return projectTo.getStartPoint();
+            if ( u >= 1.0 ) return projectTo.getEndPoint();
+            Double lon = projectTo.getStartPoint().getLongitude() + u *(projectTo.getEndPoint().getLongitude() - projectTo.getStartPoint().getLongitude());
+            Double lat = projectTo.getStartPoint().getLatitude()  + u *(projectTo.getEndPoint().getLatitude()  - projectTo.getStartPoint().getLatitude());
+            return new PointGeo(lat,lon);
+     }
+     public static IPointGeo projectPoint  (IPointGeo point, IPolyline<IPointGeo> line){
+          Double minDistance = Double.POSITIVE_INFINITY;
+          IPointGeo closestPoint = null;
+          for(Segment segment : line.getSegments()){
+              IPointGeo projected = projectPoint(point,segment);
+              Double distance = Calculations.getDistance2D(point, projected);
+              if( distance < minDistance ){
+                minDistance = distance;
+                closestPoint = projected;
+              }
+          }
+          return closestPoint;
+     }
+     public static IPointGeo projectPoint  (IPointGeo point, IPolyline<IPointGeo> line, Segment<IPointGeo> onSegment){
+         Double minDistance     = Double.POSITIVE_INFINITY;
+         IPointGeo closestPoint = null;
+         onSegment              = null;
+         //System.out.println("Topology.projectPoint:::# nodes=");
+         for( Segment segment : line.getSegments() ){
+            IPointGeo projected = projectPoint(point,segment);
+            Double distance = Calculations.getDistance2D(point,projected);
+            if ( distance < minDistance ){
+               minDistance  = distance;
+               closestPoint = projected;
+               onSegment    = segment;
+            }
+         }
+         return closestPoint;
+     }
+     public static  PointGeo projectedPoint(IPointGeo point, Double bearing, Double distance){
+         Double lat = Math.asin(Math.sin(Calculations.toRadians(point.getLatitude()))*Math.cos(distance / Calculations.EARTH_RADIOUS)
+                              + Math.cos(Calculations.toRadians(point.getLatitude()))*Math.sin(distance / Calculations.EARTH_RADIOUS) * 
+                                Math.cos(Calculations.toRadians(bearing)));
+          
+         Double lon = Calculations.toRadians(point.getLongitude()) +
+                      Math.atan2(Math.sin(Calculations.toRadians(bearing))*Math.sin(distance/Calculations.EARTH_RADIOUS)*Math.cos(Calculations.toRadians(point.getLatitude())),
+                                 Math.cos(distance / Calculations.EARTH_RADIOUS) - Math.sin(Calculations.toRadians(point.getLatitude()))*Math.sin(lat));
+         return new PointGeo(Calculations.toDegrees(lat),Calculations.toDegrees(lon));
+     }
+     public static boolean intersects(BBox bBox1, BBox bBox2){
+         return !(bBox2.getWest()  > bBox1.getEast()  || bBox2.getEast()  < bBox1.getWest() || 
+                  bBox2.getSouth() > bBox1.getNorth() || bBox2.getNorth() < bBox1.getSouth()
+                 );   
+     }
+     public static Iterable<IPointGeo> getNodesBetweenPoints(IPointGeo from, IPointGeo to, IPolyline<IPointGeo> path){
+           ArrayList<Segment<IPointGeo>> segments = path.getSegments();
+           ArrayList<IPointGeo> result = new ArrayList<IPointGeo>();
+           int fromIndex = -1;
+           int toIndex   = -1;
+           for(int i=0; i< segments.size(); i++){
+              if( Calculations.getDistance2D(from, segments.get(i)) < Calculations.EPS_LENGTH){
+                if (fromIndex > -1 && toIndex > -1 && toIndex <= fromIndex){}
+                else fromIndex = i;
+              }
+              if( Calculations.getDistance2D(to,   segments.get(i)) < Calculations.EPS_LENGTH){
+                if (fromIndex > -1 && toIndex > -1 && toIndex <= fromIndex){}
+                else toIndex = i;              
+              }
+           }
+           if ( fromIndex == -1 || toIndex == -1 ) return result;
+           if ( fromIndex == toIndex -1 ) {
+               result.add(segments.get(fromIndex).getEndPoint());}
+           else if ( fromIndex -1 == toIndex ) {
+               result.add(segments.get(toIndex).getEndPoint());
+           }
+           else if ( fromIndex < toIndex ) {
+               for(int i=fromIndex;i<toIndex;i++){
+                   result.add(segments.get(i).getEndPoint());
+               }
+           }
+           else if ( toIndex < fromIndex ) {
+               for(int i=fromIndex; i > toIndex;i--){
+                  result.add(segments.get(i).getStartPoint());
+               }
+           }
+           return result;
+     }
+}
diff --git a/src/main/java/sml/geoUtils/Vector3.java b/src/main/java/sml/geoUtils/Vector3.java
new file mode 100644
index 0000000..3177d37
--- /dev/null
+++ b/src/main/java/sml/geoUtils/Vector3.java
@@ -0,0 +1,49 @@
+
+package sml.geoUtils;
+
+import sml.geoUtils.geometry.IPointGeo;
+import sml.geoUtils.geometry.PointGeo;
+
+public class Vector3 {
+         Double x;
+         Double y;
+         Double z;
+         public Double getX(){ return x; }
+         public Double getY(){ return y; }
+         public Double getZ(){ return z; }
+         public void setX(Double x){ this.x = x; }
+         public void setY(Double y){ this.y = y; }
+         public void setZ(Double z){ this.z = z; }
+         public Vector3(){}
+         public Vector3(IPointGeo sphericalPoint){
+             Double lat = Math.PI / 2.0 - Calculations.toRadians(sphericalPoint.getLatitude());
+             Double lon = Calculations.toRadians(sphericalPoint.getLongitude());
+             this.x = Math.sin(lat) * Math.cos(lon);
+             this.y = Math.sin(lat) * Math.sin(lon);
+             this.z = Math.cos(lat);
+         }
+         public void normalize(){
+             Double length = Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z);
+             this.x = this.x/length;
+             this.y = this.y/length;
+             this.z = this.z/length;
+         }
+         public void multiply(Double factor){
+            this.x = this.x * factor;
+            this.y = this.y * factor;
+            this.z = this.z * factor;
+         }
+         public PointGeo toSpherical(){
+            PointGeo result = new PointGeo();
+            result.setLatitude(Calculations.toDegrees(Math.PI/2.0 - Math.acos(this.z)));
+            result.setLongitude(Calculations.toDegrees(Math.atan2(this.y, this.x)));
+            return result;
+         }
+         public static Vector3 crossProduct(Vector3 A, Vector3 B){
+            Vector3 crossProd = new Vector3();
+            crossProd.setX(A.getY() * B.getZ() - A.getZ() * B.getY());
+            crossProd.setY(A.getZ() * B.getX() - A.getX() * B.getZ());
+            crossProd.setZ(A.getX() * B.getY() - A.getY() * B.getX());
+            return crossProd;                  
+         } 
+}
diff --git a/src/main/java/sml/geoUtils/geometry/BBox.java b/src/main/java/sml/geoUtils/geometry/BBox.java
new file mode 100644
index 0000000..da1d959
--- /dev/null
+++ b/src/main/java/sml/geoUtils/geometry/BBox.java
@@ -0,0 +1,80 @@
+
+package sml.geoUtils.geometry;
+
+
+public class BBox {
+    private Double north;   
+    private Double south;
+    private Double east;
+    private Double west;
+    private Double minElevation;
+    private Double maxElevation;
+    private boolean initialized;
+    public BBox(){
+       this.initialized = false;
+    }
+    public BBox(Iterable<IPointGeo> pointsToCover){
+       for (IPointGeo pointToCover : pointsToCover){
+           extendToCover(pointToCover);
+       }
+    }
+    public void setNorth(Double north){this.north = north;}
+    public void setSouth(Double south){this.south = south;}
+    public void setWest(Double west)  {this.west  = west;}
+    public void setEast(Double east)  {this.east  = east;}
+    public Double getNorth(){return this.north;}
+    public Double getSouth(){return this.south;}
+    public Double getWest() {return this.west;}
+    public Double getEast() {return this.east;}
+    public final void extendToCover(IPointGeo toCover){
+       if ( !this.initialized ){
+          this.north = toCover.getLatitude();
+          this.south = toCover.getLatitude();
+          this.east  = toCover.getLongitude();
+          this.west  = toCover.getLongitude();
+          
+          this.minElevation = toCover.getElevation();
+          this.maxElevation = toCover.getElevation();
+          this.initialized = true;
+       }
+       this.north = Math.max(this.north, toCover.getLatitude() );
+       this.south = Math.min(this.south, toCover.getLatitude() );
+       this.east  = Math.max(this.east,  toCover.getLongitude());
+       this.west  = Math.min(this.west,  toCover.getLongitude());
+       
+       this.minElevation = Math.min(this.minElevation,toCover.getElevation());
+       this.maxElevation = Math.max(this.maxElevation,toCover.getElevation());
+    }
+    public boolean isInside(IPointGeo point){
+        boolean result = true;
+        if ( this.north < point.getLatitude()  ){ result = false; }
+        if ( this.south > point.getLatitude()  ){ result = false; }
+        if ( this.east  < point.getLongitude() ){ result = false; }
+        if ( this.west  > point.getLongitude() ){ result = false; }
+        if ( this.maxElevation < point.getElevation() ) { result = false; }
+        if ( this.minElevation > point.getElevation() ) { result = false; }
+        return result; 
+    }
+    public boolean isInside2D(IPointGeo point){
+        boolean result = true;
+        if(  this.north < point.getLatitude()  ){ result = false; }
+        if ( this.south > point.getLatitude()  ){ result = false; }
+        if ( this.east  < point.getLongitude() ){ result = false; }
+        if ( this.west  > point.getLongitude() ){ result = false; }
+        return result; 
+    }
+    public void inflate(Double dLat, Double dLon){
+         this.north = this.north + dLat;
+         this.south = this.south - dLat;
+         this.east  = this.east  + dLon;
+         this.west  = this.west  - dLon;
+    }
+    public PointGeo[] getCorners(){
+        PointGeo[] corners = new PointGeo[4];
+        corners[0] = new PointGeo(this.getNorth(), this.getWest());
+        corners[1] = new PointGeo(this.getNorth(), this.getEast());
+        corners[2] = new PointGeo(this.getSouth(), this.getEast());
+        corners[3] = new PointGeo(this.getSouth(), this.getWest());
+        return corners;
+    }
+}
diff --git a/src/main/java/sml/geoUtils/geometry/IPointGeo.java b/src/main/java/sml/geoUtils/geometry/IPointGeo.java
new file mode 100644
index 0000000..952b258
--- /dev/null
+++ b/src/main/java/sml/geoUtils/geometry/IPointGeo.java
@@ -0,0 +1,11 @@
+
+package sml.geoUtils.geometry;
+
+public interface IPointGeo {
+    public Double getLatitude();
+    public Double getLongitude();
+    public Double getElevation();
+    public void setLatitude(Double lat);
+    public void setLongitude(Double lon);
+    public void setElevation(Double ele);
+}
diff --git a/src/main/java/sml/geoUtils/geometry/IPolyline.java b/src/main/java/sml/geoUtils/geometry/IPolyline.java
new file mode 100644
index 0000000..b2804a3
--- /dev/null
+++ b/src/main/java/sml/geoUtils/geometry/IPolyline.java
@@ -0,0 +1,12 @@
+
+package sml.geoUtils.geometry;
+
+import java.util.ArrayList;
+public interface IPolyline<T extends IPointGeo> {
+    public ArrayList<T> getNodes();
+    public ArrayList<Segment<T>> getSegments();
+    public Double getLength();
+    public int getNodeCount();
+}
+
+
diff --git a/src/main/java/sml/geoUtils/geometry/PointGeo.java b/src/main/java/sml/geoUtils/geometry/PointGeo.java
new file mode 100644
index 0000000..28f3e33
--- /dev/null
+++ b/src/main/java/sml/geoUtils/geometry/PointGeo.java
@@ -0,0 +1,34 @@
+
+package sml.geoUtils.geometry;
+
+
+public class PointGeo implements IPointGeo {
+    Double latitude  = 0.0;
+    Double longitude = 0.0;
+    Double elevation = 0.0;
+    public PointGeo(){
+    }
+    public PointGeo(Double lat, Double lon){
+       this.latitude  = lat;
+       this.longitude = lon;
+       this.elevation = 0.0;
+    }
+    public PointGeo(Double lat, Double lon, Double ele){
+       this.latitude  = lat;
+       this.longitude = lon;
+       this.elevation = ele;
+    }
+    public Double getLatitude() { return this.latitude; }
+    public Double getLongitude(){ return this.longitude;}
+    public Double getElevation(){ return this.elevation;}
+    
+    public void setLatitude(Double lat) { this.latitude  = lat;}
+    public void setLongitude(Double lon){ this.longitude = lon;}
+    public void setElevation(Double ele){ this.elevation = ele;}
+    @Override
+    public String toString(){
+        String result;
+        result = "lat:"+this.latitude+" lon:"+this.longitude;
+        return result;
+    }
+}
diff --git a/src/main/java/sml/geoUtils/geometry/Polyline.java b/src/main/java/sml/geoUtils/geometry/Polyline.java
new file mode 100644
index 0000000..f02dbe4
--- /dev/null
+++ b/src/main/java/sml/geoUtils/geometry/Polyline.java
@@ -0,0 +1,66 @@
+
+package sml.geoUtils.geometry;
+
+import java.util.ArrayList;
+import sml.geoUtils.Calculations;
+import sml.gps.datamodel.GPSPoint;
+
+
+public class Polyline<T extends IPointGeo> implements IPolyline<T> {
+
+    ArrayList<Segment<T>> segments;
+    ArrayList<T>          nodes;
+    Double                length;
+    public Polyline(){
+        this.nodes = new ArrayList<T>();
+        //this.nodes.
+        this.invalidateComputedProperties(null);
+    
+    }
+    private void invalidateComputedProperties(Object sender){
+        this.length = Double.NaN;
+        this.segments = null;
+    }
+
+    public Double getLength(){
+        if (Double.isNaN(this.length)){
+            this.length = Calculations.getLength((IPolyline<IPointGeo>)this);
+        }
+        Double dist = 0.0;
+        if ( this.nodes.size() > 1 ){
+            T oldNode = this.nodes.get(0);
+            for(int i=1;i<this.nodes.size();i++){
+                T node = this.nodes.get(i); 
+                //System.out.println("\t\t i="+i+" node="+this.nodes.get(i).getLatitude()+","+this.nodes.get(i).getLongitude());
+                dist = dist + Calculations.getDistance2D(oldNode,node);
+                oldNode = node;
+            }
+        }
+        this.length = dist/1000.0;
+        return this.length;
+    }
+    public int getNodeCount(){
+        return this.nodes.size();
+    }
+    public ArrayList<Segment<T>> getSegments(){
+        ArrayList<Segment<T>> result = new ArrayList<Segment<T>>();
+        boolean ltrace = true;
+        if ( false ){
+            System.out.println("# nodes="+this.nodes.size());
+            for(int i=0; i < this.nodes.size()-1;i++){
+                System.out.println("about to create segment from <"+this.nodes.get(i).getLatitude()+","+this.nodes.get(i).getLongitude()+
+                                                    "> -- <"+this.nodes.get(i+1).getLatitude()+","+this.nodes.get(i+1).getLongitude()+">");
+            }
+        }
+        for(int i=0; i < this.nodes.size()-1;i++){
+            result.add(new Segment<T>(this.nodes.get(i), this.nodes.get(i+1)));
+        }
+        return result;
+    }
+    public ArrayList<T> getNodes() {
+        return this.nodes;
+    }
+    public void setNodes(ArrayList<T> newNodes){
+        this.nodes = newNodes;
+    }
+}
diff --git a/src/main/java/sml/geoUtils/geometry/Segment.java b/src/main/java/sml/geoUtils/geometry/Segment.java
new file mode 100644
index 0000000..cb730f9
--- /dev/null
+++ b/src/main/java/sml/geoUtils/geometry/Segment.java
@@ -0,0 +1,39 @@
+
+package sml.geoUtils.geometry;
+
+import sml.geoUtils.Calculations;
+
+public class Segment<T extends IPointGeo> {
+    T startPoint;
+    T endPoint;
+    Double length;
+    public Segment(T start, T end){
+        this.startPoint = start;
+        this.endPoint   = end;
+        this.length     = Calculations.getDistance2D(this.startPoint,this.endPoint);
+    }
+    public T getStartPoint(){
+       return this.startPoint;
+    }
+    private void setStartPoint(T startPoint){
+       this.startPoint = startPoint;
+    }
+    public T getEndPoint(){
+       return this.endPoint;
+    }
+    private void setEndPoint(T endPoint){
+       this.endPoint = endPoint;
+    }
+    public Double getLength(){
+       return this.length;
+    }
+    public boolean equals(Object obj){
+         boolean result = false;
+         Segment<T> other = (Segment<T>)obj;
+         if (other != null){
+             result = this.startPoint.equals(other.startPoint) && this.endPoint.equals(other.endPoint);
+         }
+         
+         return result;
+    }
+}
diff --git a/src/main/java/sml/gps/datamodel/GPSPoint.java b/src/main/java/sml/gps/datamodel/GPSPoint.java
new file mode 100644
index 0000000..3d842d8
--- /dev/null
+++ b/src/main/java/sml/gps/datamodel/GPSPoint.java
@@ -0,0 +1,222 @@
+
+package sml.gps.datamodel;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import sml.geoUtils.geometry.IPointGeo;
+
+public class GPSPoint implements IPointGeo {
+    private Long     time;
+    private Double   latitude;
+    private Double   longitude;
+    private Double   elevation;
+    private Double   speed;
+    private String   name;
+    private String   description;
+    private String   commenet;
+    private Double   orientation;
+    private Double   accuracy;
+    private int      altitudeAccuracy;
+    private Double   heading;    
+    private String   id ="";
+    private String   type;
+    private String   mac;
+    private String   cellId;
+    private int      areaId;
+    private Double   maxSpeed = -999.999;
+    private int      tag = -1;
+    final static String PRETEXT = "sml.gps.datamodel.GPSPoint";
+    DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+    public GPSPoint(String id, Double lat, Double lon){
+        this.id        = id;
+        this.latitude  = lat;
+        this.longitude = lon;
+        this.elevation =-999.999;
+        this.time      = Long.MIN_VALUE;
+        this.orientation      = -999.999;
+        this.accuracy         = -999.999;
+        this.altitudeAccuracy = -999;
+        this.heading          = -999.999;        
+    }    
+    
+    public GPSPoint(Double lat, Double lon){
+        this.latitude  = lat;
+        this.longitude = lon;
+        this.elevation =-999.999;
+        this.time      = Long.MIN_VALUE;
+        this.orientation      = -999.999;
+        this.accuracy         = -999.999;
+        this.altitudeAccuracy = -999;
+        this.heading          = -999.999;        
+    }
+    public GPSPoint(Double lat, Double lon, Long tim){
+        super();
+        this.latitude  = lat;
+        this.longitude = lon;
+        this.elevation = -999.999;
+        this.speed     = -999.999;
+        this.time      = tim;
+        this.orientation      = -999.999;
+        this.accuracy         = -999.999;
+        this.altitudeAccuracy = -999;
+        this.heading          = -999.999;
+
+    }
+    /*
+    public GPSPoint(Double lat, Double lon, Long tim, Double elevation){
+        this.latitude  = lat;
+        this.longitude = lon;
+        this.elevation = elevation;
+        this.time      = tim;
+    }*/
+    public GPSPoint(Long time, Double lat, Double lon, Double speed){
+        super();
+        this.time             = time;
+        this.latitude         = lat;
+        this.longitude        = lon;
+        this.elevation        = -999.999;
+        this.speed            = speed;
+        this.orientation      = -999.999;
+        this.accuracy         = -999.999;
+        this.altitudeAccuracy = -999;
+        this.heading          = -999.999;
+   }
+    public GPSPoint(Long time, Double lat, Double lon, Double alt, Double speed, Double ori, Double acc, int altAcc, Double hea){
+        super();     
+        this.time             = time;
+        this.latitude         = lat;
+        this.longitude        = lon;
+        this.elevation        = alt;
+        this.speed            = speed;
+        this.orientation      = ori;
+        this.accuracy         = acc;
+        this.altitudeAccuracy = altAcc;
+        this.heading          = hea;
+    }
+    @Override
+    public void   setLatitude(Double lat) {this.latitude = lat;}
+    @Override
+    public Double getLatitude()           {return this.latitude;}
+    @Override
+    public void   setLongitude(Double lon){this.longitude = lon;}
+    @Override
+    public Double getLongitude()          {return this.longitude;}
+    @Override
+    public void   setElevation(Double ele){this.elevation = ele;}
+    @Override
+    public Double getElevation()          {return this.elevation;}   
+    public void   setTime(Long tim)       {this.time = tim;}
+    public Long   getTime()               {return this.time;}  
+    public String getTimeString()         {
+        String out = "--";
+        if ( time != Long.MIN_VALUE ){
+           Date dd = new Date(this.time);
+           out = df.format(dd);
+        }
+        return out;
+    }
+    public String getDescription()        {
+        String out = id;
+        if ( time != Long.MIN_VALUE ){
+            Date dd = new Date(this.time);
+            out = df.format(dd);
+        }
+        if ( this.tag == -1 ){
+            if ( this.elevation > -900.00 ){
+                out = out +":["+String.format("%.2f", this.elevation)+"] <"+this.latitude+","+this.longitude+">";
+            }
+            else{
+                out = out + ":<"+this.latitude+","+this.longitude+">";
+            }
+        }
+        else{
+            out = out +":["+this.tag+"] <"+this.latitude+","+this.longitude+">";
+        }
+        if ( speed != null && speed > -900.0 ){
+           out = out + " ("+String.format("%.2E", this.speed)+")";
+        }
+        
+        return out; 
+    }   
+    public void   setSpeed(Double speed)  {this.speed = speed; }
+    public Double getSpeed()              {return this.speed;  }
+        public GPSPoint copy(){
+        GPSPoint newPnt = new GPSPoint(this.getLatitude(),this.getLongitude());
+        newPnt.setAccuracy(this.accuracy);
+        newPnt.setElevation(this.elevation);
+        newPnt.setId(this.id);
+        newPnt.setMaxSpeed(this.maxSpeed);
+        newPnt.setSpeed(this.speed);
+        newPnt.setTime(this.time);
+        newPnt.setType(this.type);
+        newPnt.setMac(this.mac);
+        newPnt.setCellId(this.cellId);
+        newPnt.setAreaId(this.areaId);
+        return newPnt;
+    }
+    public Double getAccuracy() {
+        return accuracy;
+    }
+
+    public void setAccuracy(Double accuracy) {
+        this.accuracy = accuracy;
+    }
+    public String getCellId() {
+        return cellId;
+    }
+
+    public void setCellId(String cellId) {
+        this.cellId = cellId;
+    }
+    public int getAreaId() {
+        return areaId;
+    }
+
+    public void setAreaId(int areaId) {
+        this.areaId = areaId;
+    }
+    public String getMac() {
+        return mac;
+    }
+
+    public void setMac(String mac) {
+        this.mac = mac;
+    }
+    public String getType()               {return this.type;}
+    public void   setType(String type)    {this.type = type;}
+
+    public Double getMaxSpeed() {
+        return maxSpeed;
+    }
+
+    public void setMaxSpeed(Double maxSpeed) {
+        this.maxSpeed = maxSpeed;
+    }
+
+    private void setId(String id) {
+        this.id = id;
+    }
+    
+    public void changeTag(){
+        if ( false ) System.out.println(PRETEXT+".changeTag()::: initial Tag="+this.tag);
+        switch (this.tag) {
+            case -1:
+                this.tag = 0;
+                break;
+            case 0:
+                this.tag = 1;
+                break;
+            case 1:
+                this.tag = -1;
+                break;
+            default:
+                break;
+        }
+        if ( false ) System.out.println(PRETEXT+".changeTag()::: final Tag="+this.tag);
+    }
+    
+    public int getTag(){
+        return this.tag;
+    }
+}
diff --git a/src/main/java/sml/gps/datamodel/GPSPoints.java b/src/main/java/sml/gps/datamodel/GPSPoints.java
new file mode 100644
index 0000000..d6d9814
--- /dev/null
+++ b/src/main/java/sml/gps/datamodel/GPSPoints.java
@@ -0,0 +1,99 @@
+package sml.gps.datamodel;
+
+import java.awt.Color;
+import java.util.ArrayList;
+import sml.visualizer.datamodel.Register;
+
+
+public class GPSPoints implements Register {
+    String name;
+    Color  lineColor = Color.BLUE;
+    int    lineWidth = 1;
+    Color  symbolColor = Color.BLUE;
+    int    symbolSize = 4;    
+    public ArrayList<GPSPoint> pnts = new ArrayList<>();
+    @Override
+    public void   setName(String name) {this.name = name;}
+    @Override
+    public String getName()            {return this.name;}
+    @Override
+    public void   setLineColor(Color color){this.lineColor = color;}
+    @Override
+    public Color  getLineColor()           {return this.lineColor;}
+
+    @Override
+    public int getLineWidth() {
+        return lineWidth;
+    }
+
+    @Override
+    public void setLineWidth(int width) {
+        this.lineWidth = width;
+    }
+    
+    @Override
+    public int getSymbolSize() {
+        return symbolSize;
+    }
+
+    @Override
+    public void setSymbolSize(int size) {
+        this.symbolSize = size;
+    }
+    public ArrayList<GPSPoint> getPnts() {
+        return pnts;
+    }
+
+    public void setPnts(ArrayList<GPSPoint> pnts) {
+        this.pnts = pnts;
+    }
+    
+    public void add(GPSPoint pnt){
+        this.pnts.add(pnt);
+    }
+
+    @Override
+    public int getNodeCount() {
+        return this.pnts.size();
+    }
+
+    @Override
+    public GPSPoint getNode(int i) {
+        return this.pnts.get(i);
+    }
+
+    @Override
+    public int getType() {
+        return 0;
+    }
+    
+    @Override
+    public GPSRoute getGPSRoute(){
+        return null;
+    }
+    
+    @Override
+    public GPSPoints getGPSPoints(){
+        return this;
+    }
+    
+    @Override
+    public void remove(int i){
+        this.getPnts().remove(i);
+    }    
+    
+    @Override
+    public ArrayList<GPSPoint> getArrayNodes(){
+        return this.getPnts();
+    }    
+
+    @Override
+    public void setSymbolColor(Color color) {
+        this.symbolColor = color;
+    }
+
+    @Override
+    public Color getSymbolColor() {
+        return this.symbolColor;
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/sml/gps/datamodel/GPSRoute.java b/src/main/java/sml/gps/datamodel/GPSRoute.java
new file mode 100644
index 0000000..43fef6f
--- /dev/null
+++ b/src/main/java/sml/gps/datamodel/GPSRoute.java
@@ -0,0 +1,191 @@
+package sml.gps.datamodel;
+
+import java.awt.Color;
+import java.util.ArrayList;
+import sml.geoUtils.Calculations;
+import sml.geoUtils.geometry.Polyline;
+import sml.visualizer.datamodel.Register;
+
+public class GPSRoute extends Polyline<GPSPoint> implements Register {
+    String name;
+    Color  lineColor = Color.BLUE;
+    int    lineWidth = 1;
+    Color  symbolColor = Color.BLUE;
+    int    symbolSize = 1;
+    
+    public GPSRoute(){}
+    @Override
+    public void   setName(String name) {this.name = name;}
+    @Override
+    public String getName()            {return this.name;}
+    @Override
+    public void   setLineColor(Color color){this.lineColor = color;}
+    @Override
+    public Color  getLineColor()           {return this.lineColor;}
+
+    @Override
+    public void setLineWidth(int width) {
+        this.lineWidth = width;
+    }
+
+    @Override
+    public int getLineWidth() {
+        return lineWidth;
+    }
+
+    @Override
+    public GPSPoint getNode(int i) {
+        return this.getNodes().get(i);
+    }
+
+    @Override
+    public int getType() {
+        return 1;
+    }
+    @Override
+    public GPSRoute getGPSRoute(){
+        return this;
+    }
+    
+    @Override
+    public GPSPoints getGPSPoints(){
+        return null;
+    }    
+    @Override
+    public void remove(int i){
+        this.getNodes().remove(i);
+    }
+    @Override
+    public ArrayList<GPSPoint> getArrayNodes(){
+        return this.getNodes();
+    }       
+
+    @Override
+    public void setSymbolColor(Color color) {
+        this.symbolColor = color;
+    }
+
+    @Override
+    public Color getSymbolColor() {
+        return this.symbolColor;
+    }
+
+    @Override
+    public void setSymbolSize(int size) {
+        this.symbolSize = size;
+    }
+
+    @Override
+    public int getSymbolSize() {
+        return this.symbolSize;
+    }
+    @Override
+    public GPSRoute clone(){
+        GPSRoute newRoute = new GPSRoute();
+        for(int i=0;i<this.getNodes().size();i++){
+           newRoute.getNodes().add(this.getNodes().get(i));
+        }
+        return newRoute;
+    }
+    
+    public Double getDeltaTime(){
+        GPSPoint ini = this.getNodes().get(0);
+        GPSPoint end = this.getNodes().get(this.getNodeCount()-1);
+        Double delta = (end.getTime() - ini.getTime())/(1000.0*60.0);
+        return delta;
+    }
+    
+    public void orderNodes(){
+        ArrayList<GPSPoint> newNodes = new ArrayList<>();
+        for(int i=0;i<this.getNodeCount();i++){
+            GPSPoint pnt = this.getNodes().get(i);
+            Long t = pnt.getTime();
+            if ( newNodes.size() > 0 ){
+                if ( t > newNodes.get(newNodes.size()-1).getTime() ){
+                    newNodes.add(pnt);
+                }
+                else{
+                    int j = 0;
+                    for (GPSPoint pt : newNodes) {
+                        if ( t < pt.getTime() ){ break; }
+                        j = j + 1;
+                    }
+                    newNodes.add(j,pnt);
+                }
+            }
+            else{
+                newNodes.add(pnt);
+            }
+        }
+        this.setNodes(newNodes);
+    
+    }
+    public void testOrder(){
+        for (int i=1;i<this.getNodeCount();i++){
+            Long ots = this.getNodes().get(i-1).getTime();
+            Long ts  = this.getNodes().get(i).getTime();
+                if ( ts < ots  ){
+                  System.out.println("PSRoute.testOrder:::: id="+this.getName()+" i="+i+"/"+this.getNodeCount()+" ts="+ts+" ots="+ots);
+                }
+        }
+    
+    }
+    public Double getSpeed(){
+        GPSPoint last = this.getNodes().get(0);
+        Double avgSpeed = 0.0;
+        for(int i=1;i<this.getNodeCount();i++){
+            GPSPoint node = this.getNodes().get(i);
+            Double dist = Calculations.getDistance2D(node, last)/1000.0;
+            Double delta = (node.getTime() - last.getTime())/(1000.0*60.0*60);
+            Double speed = dist/delta;
+            node.setSpeed(speed);
+            last = node;
+            avgSpeed = avgSpeed + speed;
+        }
+        return avgSpeed/(1.0*this.getNodeCount());
+        
+    }
+    public void setSpeed(){
+        GPSPoint last = this.getNodes().get(0);
+        for(int i=1;i<this.getNodeCount();i++){
+            GPSPoint node = this.getNodes().get(i);
+            Double dist = Calculations.getDistance2D(node, last)/1000.0;
+            Double delta = (node.getTime() - last.getTime())/(1000.0*60.0*60);
+            Double speed = dist/delta;
+            node.setSpeed(speed);
+            last = node;
+        }    
+    }
+    public Double getIniEndLength(){
+       GPSPoint ini = this.getNodes().get(0);
+       GPSPoint end = this.getNodes().get(this.getNodeCount()-1);
+       Double ieLength = Calculations.getDistance2D(ini, end)/1000.0;
+       return ieLength;
+    } 
+    
+    public GPSPoint getInterpolatePnt(Long time){
+        if ( this.getNode(0).getTime() > time ) { return new GPSPoint(-1000.0,-1000.0,time);};
+        if ( this.getNode(this.getNodeCount()-1).getTime() < time ) {return new GPSPoint(-1000.0,-1000.0,time);};
+        int i;
+        for(i=0;i<getNodeCount();i++){
+            if ( getNode(i).getTime() > time ){
+                break;
+            }
+        }
+        GPSPoint pntIm1 = this.getNode(i-1);
+        GPSPoint pntI   = this.getNode(i);
+        Double   dTIm1  = (time-pntIm1.getTime())*1.0;
+        Double   dTI    = (pntI.getTime()-time)*1.0;
+        Double   dTIIm1 = pntI.getTime()-pntIm1.getTime()*1.0;
+        Double   latIm1 = pntIm1.getLatitude();
+        Double   latI   = pntI.getLatitude();
+        Double   lonIm1 = pntIm1.getLongitude();
+        Double   lonI   = pntI.getLongitude();
+        Double   dLat   = latI - latIm1;
+        Double   dLon   = lonI - lonIm1; 
+        Double   lat    = ( latI * dTIm1 + latIm1 * dTI ) / dTIIm1;
+        Double   lon    = ( lonI * dTIm1 + lonIm1 * dTI ) / dTIIm1;
+        GPSPoint pnt = new GPSPoint(lat,lon,time);
+        return pnt;
+    }
+}
diff --git a/src/main/java/sml/gps/datamodel/GPSSegment.java b/src/main/java/sml/gps/datamodel/GPSSegment.java
new file mode 100644
index 0000000..4b6f6cc
--- /dev/null
+++ b/src/main/java/sml/gps/datamodel/GPSSegment.java
@@ -0,0 +1,21 @@
+
+package sml.gps.datamodel;
+
+import sml.geoUtils.geometry.IPointGeo;
+import sml.geoUtils.geometry.Segment;
+
+public class GPSSegment extends Segment<IPointGeo> {
+    GPSPoint start;
+    GPSPoint end;
+    public GPSSegment(GPSPoint start, GPSPoint end){
+        super(start,end);
+        this.start = start;
+        this.end   = end;
+    }
+    public Double getTravelTime(){
+        return  (this.start.getTime() - this.end.getTime())/1000.0/60.0/60.0;
+    }
+    public Double getAverageSpeed(){
+        return this.getLength()/1000.0/this.getTravelTime();
+    }
+}
diff --git a/src/main/java/sml/gps/datamodel/GPSTrack.java b/src/main/java/sml/gps/datamodel/GPSTrack.java
new file mode 100644
index 0000000..67afa2b
--- /dev/null
+++ b/src/main/java/sml/gps/datamodel/GPSTrack.java
@@ -0,0 +1,20 @@
+
+package sml.gps.datamodel;
+
+import java.util.ArrayList;
+
+
+public class GPSTrack {
+    String name;
+    ArrayList<GPSTrackSegment> segments;
+    public GPSTrack(){
+        this.segments = new ArrayList<GPSTrackSegment>();
+    }
+    public GPSTrack(String name){
+        this.segments = new ArrayList<GPSTrackSegment>();
+        this.name = name;
+    }
+    public String getName(){return this.name;}
+    public void setName(String name){this.name = name;}
+    public ArrayList<GPSTrackSegment> getSegments(){return this.segments;}
+}
diff --git a/src/main/java/sml/gps/datamodel/GPSTrackSegment.java b/src/main/java/sml/gps/datamodel/GPSTrackSegment.java
new file mode 100644
index 0000000..01d9ee9
--- /dev/null
+++ b/src/main/java/sml/gps/datamodel/GPSTrackSegment.java
@@ -0,0 +1,17 @@
+package sml.gps.datamodel;
+
+import java.util.ArrayList;
+import sml.geoUtils.geometry.Polyline;
+
+
+public class GPSTrackSegment extends Polyline<GPSPoint> {
+    public GPSTrackSegment(){
+        super();
+    }
+    public GPSTrackSegment(ArrayList<GPSPoint> points){
+        for(GPSPoint point : points){
+           super.getNodes().add(point);
+        }
+    }
+
+}
diff --git a/src/main/java/sml/stops/datamodel/StopData.java b/src/main/java/sml/stops/datamodel/StopData.java
new file mode 100644
index 0000000..b626cf0
--- /dev/null
+++ b/src/main/java/sml/stops/datamodel/StopData.java
@@ -0,0 +1,427 @@
+package sml.stops.datamodel;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.PrintWriter;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import sml.geoUtils.Calculations;
+import sml.gps.datamodel.GPSPoint;
+import sml.utils.GeoDistance;
+
+public class StopData {
+   
+   boolean ltrace = false; 
+   private GPSPoint centroidNode;
+   private ArrayList<GPSPoint> pnts = new ArrayList<>();
+   
+   private Double      radious = 0.0;
+   private Long        iniTime;
+   private Long        endTime;
+   private Double      maxDist;
+   private String      nextStopId;
+   
+   
+   public StopData()           {
+       //APos         = new ArrayList<ArrayList<Double>>();
+       //ATimes       = new ArrayList<Long>();
+       //centroidNode = new ArrayList<Double>();    
+       pnts         = new ArrayList<>();
+   }  
+   public ArrayList<GPSPoint> getPnts(){
+        return this.pnts;
+   }
+   public StopData(StopData cd){
+        this.pnts = new ArrayList<>();
+        for(int i=0;i<cd.getPnts().size();i++){
+            GPSPoint cdNode = cd.getPnts().get(i);
+            GPSPoint newNode = cdNode.copy();
+            this.pnts.add(newNode);
+        }
+   }
+   
+   private void                        calcCentroid()    {
+
+        Double centroid_lat = 0.0;
+        Double centroid_lon = 0.0;
+        for(int i=0;i<this.pnts.size();i++){
+           centroid_lat = centroid_lat + this.pnts.get(i).getLatitude();
+           centroid_lon = centroid_lon + this.pnts.get(i).getLongitude();
+        }
+        centroid_lat = centroid_lat / this.pnts.size();
+        centroid_lon = centroid_lon / this.pnts.size();
+        this.centroidNode = new GPSPoint(centroid_lat,centroid_lon);
+   }
+   private void                        calcRadious()     {
+       this.calcCentroid();
+       Double dist = -1.0;
+       for (int i=0;i<this.pnts.size();i++){
+            Double newDist = Calculations.getDistance2D(this.centroidNode,this.pnts.get(i));
+            if ( newDist > dist ) dist = newDist;
+       }
+       this.radious = dist;
+   }
+   private void                        calcMaxDistance() {
+       Double dist = -1.0;
+       for (int i=0;i<this.pnts.size();i++){
+           for (int j=i+1;j<this.pnts.size();j++){
+               Double newDist = Calculations.getDistance2D(this.pnts.get(j),this.pnts.get(i));
+               if ( newDist > dist ) dist = newDist;
+           }
+       }
+       this.maxDist = dist;
+   }
+   private void                        calcIniTime()     {
+       this.iniTime = Long.MAX_VALUE;
+          for(int i=0;i<this.pnts.size();i++){if ( pnts.get(i).getTime() < this.iniTime) this.iniTime = pnts.get(i).getTime();
+       }
+       if ( this.pnts.get(0).getTime() != this.iniTime ){
+          System.out.println("ClusteringData id="+this.iniTime+" not ordered in time ATimes.get(0)="+this.pnts.get(0).getTime()+", iniTime="+this.iniTime);
+       }
+       this.iniTime = this.pnts.get(0).getTime();
+   }
+   private void                        calcEndTime()     {
+       this.endTime = Long.MIN_VALUE;
+       for(int i=0;i<this.pnts.size();i++){if ( this.pnts.get(i).getTime() > this.endTime) this.endTime = this.pnts.get(i).getTime();}
+       if ( this.endTime-this.pnts.get(this.pnts.size()-1).getTime() != 0 ){
+          System.out.println("ClusteringData id="+this.iniTime+" not ordered in time ATimes.get(last)="+this.pnts.get(this.pnts.size()-1).getTime()+", endTime="+this.endTime);
+       }
+       this.endTime = this.pnts.get(this.pnts.size()-1).getTime();
+   }     
+   public void                         addNode(GPSPoint newNode)                    {
+       if ( ltrace ) System.out.println("addNode::in StopData "+this.iniTime+" size="+this.pnts.size());
+       boolean done = false;
+       for(int i=0; i<this.pnts.size();i++){
+           if ( this.pnts.get(i).getTime() == newNode.getTime() ){
+               this.pnts.set(i,newNode);
+               done = true;
+               break;
+           }
+           else if ( this.pnts.get(i).getTime() >  newNode.getTime() ){
+               this.pnts.add(i, newNode);
+
+               done = true;
+               break;
+           }
+       }
+       if ( !done ){
+          this.pnts.add(newNode);
+       }
+       calcCentroid();
+       calcRadious();
+       if ( ltrace ) System.out.println("addNode::in ClusterData "+this.iniTime+" size="+this.pnts.size());
+   }
+   public void                         addNode(GPSPoint newNode, boolean beginning) {
+       if ( ltrace ) System.out.println("addNode::in ClusterData "+this.iniTime+" size="+this.pnts.size());
+          if ( beginning ){
+              this.pnts.add(0,newNode);
+          }
+          else{
+              this.pnts.add(newNode);          
+          }
+          calcCentroid();
+          calcRadious();
+       if ( ltrace ) System.out.println("addNode::in ClusterData "+this.iniTime+" size="+this.pnts.size());
+   }
+   public void                         writeToFile()     {
+        try {
+            Long ini = this.getIniTime();
+            Date d = new Date(ini*1000);
+            Calendar cal = null;
+            cal = Calendar.getInstance();
+            cal.setTime(d);
+            String dirName = cal.get(Calendar.YEAR)+"_"+(cal.get(Calendar.MONTH)+1)+"_"+ cal.get(Calendar.DAY_OF_MONTH);
+            File theDir = new File(dirName);
+            if ( ltrace )
+               System.out.println("ClusterData.writeToFile::: theDir="+theDir+" ini="+getIniTime());
+            if (!theDir.exists()) {
+               if ( ltrace )
+               System.out.println("Creating directory: " + dirName);
+               boolean result = theDir.mkdir();  
+               if ( result && ltrace ) {System.out.println("DIR created");}
+            }
+            File theFile = new File(dirName+"/"+this.getIniTime()+".csv");
+            PrintWriter writer  = new PrintWriter(theFile, "UTF-8");
+            writer.println(this.iniTime+","+this.nextStopId);
+            for (int i=0;i<this.pnts.size();i++){
+                GPSPoint node = this.pnts.get(i);
+                writer.println(node.getTime()+","+node.getLatitude()+","+node.getLongitude());
+            }
+            writer.close();
+        } catch (FileNotFoundException ex) {
+            Logger.getLogger(StopData.class.getName()).log(Level.SEVERE, null, ex);
+        } catch (UnsupportedEncodingException ex) {
+            Logger.getLogger(StopData.class.getName()).log(Level.SEVERE, null, ex);
+        }
+   
+   }
+   public String                       getId()           {return this.iniTime+"";}
+   public String                       getInfo()         {
+        String info = "#################\n";
+        info = info + " id   ="+this.getId()+"\n";
+        info = info + " size ="+this.getSize()+"\n";
+        info = info + " iTime="+this.getIniTime()+"\n";
+        int totalSecs = (int) (this.getEndTime()-this.getIniTime());
+        int hours    = Math.round(totalSecs/3600);
+        int partSecs = totalSecs - hours*3600;
+        int mins     = Math.round(partSecs/60);
+        int secs     = partSecs - mins*60;
+        info = info + " ini Date="+new Date( getIniTime()* 1000 ).toString()+"\n";
+        info = info + " ini Hour="+this.getIniTimeOfDay()+"\n";
+        info = info + " end Date="+new Date( getEndTime()* 1000 ).toString()+"\n";
+        info = info + " end Hour="+this.getEndTimeOfDay()+"\n";
+        info = info + " DeltaT="+hours+"h"+mins+"m"+secs+"s\n";
+        info = info + " Next Clus id="+this.nextStopId+"\n";
+        info = info + " Centroid=<"+this.getCentroid().getLatitude()+","+this.getCentroid().getLongitude()+">\n";
+        info = info + " Radious="+getRadious()+"\n";
+        info = info + "#################\n";
+        return info;
+   }      
+   public int                          getSize()         {return this.pnts.size();}
+   public Long                         getIniTime()      {this.calcIniTime();return this.iniTime;}
+   public Long                         getEndTime()      {this.calcEndTime();return this.endTime;}
+   public Long                         getDeltaTime()    {
+       Long DeltaTime = (this.getEndTime()-this.getIniTime())/1000;
+       return DeltaTime;
+   }
+   public Date                         getIniDate()      {return new Date( this.getIniTime()* 1000 );}
+   public Date                         getEndDate()      {return new Date( this.getEndTime()* 1000 );}
+   public int                          getIniTimeOfDay() {
+       Calendar cal = Calendar.getInstance();
+       cal.setTime(getIniDate());
+       int hour = cal.get(Calendar.HOUR_OF_DAY);
+       int min  = cal.get(Calendar.MINUTE);
+       int sec  = cal.get(Calendar.SECOND); 
+       int timeOfDay = hour*60*60+min*60+sec;
+       return timeOfDay;
+   }
+   public int                          getEndTimeOfDay() {
+       Calendar cal = Calendar.getInstance();
+       cal.setTime(getEndDate());
+       int hour = cal.get(Calendar.HOUR_OF_DAY);
+       int min  = cal.get(Calendar.MINUTE);
+       int sec  = cal.get(Calendar.SECOND); 
+       int timeOfDay = hour*60*60+min*60+sec;
+       return timeOfDay;
+   }   
+   public Date                         getIniDayDate()   {
+        return new Date((this.getIniTime()-getIniTimeOfDay())*1000); 
+   }
+   public Date                         getEndDayDate()   {
+        return new Date((this.getEndTime()-getEndTimeOfDay())*1000); 
+   }
+   public int                          getIniWeekDay()   {
+        Calendar c = Calendar.getInstance();
+        c.setTime(this.getIniDate());
+        int dayOfWeek = c.get(Calendar.DAY_OF_WEEK);
+        return dayOfWeek;
+   }
+   public int                          getEndWeekDay()   {
+        Calendar c = Calendar.getInstance();
+        c.setTime(this.getEndDate());
+        int dayOfWeek = c.get(Calendar.DAY_OF_WEEK);
+        return dayOfWeek;
+   }
+   public GPSPoint                     getCentroid()     {this.calcCentroid();return this.centroidNode;}
+   public Double                       getMaxDist()      {this.calcMaxDistance(); return this.maxDist;}
+   public Double                       getRadious()      {this.calcRadious();return this.radious;}
+   public String                       getNextStop()     {
+       return this.nextStopId;
+   }
+   public void      setNextStop(String nextStopId)                     {
+       this.nextStopId = nextStopId;
+   }
+   public boolean   compare(StopData otherClus, double maxDistance)    {
+        boolean joinable = false;
+        if ( Calculations.getDistance2D(this.getCentroid(),otherClus.getCentroid()) < maxDistance ){
+            // &&   (otherClus.getIniTime()- this.getEndTime()) < 60*5 ){
+           joinable = true;
+        }
+        return joinable;
+   }
+   public StopData  joinTwoStopData   (StopData otherStop)             {
+        StopData newStop = new StopData();
+        if ( ltrace ) System.out.println("joinTwoStopData:::stop1.size="+this.getSize()+" stop2.size="+otherStop.getSize());
+        for (int i=0;i<this.getSize();i++){
+            newStop.addNode(this.getPnts().get(i));
+        }        
+        for (int i=0;i<otherStop.getSize();i++){
+            newStop.addNode(otherStop.getPnts().get(i));
+        }
+        if ( ltrace ) System.out.println("joinTwoStopData:::newStop.size="+newStop.getSize());
+        return newStop;
+   }
+    public boolean   checkStopData  ( double  maxSpread, int minTimeI ) {
+        boolean ok = true;
+        if ( this.getSize() == 1 || this.getMaxDist() > maxSpread || this.getDeltaTime() < minTimeI ){ ok = false; }
+        return ok;
+    }
+    public boolean   checkStopData (double maxSpread,
+                                    int minTimeI,
+                                    int iNumTestPoints,
+                                    double randWalkPercent,
+                                    boolean beginning ){
+        boolean ok = true;
+        if ( ltrace ) System.out.println("");
+       
+        int numTestPoints = Math.min(iNumTestPoints,this.pnts.size());
+        if ( ltrace ) System.out.println("checkClusterData::: randWalkPercent="+randWalkPercent+", numTestPoints="+numTestPoints+", begining="+beginning);
+        GPSPoint iniNode = null;
+        GPSPoint endNode = null;
+        if ( checkStopData(maxSpread,minTimeI) == false ){
+            return false;
+        }
+        if ( beginning ){
+            iniNode = this.pnts.get(0);
+            endNode = this.pnts.get(numTestPoints-1);
+        }
+        else{
+            iniNode = this.pnts.get(this.getSize()-numTestPoints);
+            endNode = this.pnts.get(this.getSize()-1);       
+        }
+        double ieDist = Calculations.getDistance2D(iniNode, endNode);
+        double totalDist = 0.0;
+        int j = 1;       
+        for (int i=1; i<numTestPoints;i++){
+            if ( !beginning ) {j = this.getSize()-numTestPoints+i;}
+            endNode = this.pnts.get(j);
+            totalDist = totalDist + Calculations.getDistance2D(iniNode, endNode); 
+            iniNode   = endNode;
+            if ( false ) System.out.println("checkClusterData:::             "+i+"-> td("+j+")="+totalDist);
+            j++;
+        }
+        if ( totalDist+0.0001 < (1.0+randWalkPercent)*ieDist && 
+            numTestPoints > iNumTestPoints-1 ){ 
+            ok=false; 
+        }
+        if ( ltrace ) System.out.println("checkClusterData::: ok="+ok+" tD="+totalDist+", ieD="+ieDist);
+        if ( totalDist+0.0001 < ieDist ){
+            System.out.println("\n");
+            System.out.println("!!!!!ALARM!!!!!!");
+            System.out.println("checkClusterData::: begining="+beginning+", ok="+ok+" tD="+totalDist+", ieD="+ieDist+
+                  ", numTestPoints="+numTestPoints+", beginning="+beginning);
+            if ( beginning ){
+                iniNode = this.pnts.get(0);
+                endNode = this.pnts.get(numTestPoints-1);
+            }
+            else{
+                iniNode = this.pnts.get(this.getSize()-numTestPoints);
+                endNode = this.pnts.get(this.getSize()-1);       
+            }
+          
+            totalDist = 0.0;
+            j = 1;
+            for (int i=1; i<numTestPoints;i++){
+                if ( !beginning ) {j = this.getSize()-numTestPoints+i;}
+                endNode = this.pnts.get(j);
+                totalDist = totalDist + Calculations.getDistance2D(iniNode, endNode);
+
+                if ( true ) System.out.println("checkClusterData:::             "+i+"-> td("+j+")="+totalDist+
+                       " <"+iniNode.getLatitude()+","+iniNode.getLongitude()+">  -- <"+endNode.getLatitude()+","+endNode.getLongitude()+">["+Calculations.getDistance2D(iniNode, endNode)+"]");
+                iniNode   = endNode;
+                j++;
+            }
+            if ( beginning ){
+                for(int i=0;i<numTestPoints;i++){
+                    GPSPoint node = this.pnts.get(i);
+                    System.out.println("<"+node.getLatitude()+","+node.getLongitude()+">");
+                }
+            }else{
+                for(int i=0;i<numTestPoints;i++){
+                    GPSPoint node = this.pnts.get(this.pnts.size()-1-i);
+                    System.out.println("<"+node.getLatitude()+","+node.getLongitude()+">");
+                }          
+            }
+            System.out.println("!!!!!ALARM!!!!!!");
+            System.out.println("\n");
+        }        
+        
+        
+        return ok;
+    }
+   public boolean   checkStopDataO ( double  maxSpread, 
+                                     int     minTimeI,
+                                     int     iNumTestPoints, 
+                                     double  randWalkPercent, 
+                                     boolean beginning  )              {
+       boolean ok = true;
+       if ( ltrace ) System.out.println("");
+       
+       int numTestPoints = Math.min(iNumTestPoints,this.pnts.size());
+       if ( ltrace ) System.out.println("checkClusterData::: randWalkPercent="+randWalkPercent+", numTestPoints="+numTestPoints+", begining="+beginning);
+       GPSPoint iniNode = null;
+       GPSPoint endNode = null;
+       if ( checkStopData(maxSpread,minTimeI) == false ){
+          return false;
+       }
+       if ( beginning ){
+            iniNode = this.pnts.get(0);
+            endNode = this.pnts.get(numTestPoints-1);
+       }
+       else{
+            iniNode = this.pnts.get(this.getSize()-numTestPoints);
+            endNode = this.pnts.get(this.getSize()-1);       
+       }
+       double ieDist = Calculations.getDistance2D(iniNode, endNode);
+       double totalDist = 0.0;
+       int j = 1;       
+       for (int i=1; i<numTestPoints;i++){
+            if ( !beginning ) {j = this.getSize()-numTestPoints+i;}
+            endNode = this.pnts.get(j);
+            totalDist = totalDist + Calculations.getDistance2D(iniNode, endNode);;
+            iniNode   = endNode;
+            if ( false ) System.out.println("checkClusterData:::             "+i+"-> td("+j+")="+totalDist);
+            j++;
+       }
+       if ( totalDist+0.0001 < (1.0+randWalkPercent)*ieDist && 
+            numTestPoints > iNumTestPoints-1 ){ 
+            ok=false; 
+       }
+       if ( ltrace ) System.out.println("checkClusterData::: ok="+ok+" tD="+totalDist+", ieD="+ieDist);
+       if ( totalDist+0.0001 < ieDist ){
+            System.out.println("\n");
+            System.out.println("!!!!!ALARM!!!!!!");
+            System.out.println("checkClusterData::: begining="+beginning+", ok="+ok+" tD="+totalDist+", ieD="+ieDist+
+                  ", numTestPoints="+numTestPoints+", beginning="+beginning);
+            if ( beginning ){
+                iniNode = this.pnts.get(0);
+                endNode = this.pnts.get(numTestPoints-1);
+            }
+            else{
+                iniNode = this.pnts.get(this.getSize()-numTestPoints);
+                endNode = this.pnts.get(this.getSize()-1);       
+            }
+          
+          totalDist = 0.0;
+          j = 1;
+          for (int i=1; i<numTestPoints;i++){
+               if ( !beginning ) {j = this.getSize()-numTestPoints+i;}
+               endNode = this.pnts.get(j);
+               totalDist = totalDist + Calculations.getDistance2D(iniNode, endNode);
+
+               if ( true ) System.out.println("checkClusterData:::             "+i+"-> td("+j+")="+totalDist+
+                       " <"+iniNode.getLatitude()+","+iniNode.getLongitude()+">  -- <"+endNode.getLatitude()+","+endNode.getLongitude()+">["+Calculations.getDistance2D(iniNode, endNode)+"]");
+               iniNode   = endNode;
+               j++;
+          }
+          if ( beginning ){
+            for(int i=0;i<numTestPoints;i++){
+                GPSPoint node = this.pnts.get(i);
+                System.out.println("<"+node.getLatitude()+","+node.getLongitude()+">");
+            }
+          }else{
+            for(int i=0;i<numTestPoints;i++){
+                GPSPoint node = this.pnts.get(this.pnts.size()-1-i);
+                System.out.println("<"+node.getLatitude()+","+node.getLongitude()+">");
+            }          
+          }
+          System.out.println("!!!!!ALARM!!!!!!");
+          System.out.println("\n");
+       }
+       return ok;
+   }
+}
diff --git a/src/main/java/sml/utils/Calculations.java b/src/main/java/sml/utils/Calculations.java
new file mode 100644
index 0000000..6cb0af9
--- /dev/null
+++ b/src/main/java/sml/utils/Calculations.java
@@ -0,0 +1,71 @@
+package sml.utils;
+
+import sml.geoUtils.geometry.IPointGeo;
+import sml.geoUtils.geometry.IPolyline;
+import sml.geoUtils.geometry.PointGeo;
+import sml.geoUtils.geometry.Segment;
+import sml.gps.datamodel.GPSPoint;
+
+
+
+
+
+public class Calculations {
+    public static Double EARTH_RADIOUS = 6371010.0;
+    public static Double EPS_LENGTH    = 0.01;
+    //static GreatCircleDistanceCalculator distanceCalculator;
+
+    public static Double getBearing(IPointGeo pt1, IPointGeo pt2){
+        Double dLon = Calculations.toRadians(pt1.getLongitude()-pt2.getLongitude());
+        Double dLat = Calculations.toRadians(pt1.getLatitude()-pt2.getLatitude());
+        
+        Double y = Math.sin(dLon) * Math.cos(Calculations.toRadians(pt2.getLatitude()));
+        Double x = Math.cos(Calculations.toRadians(pt1.getLatitude())) * Math.sin(Calculations.toRadians(pt2.getLatitude()))
+                - Math.sin(Calculations.toRadians(pt1.getLatitude())) * Math.cos(Calculations.toRadians(pt2.getLatitude())) * Math.cos(dLon);
+        return Calculations.toDegrees(Math.atan2(y, x));
+    }
+    public static Double getBearing(Segment<IPointGeo> segment){
+        return Calculations.getBearing(segment.getStartPoint(), segment.getEndPoint());
+    };
+    public static Double getDistance2D(GPSPoint pt1, GPSPoint pt2){
+        return GeoDistance.CalcDistance(pt1,pt2);
+    }
+    public static Double getDistance2D(IPointGeo pt1, IPointGeo pt2){
+        //System.out.println("pt1=<"+pt1.getLatitude()+","+pt1.getLongitude()+">");
+        //System.out.println("pt1=<"+pt2.getLatitude()+","+pt2.getLongitude()+">");
+        if( pt1 == null ){ System.err.println("Calculations.getDistance:::pt1=null"); System.exit(1);}
+        if( pt2 == null ){ System.err.println("Calculations.getDistance:::pt2=null"); System.exit(1);}
+        return Calculations.getDistance2D(pt1, pt2);
+    }
+    public static Double getPathLength(IPointGeo from, IPointGeo to, Segment<IPointGeo> path){
+        return Calculations.getDistance2D(from, to);
+    }
+    public static Long getPathTime(IPointGeo from, IPointGeo to, Segment<IPointGeo> path){
+        Double length = getPathLength(from, to, path);
+        return 10L;
+    }
+    public static Double getLength(IPolyline<IPointGeo> line){
+        Double length = 0.0;
+        for(Segment segment : line.getSegments()){
+           length = length + segment.getLength();
+        }
+        return length;
+    }
+    public static PointGeo Averaging(Iterable<IPointGeo> points){
+       int count = 0;
+       Double lat = 0.0;
+       Double lon = 0.0;
+       for(IPointGeo point : points){
+           lat = lat + point.getLatitude();
+           lon = lon + point.getLongitude();
+           count = count + 1;
+       }
+       return new PointGeo(lat/count, lon/count, -1.0092);
+    }
+    public static Double toRadians(Double angle){
+       return angle * Math.PI / 180.0;
+    }
+    public static double toDegrees(Double angle){
+       return angle * 180.0 / Math.PI;
+    }
+}
diff --git a/src/main/java/sml/utils/GeoDistance.java b/src/main/java/sml/utils/GeoDistance.java
new file mode 100644
index 0000000..5ebceb0
--- /dev/null
+++ b/src/main/java/sml/utils/GeoDistance.java
@@ -0,0 +1,30 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package sml.utils;
+
+import java.util.ArrayList;
+import sml.gps.datamodel.GPSPoint;
+
+public class GeoDistance {
+    static public Double CalcDistance(GPSPoint pnt1, GPSPoint pnt2){
+          return GeoDistance.calcDistance(pnt1.getLatitude(),pnt2.getLatitude(),pnt1.getLongitude(),pnt2.getLongitude());
+    }
+    static public Double calcDistance(Double lat1, Double lat2, Double lon1, Double lon2){
+          lat1 = Math.toRadians(lat1);
+          lat2 = Math.toRadians(lat2);
+          lon1 = Math.toRadians(lon1);
+          lon2 = Math.toRadians(lon2);
+          Double distance = 0.0;
+          distance = Math.sqrt((lat1-lat2)*(lat1-lat2) + (lon1-lon2)*(lon1-lon2)*Math.cos(lat1)*Math.cos(lat2))*6371.0;
+          return distance;
+    }
+    static public Double calcDist(ArrayList<Double> Node1, ArrayList<Double> Node2){
+        Double lat1 = Node1.get(0);
+        Double lat2 = Node2.get(0);
+        Double lon1 = Node1.get(1);
+        Double lon2 = Node2.get(1);
+        return calcDistance(lat1,lat2,lon1,lon2);
+    }
+}
diff --git a/src/main/java/sml/utils/PolyLineEncoder.java b/src/main/java/sml/utils/PolyLineEncoder.java
new file mode 100644
index 0000000..9c4420a
--- /dev/null
+++ b/src/main/java/sml/utils/PolyLineEncoder.java
@@ -0,0 +1,89 @@
+
+package sml.utils;
+
+import java.util.ArrayList;
+
+public class PolyLineEncoder {
+private static Double precision = 1.0E6;
+private static StringBuffer encodeSignedNumber(int num) {
+        int sgn_num = num << 1;
+        if (num < 0) {
+            sgn_num = ~(sgn_num);
+        }
+        return(encodeNumber(sgn_num));
+    }
+
+    private static StringBuffer encodeNumber(int num) {
+        StringBuffer encodeString = new StringBuffer();
+        while (num >= 0x20) {
+                int nextValue = (0x20 | (num & 0x1f)) + 63;
+                encodeString.append((char)(nextValue));
+            num >>= 5;
+        }
+        num += 63;
+        encodeString.append((char)(num));
+        return encodeString;
+    }
+   
+    /**
+     * Encode a polyline with Google polyline encoding method
+     * @param polyline the polyline
+     * @param precision 1 for a 6 digits encoding, 10 for a 5 digits encoding.
+     * @return the encoded polyline, as a String
+     */
+    public static String encode(ArrayList<ArrayList<Double>> polyline) {
+                StringBuffer encodedPoints = new StringBuffer();
+                int prev_lat = 0, prev_lng = 0;
+                for (ArrayList<Double> trackpoint:polyline) {
+                        int lat = (int) Math.round(trackpoint.get(0)*precision);
+                        int lng = (int) Math.round(trackpoint.get(1)*precision);
+                        encodedPoints.append(encodeSignedNumber(lat - prev_lat));
+                        encodedPoints.append(encodeSignedNumber(lng - prev_lng));                      
+                        prev_lat = lat;
+                        prev_lng = lng;
+                }
+                return encodedPoints.toString();
+        }
+   
+    /**
+     * Decode a "Google-encoded" polyline
+     * @param encodedString
+     * @param precision 1 for a 6 digits encoding, 10 for a 5 digits encoding.
+     * @return the polyline.
+     */
+    public static ArrayList<ArrayList<Double>> decode(String encodedString) {
+        ArrayList<ArrayList<Double>> polyline = new ArrayList<ArrayList<Double>>();
+        int index = 0;
+        int len = encodedString.length();
+        int lat = 0, lng = 0;
+
+        while (index < len) {
+            int b, shift = 0, result = 0;
+            do {
+                b = encodedString.charAt(index++) - 63;
+                result |= (b & 0x1f) << shift;
+                shift += 5;
+            } while (b >= 0x20);
+            int dlat = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1));
+            lat += dlat;
+
+            shift = 0;
+            result = 0;
+            do {
+                b = encodedString.charAt(index++) - 63;
+                result |= (b & 0x1f) << shift;
+                shift += 5;
+            } while (b >= 0x20);
+            int dlng = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1));
+            lng += dlng;
+
+            ArrayList<Double> pos = new ArrayList<Double>();
+            pos.add((Double) (lat/precision));
+            pos.add((Double) (lng/precision));
+            polyline.add(pos);
+        }
+
+        return polyline;
+    }
+    
+}
diff --git a/src/main/java/sml/utils/RawData.java b/src/main/java/sml/utils/RawData.java
new file mode 100644
index 0000000..b04a748
--- /dev/null
+++ b/src/main/java/sml/utils/RawData.java
@@ -0,0 +1,54 @@
+package sml.utils;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.ArrayList;
+
+
+public class RawData {
+   private ArrayList<ArrayList<Double>> APos    = new ArrayList<ArrayList<Double>>();
+   private ArrayList<Long>              ATimes  = new ArrayList<Long>();
+   private ArrayList<Double>            AVels   = new ArrayList<Double>();
+   private File file;
+   
+   public RawData(File file)                             {
+        this.file = file;
+        readData();
+   }
+   private void                        readData() {
+      BufferedReader br         = null;
+      String         line       = "";
+      String         cvsSplitBy = ",";
+      try {
+            br = new BufferedReader(new FileReader(this.file.getAbsolutePath()));
+            int k=0;
+
+            while ((line = br.readLine()) != null) {
+                String[] lineText = line.split(cvsSplitBy);
+                this.ATimes.add(Long.parseLong(lineText[0])/1000);
+                Double vel = -1.0;
+                if ( lineText.length > 2 ){
+                   if (!"".equals(lineText[3]) || lineText[3] != null){
+                      vel = Double.parseDouble(lineText[3]);
+                   }
+                }
+                AVels.add(vel);
+                ArrayList<Double> Node = new ArrayList<Double>();
+                Node.add(Double.parseDouble(lineText[1]));
+                Node.add(Double.parseDouble(lineText[2]));
+                this.APos.add(Node);
+                k++;
+            }
+         
+      } catch (IOException ex) {
+            System.out.println("problem accessing file"+this.file.getAbsolutePath());
+      }
+   }
+   public File                         getFile()  {return this.file;}
+   public int                          getSize()  {return this.APos.size();}
+   public ArrayList<ArrayList<Double>> getAPos()  {return this.APos;}
+   public ArrayList<Long>              getATimes(){return this.ATimes;}
+   public ArrayList<Double>            getAVels() {return this.AVels;}
+}
diff --git a/src/main/java/sml/visualizer/datamodel/Register.java b/src/main/java/sml/visualizer/datamodel/Register.java
new file mode 100644
index 0000000..75872a4
--- /dev/null
+++ b/src/main/java/sml/visualizer/datamodel/Register.java
@@ -0,0 +1,29 @@
+package sml.visualizer.datamodel;
+
+
+import java.awt.Color;
+import java.util.ArrayList;
+import sml.gps.datamodel.GPSPoint;
+import sml.gps.datamodel.GPSPoints;
+import sml.gps.datamodel.GPSRoute;
+
+
+public interface Register  {
+    public int    getType();
+    public void   setName(String name);
+    public String getName();
+    public void   setLineColor(Color color);
+    public Color  getLineColor();
+    public void   setLineWidth(int width);
+    public int    getLineWidth();  
+    public void   setSymbolColor(Color color);
+    public Color  getSymbolColor();
+    public void   setSymbolSize(int size);
+    public int    getSymbolSize();    
+    public int    getNodeCount();
+    public GPSPoint getNode(int i);
+    public GPSRoute getGPSRoute();
+    public GPSPoints getGPSPoints();
+    public ArrayList<GPSPoint> getArrayNodes();
+    public void    remove(int i);
+}
diff --git a/src/main/java/sml/visualizer/gui/functions/DivideInTraysCover.java b/src/main/java/sml/visualizer/gui/functions/DivideInTraysCover.java
new file mode 100644
index 0000000..330710e
--- /dev/null
+++ b/src/main/java/sml/visualizer/gui/functions/DivideInTraysCover.java
@@ -0,0 +1,70 @@
+package sml.visualizer.gui.functions;
+
+import java.util.ArrayList;
+import sml.gps.datamodel.GPSPoint;
+import sml.gps.datamodel.GPSRoute;
+import sml.stops.datamodel.StopData;
+
+public class DivideInTraysCover {
+    
+   private GPSRoute route;
+   private final static String PRETEXT = "sml.visualizer.gui.functions.DivideInTraysCover";
+
+   public DivideInTraysCover(GPSRoute route){
+        this.route = route;
+        //doFindingTrays();
+   }
+   public ArrayList<GPSRoute> doFindingTrays(){
+       ArrayList<GPSRoute> out = new ArrayList<>();
+  
+        double maxSpread = 0.2;
+        double minTime   = 10;
+        double maxVel    = 2.0;
+        int    numPoints = 4;
+        double randWalk  = 0.02;
+        FindStops fs = new FindStops(maxSpread,minTime,maxVel,numPoints,randWalk);         
+        fs.addRawData(route);
+        ArrayList<StopData> stops = fs.doStops();
+        ArrayList<Long> iniStops = new ArrayList<>();
+        ArrayList<Long> endStops = new ArrayList<>();
+        for(int s=0;s<stops.size();s++){
+           iniStops.add(stops.get(s).getIniTime());
+           endStops.add(stops.get(s).getEndTime());
+           System.out.println(PRETEXT+".doFindingTrays::: CDR stop="+s+" ["+stops.get(s).getIniTime()+","+stops.get(s).getEndTime()+"]");
+        }
+        iniStops.add(Long.MAX_VALUE);
+        endStops.add(Long.MAX_VALUE);
+        int s = 0;
+        GPSRoute routeTray = null;
+        
+        Long time;
+        boolean inStop = false;
+        time = route.getNodes().get(0).getTime();
+        if ( time < iniStops.get(s) ){
+               routeTray = new GPSRoute();
+               routeTray.setName(route.getName()+"_tray_-1");
+              
+        }
+        for(int p=0;p<route.getNodeCount();p++){
+           time = route.getNodes().get(p).getTime();
+           if ( time >= iniStops.get(s) && time < endStops.get(s) ){
+               inStop = true;
+           }
+           else if ( time >= endStops.get(s) ){
+               routeTray = new GPSRoute();
+               routeTray.setName(route.getName()+"_tray_"+s);
+               out.add(routeTray);
+             
+               s++;   
+               inStop = false;
+           }
+           if ( !inStop ){
+              GPSPoint pnt = route.getNodes().get(p);
+              routeTray.getNodes().add(pnt);
+             
+           }
+        }
+      
+       return out;
+    }
+}
diff --git a/src/main/java/sml/visualizer/gui/functions/FindStops.java b/src/main/java/sml/visualizer/gui/functions/FindStops.java
new file mode 100644
index 0000000..df5819f
--- /dev/null
+++ b/src/main/java/sml/visualizer/gui/functions/FindStops.java
@@ -0,0 +1,364 @@
+package sml.visualizer.gui.functions;
+
+import java.util.ArrayList;
+import sml.gps.datamodel.GPSPoint;
+import sml.gps.datamodel.GPSRoute;
+import sml.stops.datamodel.StopData;
+import sml.utils.GeoDistance;
+
+public class FindStops {
+   
+   private ArrayList<ArrayList<Double>>  APos          = new ArrayList<ArrayList<Double>>();
+   private ArrayList<Long>               ATimes        = new ArrayList<Long>();
+   private ArrayList<Double>             AVels         = new ArrayList<Double>();
+   private ArrayList<StopData>           stops         = new ArrayList<StopData>();
+   private ArrayList<ArrayList<Integer>> AStopIndxs    = new ArrayList<ArrayList<Integer>>();
+   // For the loop...
+   private StopData           currentStop = null;
+   private ArrayList<Integer> stopIndxs   = new ArrayList<Integer>();
+   ////////////////////////////////////////////////////////////////////
+   private double maxSpread       = 0.4;
+   private double minTime         = 0.4;
+   private int    minTimeI        = 24;
+   private double maxVel          = 2.0;
+   private int    numTestPoints   = 4;
+   private double randWalkPercent = 0.35;
+   private int    oldI = -1;
+   public FindStops()                                  {
+         APos    = new ArrayList<>();
+         ATimes  = new ArrayList<>();
+         AVels   = new ArrayList<>();
+   };
+   public FindStops(double maxSpread,       // maximum Spreading for clusters
+                    double minTime,         // minimum time for a LoI
+                    double maxVel,          // maximum velocity for initial cluster identification
+                    int numTestPoints,      // # test points for linearity
+                    double randWalkPercent  // measure of the random walk-enes.
+         )                                              {
+         APos    = new ArrayList<>();
+         ATimes  = new ArrayList<>();
+         AVels   = new ArrayList<>();
+         this.maxSpread       = maxSpread;
+         this.minTime         = minTime;
+         this.minTimeI        = (int) Math.round(minTime*60);
+         this.maxVel          = maxVel;
+         this.numTestPoints   = numTestPoints;
+         this.randWalkPercent = randWalkPercent; 
+   }
+   
+   public  void addRawData(GPSRoute rd)                  {
+        ArrayList<ArrayList<Double>> newAPos   = new ArrayList<>();
+        ArrayList<Long>              newATimes = new ArrayList<>();
+        ArrayList<Double>            newAVels  = new ArrayList<>();
+        for(int i=0;i<rd.getNodeCount();i++){
+            ArrayList<Double> pos = new ArrayList<>();
+            pos.add(rd.getNodes().get(i).getLatitude());
+            pos.add(rd.getNodes().get(i).getLongitude());  
+            newAPos.add(pos);
+            newATimes.add(rd.getNodes().get(i).getTime()/1000);
+            newAVels.add(rd.getNodes().get(i).getSpeed());
+        }
+        for (int j=0;j<newATimes.size();j++){
+            Long              newTime = newATimes.get(j);
+            ArrayList<Double> newNode = newAPos.get(j);
+            Double            newVel  = newAVels.get(j);
+            boolean done = false;
+            if ( oldI >= 0 ){ if ( this.ATimes.get(oldI) > newTime ){ oldI = 0;} }
+            else                                                    { oldI = 0;}
+            for(int i=oldI; i<this.ATimes.size();i++){
+               if ( this.ATimes.get(i) == newTime ){
+                   this.APos.set(i,  newNode);
+                   this.AVels.set(i, newVel);
+                   done = true;
+                   oldI = i;
+                   break;
+               }
+               else if ( this.ATimes.get(i) >  newTime ){
+                   this.ATimes.add(i, newTime);
+                   this.APos.add(i,newNode);
+                   this.AVels.add(i,newVel);
+                   done = true;
+                   oldI = i;
+                   break;
+               }
+            } 
+            if ( !done ){
+              oldI = this.ATimes.size();
+              this.ATimes.add(newTime);
+              this.APos.add(newNode);
+              this.AVels.add(newVel);
+            }
+        }
+        /*
+        System.out.println("FindStops.addRawData:::size(output)="+ATimes.size());  
+        for(int i=0;i<this.ATimes.size();i++){
+           System.out.println("time="+this.ATimes.get(i)+" <"+this.APos.get(i).get(0)+","+this.APos.get(i).get(1)+"> ("+this.AVels.get(i)+")");
+        }*/
+   }
+   private void calcZeroVelClusters()                   {
+       double vel;
+       boolean ltrace = false;
+       Long time;
+       Long lastTime = -1L;
+       Long minStop  = Math.round(maxSpread/maxVel*3600L); 
+       //Long minStop = 5*60L;
+       System.out.println("minStop="+minStop);
+       boolean inStop = false;
+       ArrayList<Double>  node     = new ArrayList<>();
+       ArrayList<Double>  lastNode = new ArrayList<>();
+       ArrayList<Double>  nextNode = new ArrayList<>();
+       currentStop  = null; 
+       for (int i=0;i<ATimes.size();i++){
+            time = ATimes.get(i);
+            vel  = AVels.get(i);
+            node = APos.get(i);
+            //if ( time == 1378874028 || lastTime == 1378874028) ltrace = true;
+            //else ltrace = false;
+            if (ltrace)
+              System.out.println("i:: "+i+" time="+time+", vel="+vel+", inStop"+inStop+", currentCluster="+currentStop);
+            if ( vel < maxVel ){
+                if (ltrace) System.out.println("vel<maxVel");
+                inStop = addRawPointToCluster(time, i, node, lastNode, inStop);
+            }
+            else if ( time-lastTime > minStop && !inStop ){
+                if (ltrace) System.out.println("in the past!!");
+                inStop = addRawPointToCluster(time, i, node, lastNode, inStop);
+            }
+            else if ( i< ATimes.size()-1 && ATimes.get(i+1)-ATimes.get(i) > minStop ){
+                if (ltrace) System.out.println("in the future!!");
+                nextNode  = APos.get(i+1);
+                inStop = addRawPointToCluster(time, i, node, nextNode, inStop);
+            }
+            else{
+                inStop = false;
+            }
+            lastNode = node;
+            lastTime = time;
+       }
+       for(int i=0;i<stops.size();i++){
+           if (stops.get(i).getRadious()> maxSpread )
+              System.out.println(i+": radious"+stops.get(i).getRadious()+"!!!!!!!!!!!!!!!!!!!!");
+       }
+   }
+   private boolean addRawPointToCluster(long time, int i,
+                         ArrayList<Double> node, ArrayList<Double> otherNode, 
+                         boolean inStop)             {
+             boolean lInStop = inStop;
+             boolean ltrace = false;
+             GPSPoint pnt = new GPSPoint(node.get(0),node.get(1),time*1000);
+             if ( i == 860 || i == 859 ) ltrace = true;
+             else ltrace = false;
+             if ( ltrace ){
+                System.out.println("time="+time+" i="+i+" inStop="+inStop+" otherNode.size()="+otherNode.size()+
+                        " node=<"+node.get(0)+","+node.get(1)+"> otherNode="+otherNode.get(0)+","+otherNode.get(1)+"> D="+GeoDistance.calcDist(node,otherNode));
+             }
+             if ( otherNode.size() > 0 ){ 
+                 if ( GeoDistance.calcDist(node,otherNode) < maxSpread ){
+                     
+                     if ( !lInStop ){
+                         lInStop = true;
+                         currentStop = new StopData();
+                         stopIndxs   = new ArrayList<>();
+                         AStopIndxs.add(stopIndxs);
+                         stops.add(currentStop);
+                     }
+                     
+                     currentStop.addNode(pnt);
+                     stopIndxs.add(i);
+                 }
+                 else{
+                     lInStop = false;
+                 }
+             }
+             else{
+                 if ( !lInStop ){
+                     lInStop = true;
+                     currentStop = new StopData();
+                     stopIndxs   = new ArrayList<Integer>();
+                     AStopIndxs.add(stopIndxs);
+                     stops.add(currentStop);
+                 }
+                 currentStop.addNode(pnt);
+                 stopIndxs.add(i);             
+             }
+             if ( lInStop && ltrace ) System.out.println("point added to cluster cluster.size()="+currentStop.getSize());
+             return lInStop;
+   }
+   private void joinStops()                          {
+       int oldStopsSize     = this.stops.size();
+       int newStopsSize     = -this.stops.size();
+       System.out.println("joinClusters::: (on input) clusterSize="+oldStopsSize);
+       int pass = 0;
+       while ( newStopsSize != oldStopsSize ){
+           System.out.println("       joinStops::: pass="+pass+" size="+newStopsSize);
+           int initialStop = 0;
+           this.stops   = this.joinStopsSinglePass(this.stops,initialStop);
+           initialStop = 1;
+           this.stops   = this.joinStopsSinglePass(this.stops,initialStop);
+           oldStopsSize = newStopsSize;
+           newStopsSize = this.stops.size();
+           pass++;
+       }
+       System.out.println("joinStops::: (on output) stopsSize="+newStopsSize);
+   }
+   private ArrayList<StopData> joinStopsSinglePass(ArrayList<StopData> clusters, int initialStop){
+       int limit = clusters.size();
+       ArrayList<StopData> newStops = new ArrayList<StopData>();
+       //for (int k=0;k<1;k++){
+           for (int c1=initialStop;c1<limit-1;c1=c1+2){
+               StopData stop1 = stops.get(c1);
+               StopData stop2 = stops.get(c1+1);
+               if ( c1 == 1 ){
+                 newStops.add(stops.get(0));
+               }
+               boolean joined = false;
+               StopData stop = null;
+               if ( stop1.compare(stop2,maxSpread) ) {
+                   stop = stop1.joinTwoStopData(stop2);
+                   joined = true;
+                   if ( stop.checkStopData(maxSpread,minTimeI) ){ joined = true; }
+               }
+               if ( joined ){
+                   newStops.add(stop);
+               }
+               else{
+                   newStops.add(stop1);
+                   newStops.add(stop2);
+               }
+               if ( c1 == limit-3 ){
+                   newStops.add(stops.get(limit-1));
+               }
+
+           //}
+       }
+       return newStops;
+   }    
+   private void expandStops()                        {
+        int changes = 0;
+        for(int c=0;c<this.stops.size();c++){
+           //System.out.println("expandClusters::: cluster ="+c);
+           changes = this.expandSingleStop(c);
+        }
+   }
+   private int  expandSingleStop(int stopIndx)       {
+       boolean ltrace = false;
+       int oldSize = stops.get(stopIndx).getSize();
+       int newSize = -1;
+       int changes = -1;
+       int k=0;
+       while ( newSize != oldSize ){
+           StopData stop = stops.get(stopIndx);
+           int iniIndx = AStopIndxs.get(stopIndx).get(0);
+           int endIndx = AStopIndxs.get(stopIndx).get(AStopIndxs.get(stopIndx).size()-1);
+           StopData newTempStop = new StopData(stop);
+           if ( ltrace ){
+              System.out.print("change "+k+":        [");
+              for(int j=0;j<AStopIndxs.get(stopIndx).size();j++){
+                  System.out.print(AStopIndxs.get(stopIndx).get(j)+",");
+              }
+              System.out.print("\b\b] size="+stop.getSize()+"  \n");
+           }
+           if ( iniIndx > 0 ) {
+               newTempStop = expandSingleClusterBegining(stop, ATimes.get(iniIndx-1),APos.get(iniIndx-1));
+               if ( newTempStop.checkStopData(maxSpread, minTimeI, numTestPoints, randWalkPercent, true) ){
+                  if ( ltrace ) System.out.println("B");
+                  stops.set(stopIndx, newTempStop);
+                  AStopIndxs.get(stopIndx).add(0, iniIndx-1);
+               }
+           }
+           stop = stops.get(stopIndx);
+           if ( endIndx < ATimes.size()-1 ) {
+               newTempStop = expandSingleClusterEnd(stop, ATimes.get(endIndx+1),APos.get(endIndx+1));
+               if ( newTempStop.checkStopData(maxSpread, minTimeI, numTestPoints, randWalkPercent, false) ){
+                  if ( ltrace ) System.out.println("E");
+                  stops.set(stopIndx, newTempStop);
+                  AStopIndxs.get(stopIndx).add(endIndx+1);
+               }
+           }
+           if ( ltrace ) System.out.println("");
+           oldSize = newSize;
+           newSize =  stops.get(stopIndx).getSize();
+           changes++;
+           k++;
+           if ( k > 1000000000 ) break;
+       }
+       return changes;
+   }
+   private StopData expandSingleClusterBegining(StopData stop, Long time, ArrayList<Double> pos)     {
+        StopData newTempStop = new StopData(stop);
+        GPSPoint pnt = new GPSPoint(pos.get(0),pos.get(1),time*1000);
+        newTempStop.addNode(pnt, true);
+        return newTempStop;
+   }
+   private StopData expandSingleClusterEnd     (StopData stop, Long time, ArrayList<Double> pos)     {
+        StopData newTempClus = new StopData(stop);
+        GPSPoint pnt = new GPSPoint(pos.get(0),pos.get(1),time*1000);
+        newTempClus.addNode(pnt, false);
+        return newTempClus;
+   }  
+   private void removeShortTimeStops(double minTime) {
+       ArrayList<Integer> indxsToErase = new ArrayList<Integer>();
+       for(int c=0;c<this.stops.size();c++){
+          if ( this.stops.get(c).getDeltaTime() < minTime*60 && c!=0 && c!=(this.stops.size()-1) ){
+             indxsToErase.add(c);
+            // System.out.println("removeShortTimeClusters:: this.clusters.getDeltaTime("+c+")="+this.clusters.get(c).getDeltaTime());
+          }
+       }
+       for(int c=stops.size()-1;c>=0;c--){
+           if ( indxsToErase.contains(c) ) this.stops.remove(c);
+       }
+   }
+   private void writeAllStops()                      {
+       int wclus = 0;
+       for (int c=0;c<this.stops.size();c++){          
+           StopData cluster = this.stops.get(c);
+           //if ( cluster.checkClusteringData() ){
+           cluster.writeToFile();
+           wclus++;
+           //}
+       }   
+   } 
+   private ArrayList<StopData> orderStops()       {
+       ArrayList<StopData> orderedStops = new ArrayList<StopData>();
+       for(int c1=0;c1<this.stops.size();c1++){
+          StopData stop = stops.get(c1);
+          Long stopIni = stop.getIniTime();
+          boolean alreadyAdded = false;
+          for (int c2=0;c2<orderedStops.size();c2++){
+              StopData clusO = orderedStops.get(c2);
+              Long clusOIni = clusO.getIniTime();
+              if ( clusOIni>stopIni ){
+                 orderedStops.add(c2,stop);
+                 alreadyAdded = true;
+              }
+          }
+          if (!alreadyAdded){
+             orderedStops.add(stop);
+          }
+       }
+       return orderedStops;
+   }
+   private void findAllNextStops()                   {
+        ArrayList<StopData> orderedStops = orderStops();
+        for(int c=0;c<orderedStops.size()-1;c++){
+           StopData clus   = orderedStops.get(c);
+           StopData clusp1 = orderedStops.get(c+1);
+           if ( clus.getEndTime() < clusp1.getIniTime() ){
+              clus.setNextStop(clusp1.getId());
+           }
+        }
+   }
+   public  ArrayList<StopData> doStops()                          {
+       stops = new ArrayList<StopData>();
+       this.calcZeroVelClusters();
+       System.out.println("Zero Vel Stops identified #(Clus)="+this.stops.size());
+       this.expandStops();
+       System.out.println("Stops expanded #(Clus)="+this.stops.size());
+       this.joinStops();
+       System.out.println("Stops joined #(Clus)="+this.stops.size());
+       this.removeShortTimeStops(this.minTime);
+       System.out.println("Small Stops (<"+minTime+" mins) erased #(Clus)="+this.stops.size());
+       this.findAllNextStops();
+       return stops;
+   }
+}
-- 
GitLab