/*
*
* Copyright (C) 2012-2014 R T Huitema. All Rights Reserved.
* Web: www.42.co.nz
* Email: robert@42.co.nz
* Author: R T Huitema
*
* This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
* WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package nz.co.fortytwo.signalk.processor;
import static nz.co.fortytwo.signalk.util.SignalKConstants.PUT;
import static nz.co.fortytwo.signalk.util.SignalKConstants.dot;
import static nz.co.fortytwo.signalk.util.SignalKConstants.key;
import static nz.co.fortytwo.signalk.util.SignalKConstants.name;
import static nz.co.fortytwo.signalk.util.SignalKConstants.nav_position_latitude;
import static nz.co.fortytwo.signalk.util.SignalKConstants.nav_position_longitude;
import static nz.co.fortytwo.signalk.util.SignalKConstants.resources;
import static nz.co.fortytwo.signalk.util.SignalKConstants.resources_routes;
import static nz.co.fortytwo.signalk.util.SignalKConstants.routes;
import static nz.co.fortytwo.signalk.util.SignalKConstants.self;
import static nz.co.fortytwo.signalk.util.SignalKConstants.source;
import static nz.co.fortytwo.signalk.util.SignalKConstants.sourceRef;
import static nz.co.fortytwo.signalk.util.SignalKConstants.timestamp;
import static nz.co.fortytwo.signalk.util.SignalKConstants.type;
import static nz.co.fortytwo.signalk.util.SignalKConstants.value;
import static nz.co.fortytwo.signalk.util.SignalKConstants.vessels_dot_self_dot;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.NavigableSet;
import java.util.UUID;
import mjson.Json;
import nz.co.fortytwo.signalk.handler.JsonStorageHandler;
import nz.co.fortytwo.signalk.model.SignalKModel;
import nz.co.fortytwo.signalk.util.ConfigConstants;
import nz.co.fortytwo.signalk.util.Position;
import nz.co.fortytwo.signalk.util.SignalKConstants;
import nz.co.fortytwo.signalk.util.TrackSimplifier;
import nz.co.fortytwo.signalk.util.Util;
import org.apache.camel.Exchange;
import org.apache.camel.Processor;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger;
import org.joda.time.DateTime;
/**
* Periodic saves the current track to the signalkModel
*
* @author robert
*
*/
public class TrackProcessor extends SignalkProcessor implements Processor {
private static final String COORDINATES = "coordinates";
private static final String GEOMETRY = "geometry";
private static final String FEATURE = "feature";
private static final String GEOJSON = "[{\"geometry\":{\"coordinates\":[],\"type\":\"LineString\"},\"properties\":null,\"id\":\"na\",\"type\":\"Feature\"}]";
// simplify to about 2m out of true (at equator)
private static final double TRACK_TOLERANCE = 0.00002;
private static final int MAX_COUNT = 5000;
private static final int SAVE_COUNT = 60;
private static Logger logger = LogManager.getLogger(TrackProcessor.class);
private Json msg ;
//TODO: thread safety??
private List<Position> track = new ArrayList<Position>();
private Json coords;
private Json geometry;
private static int count = 0;
private boolean initialized = false;
public TrackProcessor() throws Exception {
msg = createTrackMsg();
}
public void process(Exchange exchange) throws Exception {
try {
if(logger.isDebugEnabled()&& exchange.getIn().getBody()!=null){
logger.debug("Processing:"+exchange.getIn().getBody().getClass());
logger.trace("Processing:"+exchange.getIn().getBody());
}
if(!initialized){
//save new message
inProducer.sendBody(msg.toString());
initialized=true;
}
if (exchange.getIn().getBody() instanceof SignalKModel) {
handle(exchange.getIn().getBody(SignalKModel.class));
}else{
if(logger.isTraceEnabled())logger.trace("Ignored, not a track:"+exchange.getIn().getBody(Json.class));
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
}
// @Override
public void handle(SignalKModel node) {
if (node.getData().size() == 0)
return;
if (node.getData().containsKey(vessels_dot_self_dot + nav_position_latitude) && node.getData().containsKey(vessels_dot_self_dot + nav_position_longitude)) {
if (logger.isTraceEnabled())
logger.trace("TrackProcessor updating " + node);
// we have a track change.
track.add(new Position((double)node.get(vessels_dot_self_dot + nav_position_latitude), (double)node.get(vessels_dot_self_dot + nav_position_longitude)));
count++;
// append to file
if (count % SAVE_COUNT == 0) {
// save it
for(Position p: track){
coords.add(Json.array(p.longitude(), p.latitude()));
}
geometry.set(COORDINATES, coords);
if(logger.isDebugEnabled())logger.debug("Saving track:"+count);
updateSourceAndTime(msg);
inProducer.asyncSendBody(inProducer.getDefaultEndpoint(), msg.toString());
coords.asJsonList().clear();
}
if (count % (SAVE_COUNT * 4) == 0) {
if (logger.isDebugEnabled())
logger.debug("Simplify track, size:" + coords.asList().size());// +":"+coords);
track = TrackSimplifier.simplify(track, TRACK_TOLERANCE);
if (logger.isDebugEnabled())
logger.debug("Simplify track, done, size:" + coords.asList().size());
count = track.size();
}
// reset?
if (count > MAX_COUNT) {
if(logger.isDebugEnabled())logger.debug("Rolling to new track:"+count);
for(Position p: track){
coords.add(Json.array(p.longitude(), p.latitude()));
}
geometry.set(COORDINATES, coords);
updateSourceAndTime(msg);
inProducer.asyncSendBody(inProducer.getDefaultEndpoint(), msg.toString());
msg = createTrackMsg();
//save new message
inProducer.asyncSendBody(inProducer.getDefaultEndpoint(), msg.toString());
count = 0;
track.clear();
}
}
}
private Json createTrackMsg(){
Json val = Json.object();
val.set(SignalKConstants.PATH, routes + dot + "urn:mrn:signalk:uuid:"+UUID.randomUUID().toString());
Json currentTrack = Json.object();
val.set(value, currentTrack);
String time = Util.getIsoTimeString();
time = time.substring(0, time.indexOf("."));
currentTrack.set(name, "Vessel Track from "+time);
currentTrack.set("description", "Auto saved vessel track from "+time);
Json values = Json.array();
values.add(val);
Json update = Json.object();
update.set(SignalKConstants.values, values);
Json updates = Json.array();
updates.add(update);
Json msg = Json.object();
msg.set(SignalKConstants.CONTEXT, resources);
msg.set(SignalKConstants.PUT, updates);
updateSourceAndTime(msg);
Json geoJson = Json.read(GEOJSON);
geometry = geoJson.at(0).at(GEOMETRY);
coords = geometry.at(COORDINATES);
currentTrack.set(FEATURE, geoJson);
if(logger.isDebugEnabled())logger.debug("Created new track msg:"+msg);
return msg;
}
private void updateSourceAndTime(Json msg) {
Json update = msg.at(PUT).at(0);
update.set(timestamp, Util.getIsoTimeString());
update.set(source, Json.read("{\"internal\": {"+
"\"value\": \"TrackProcessor\","+
"\"timestamp\": \""+Util.getIsoTimeString()+"\"}}"));
}
}