/* * Geotoolkit - An Open Source Java GIS Toolkit * http://www.geotoolkit.org * * (C) 2013, Geomatys * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. */ package org.geotoolkit.data.nmea; import com.vividsolutions.jts.geom.Coordinate; import com.vividsolutions.jts.geom.GeometryFactory; import com.vividsolutions.jts.geom.Point; import static org.geotoolkit.data.nmea.NMEAFeatureStore.ALT_NAME; import static org.geotoolkit.data.nmea.NMEAFeatureStore.DATE_NAME; import static org.geotoolkit.data.nmea.NMEAFeatureStore.DEPTH_NAME; import static org.geotoolkit.data.nmea.NMEAFeatureStore.GEOM_NAME; import static org.geotoolkit.data.nmea.NMEAFeatureStore.SPEED_NAME; import java.util.HashMap; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.event.EventListenerList; import net.sf.marineapi.nmea.event.SentenceEvent; import net.sf.marineapi.nmea.event.SentenceListener; import net.sf.marineapi.nmea.io.SentenceReader; import net.sf.marineapi.nmea.sentence.DateSentence; import net.sf.marineapi.nmea.sentence.DepthSentence; import net.sf.marineapi.nmea.sentence.GGASentence; import net.sf.marineapi.nmea.sentence.GLLSentence; import net.sf.marineapi.nmea.sentence.PositionSentence; import net.sf.marineapi.nmea.sentence.RMCSentence; import net.sf.marineapi.nmea.sentence.Sentence; import net.sf.marineapi.nmea.sentence.SentenceId; import net.sf.marineapi.nmea.sentence.TimeSentence; import net.sf.marineapi.nmea.util.Date; import net.sf.marineapi.nmea.util.Position; import net.sf.marineapi.nmea.util.Time; import net.sf.marineapi.provider.event.ProviderEvent; import net.sf.marineapi.provider.event.ProviderListener; import org.apache.sis.util.logging.Logging; import org.opengis.feature.Feature; /** * Create features of type {@link NMEAFeatureStore#NMEA_TYPE} from * {@link Sentence} read by a {@link SentenceReader} or given by the user. * * This class implements {@link SentenceListener} interface, what allows you to * attach a NMEABuilder to a reader. You can instanciate NMEABuilder by giving * it the reader, in which case listener is attached / released automatically. * You can also call the default constructor, and manage the reader on your * side. When the builder has enough data to build a feature, a * {@link FeatureEvent} is propagated to all its {@link ProviderListener}, which * allow them to get the built feature. * * To allow user reading data without using any listener, there's a function * {@link NMEABuilder#readSentence(net.sf.marineapi.nmea.sentence.Sentence)}. * With that function, you can give directly your sentences to the builder, and * it will give you back a boolean indicating if there's sufficient information * to create a valid point. If the method return true, you can get the built * feature using {@link NMEABuilder#next()}. * * When a feature is created, the sentences already given are deleted, and will * not involve in next feature generation. * * @author Alexis Manin (Geomatys) */ public class NMEABuilder implements SentenceListener { private static final Logger LOGGER = Logging.getLogger("org.geotoolkit.data.nmea"); private static final GeometryFactory GFACTORY = new GeometryFactory(); private final SentenceReader sReader; private final HashMap<String, Sentence> cache = new HashMap<>(); private final EventListenerList listeners = new EventListenerList(); private Feature next = null; public NMEABuilder() { this(null); } public NMEABuilder(final SentenceReader reader) { sReader = reader; if (sReader != null) { sReader.addSentenceListener(this); } } public boolean readSentence(Sentence s) { // If the current sentence is already stored and we've got a position data, // We must have all needed information to build gps data. if (cache.containsKey(s.getSentenceId()) && (cache.containsKey(SentenceId.GGA.name()) || cache.containsKey(SentenceId.GLL.name()) || cache.containsKey(SentenceId.RMC.name()))) { buildFeature(); reset(); if (next != null) { final FeatureEvent evt = new FeatureEvent(this, next); fireFeatureEvent(evt); } } cache.put(s.getSentenceId(), s); return next != null; } /** * Clears the list of collected sentences. */ private void reset() { cache.clear(); } /** * Call when there is nothing left in the stream, and try to build a last feature. * * @return */ public boolean endFeature(){ buildFeature(); return next != null; } private void buildFeature() { Position position = null; Double depth = null; Double speed = null; Date date = null; Time time = null; for (Sentence s : cache.values()) { try { // Check for main geographic information. if (s instanceof GGASentence || s instanceof GLLSentence) { position = ((PositionSentence) s).getPosition(); } // If we can't get geographic position from main source, take it where we can. else if (s instanceof PositionSentence && position == null) { position = ((PositionSentence) s).getPosition(); } // Speed and time information if (s instanceof RMCSentence) { final RMCSentence rmc = (RMCSentence) s; speed = rmc.getSpeed(); date = rmc.getDate(); time = rmc.getTime(); } else { if (s instanceof TimeSentence && time == null) { time = ((TimeSentence) s).getTime(); } if (s instanceof DateSentence && date == null) { date = ((DateSentence) s).getDate(); } } if (s instanceof DepthSentence) { depth = ((DepthSentence) s).getDepth(); } } catch (Exception ex) { // Do nothing. If a measure failed, it doesn't mean all data failed. } } if (position != null) { next = NMEAFeatureStore.NMEA_TYPE.newInstance(); final Point geom = GFACTORY.createPoint(new Coordinate(position.getLongitude(), position.getLatitude(), position.getAltitude())); next.setPropertyValue(GEOM_NAME.toString(), geom); next.setPropertyValue(ALT_NAME.toString(), position.getAltitude()); } else { // We don't get geographic coordinate, so we give up the current feature. next = null; return; } if (date != null) { // Concatenate measure date with measure time in the day to get java date. final java.util.Date trueDate = new java.util.Date( date.toDate().getTime() + ((time == null) ? 0 : time.getMilliseconds())); next.setPropertyValue(DATE_NAME.toString(), trueDate); } if (speed != null) { next.setPropertyValue(SPEED_NAME.toString(), speed); } if (depth != null) { next.setPropertyValue(DEPTH_NAME.toString(), depth); } } public Feature next() { final Feature tmp = next; next = null; return tmp; } @Override public void readingPaused() { LOGGER.log(Level.INFO, "NMEA Reader paused."); reset(); fireFeatureEvent(null); } @Override public void readingStarted() { LOGGER.log(Level.INFO, "NMEA Reader started."); reset(); } @Override public void readingStopped() { LOGGER.log(Level.INFO, "NMEA Reader stopped."); reset(); // TODO : Re-activate listener release ? It have been desactivated because of ConcurrentModificationException. // synchronized (sReader) { // if (sReader != null) { // sReader.removeSentenceListener(this); // } // } fireFeatureEvent(null); } @Override public void sentenceRead(SentenceEvent event) { readSentence(event.getSentence()); } /** * Register the given listener the list of objects to notice. * * @param listener Listener to add */ public void addListener(ProviderListener<FeatureEvent> listener) { listeners.add(ProviderListener.class, listener); } /** * Remove the specified listener from provider. * * @param listener Listener to remove */ public void removeListener(ProviderListener<FeatureEvent> listener) { listeners.remove(ProviderListener.class, listener); LOGGER.log(Level.FINE, "A listener have been removed."); } /** * Notice listeners that a new feature have been created. * * @param event TPVUpdateEvent to dispatch */ private void fireFeatureEvent(FeatureEvent event) { for (ProviderListener listener : listeners.getListeners(ProviderListener.class)) { listener.providerUpdate(event); } } /** * Event propagated when a feature is built. It contains the created feature, * which can be retrieved with {@link FeatureEvent#getData()} method. */ public class FeatureEvent extends ProviderEvent { private final Feature data; public FeatureEvent(Object source, final Feature f) { super(source); data = f; } public Feature getData() { return data; } } }