/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.mozstumbler.client.serialize;
import android.os.AsyncTask;
import android.util.Log;
import com.ekito.simpleKML.Serializer;
import com.ekito.simpleKML.model.Coordinate;
import com.ekito.simpleKML.model.Data;
import com.ekito.simpleKML.model.Document;
import com.ekito.simpleKML.model.ExtendedData;
import com.ekito.simpleKML.model.Feature;
import com.ekito.simpleKML.model.Folder;
import com.ekito.simpleKML.model.Geometry;
import com.ekito.simpleKML.model.Icon;
import com.ekito.simpleKML.model.IconStyle;
import com.ekito.simpleKML.model.Kml;
import com.ekito.simpleKML.model.Placemark;
import com.ekito.simpleKML.model.Point;
import com.ekito.simpleKML.model.Style;
import com.ekito.simpleKML.model.StyleSelector;
import com.ekito.simpleKML.model.TimeStamp;
import org.joda.time.DateTime;
import org.mozilla.mozstumbler.client.mapview.ObservationPoint;
import org.mozilla.mozstumbler.svclocator.services.log.LoggerUtil;
import java.io.File;
import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
// add menu item for save/load observations
// activity will have save and load buttons
// save -> save with name obs-date-#obs.kml
// load -> show list to pick from
public class ObservationPointSerializer extends AsyncTask<Void, Void, Boolean> {
public static final String KML_PROVIDER = "KML_FILE";
public static final String WIFIS = "Wi-Fis";
public static final String CELLS = "Cells";
public static final String TRACK_SEGMENT = "Track segment";
private static final String LOG_TAG = LoggerUtil.makeLogTag(ObservationPointSerializer.class);
private static final String GPS_NAME = "GPS";
private static final String MLS_NAME = "MLS";
private static final String ICON_RED_CIRCLE = "http://maps.google.com/mapfiles/kml/shapes/placemark_circle_highlight.png";
private static final String STYLE_NAME_RED_CIRCLE = "redcircle";
private static final String ICON_ARROW = "http://earth.google.com/images/kml-icons/track-directional/track-0.png";
private static final String STYLE_NAME_ARROW = "arrow";
private static final String COLOR_HAS_WIFI = "ff0000ff"; // red in AABBGGRR
private static final String COLOR_HAS_CELLS = "ffff0000"; // blue
private static final String COLOR_HAS_BOTH = "ff00ff00"; // green
private static final String COLOR_HAS_NONE = "aa000000"; // transparent black
final WeakReference<IListener> mObservationPointSerializerListener;
private final LinkedList<ObservationPoint> mPointList;
private File mFile;
private Mode mMode;
ObservationPointSerializer(IListener listener, Mode mode, File file,
LinkedList<ObservationPoint> pointList) {
mObservationPointSerializerListener = new WeakReference<IListener>(listener);
mFile = file;
mPointList = pointList;
mMode = mode;
}
void addStyle(Document doc, String iconHref, String styleName, Float scale) {
Icon icon = new Icon();
icon.setHref(iconHref);
IconStyle iconStyle = new IconStyle();
iconStyle.setIcon(icon);
if (scale != null) {
iconStyle.setScale(scale);
}
Style style = new Style();
style.setId(styleName);
style.setIconStyle(iconStyle);
List<StyleSelector> styleSelectorList = doc.getStyleSelector();
if (doc.getStyleSelector() == null) {
styleSelectorList = new LinkedList<StyleSelector>();
doc.setStyleSelector(styleSelectorList);
}
styleSelectorList.add(style);
}
Folder createFolder(String name, List<Feature> features) {
Folder folder = new Folder();
folder.setName(name);
folder.setFeatureList(features);
return folder;
}
void setHeadingAndColor(Placemark placemark, double heading, String color) {
placemark.setStyleUrl("#" + STYLE_NAME_ARROW);
IconStyle iconStyle = new IconStyle();
iconStyle.setHeading((float) heading);
if (color != null) {
iconStyle.setColor(color);
}
Style style = new Style();
style.setIconStyle(iconStyle);
List<StyleSelector> styleSelector = new LinkedList<StyleSelector>();
styleSelector.add(style);
placemark.setStyleSelector(styleSelector);
}
synchronized boolean writeOut(File outFile) {
List<Feature> gpsFeatures = new LinkedList<Feature>();
List<Feature> mlsFeatures = new LinkedList<Feature>();
int idCounter = 0;
for (ObservationPoint observationPoint : mPointList) {
if (observationPoint.pointGPS.getProvider().equals(KML_PROVIDER)) {
// This was previously read in, don't write out again
continue;
}
idCounter++;
Point point = new Point();
point.setId("p" + idCounter); // used to match with MLS point
point.setCoordinates(observationPoint.getGPSCoordinate());
Placemark placemark = new Placemark();
placemark.setName(GPS_NAME);
DateTime dateTime = new DateTime(observationPoint.pointGPS.getTime());
TimeStamp time = new TimeStamp();
time.setWhen(dateTime.toString() /* Date auto formats to RFC 3339 */);
placemark.setTimePrimitive(time);
String color = COLOR_HAS_NONE;
if (observationPoint.mWifiCount > 0 && observationPoint.mCellCount > 0) {
color = COLOR_HAS_BOTH;
} else if (observationPoint.mWifiCount > 0) {
color = COLOR_HAS_WIFI;
} else if (observationPoint.mCellCount > 0) {
color = COLOR_HAS_CELLS;
}
setHeadingAndColor(placemark, observationPoint.pointGPS.getBearing(), color);
List<Data> dataList = new LinkedList<Data>();
Data data = new Data();
data.setName(WIFIS);
data.setValue(String.valueOf(observationPoint.mWifiCount));
dataList.add(data);
data = new Data();
data.setName(CELLS);
data.setValue(String.valueOf(observationPoint.mCellCount));
dataList.add(data);
data = new Data();
data.setName(TRACK_SEGMENT);
data.setValue(String.valueOf(observationPoint.mTrackSegment));
dataList.add(data);
ExtendedData extendedData = new ExtendedData();
extendedData.setDataList(dataList);
placemark.setExtendedData(extendedData);
List<Geometry> geometryList = new LinkedList<Geometry>();
geometryList.add(point);
placemark.setGeometryList(geometryList);
gpsFeatures.add(placemark);
if (observationPoint.pointMLS != null) {
placemark = new Placemark();
placemark.setStyleUrl("#" + STYLE_NAME_RED_CIRCLE);
placemark.setName(MLS_NAME);
placemark.setTimePrimitive(time);
point = new Point();
point.setId("p" + idCounter); // used to match with gps point
point.setCoordinates(observationPoint.getMLSCoordinate());
geometryList = new LinkedList<Geometry>();
geometryList.add(point);
placemark.setGeometryList(geometryList);
mlsFeatures.add(placemark);
}
}
Document doc = new Document();
addStyle(doc, ICON_RED_CIRCLE, STYLE_NAME_RED_CIRCLE, null);
addStyle(doc, ICON_ARROW, STYLE_NAME_ARROW, 1.2f);
List<Feature> docFeatures = new LinkedList<Feature>();
docFeatures.add(createFolder(GPS_NAME, gpsFeatures));
docFeatures.add(createFolder(MLS_NAME, mlsFeatures));
doc.setFeatureList(docFeatures);
Kml kml = new Kml();
kml.setFeature(doc);
Serializer kmlSerializer = new Serializer();
try {
kmlSerializer.write(kml, outFile);
} catch (Exception e) {
Log.e(LOG_TAG, e.getMessage());
return false;
}
Log.d(LOG_TAG, "write done");
return true;
}
private boolean isGpsPointRead(String name) {
return name.equals(GPS_NAME);
}
synchronized boolean readIn(File file) {
Serializer kmlSerializer = new Serializer();
Kml kml;
try {
kml = kmlSerializer.read(file);
} catch (Exception e) {
Log.e(LOG_TAG, e.getMessage());
return false;
}
Feature feature = kml.getFeature();
if (!(feature instanceof Document)) {
Log.e(LOG_TAG, "expected document");
return false;
}
Document doc = (Document) feature;
List<Feature> featureList = doc.getFeatureList();
if (featureList == null) {
Log.e(LOG_TAG, "expected doc features");
return false;
}
HashMap<String, ObservationPoint> gpsList = new HashMap<String, ObservationPoint>();
HashMap<String, Coordinate> mlsList = new HashMap<String, Coordinate>();
for (Feature topFeatures : featureList) {
if (!(topFeatures instanceof Folder)) {
continue;
}
List<Feature> subFeatures = ((Folder) topFeatures).getFeatureList();
if (subFeatures == null) {
continue;
}
for (Feature f : subFeatures) {
if (!(f instanceof Placemark)) {
continue;
}
Placemark placemark = (Placemark) f;
List<Geometry> geometryList = placemark.getGeometryList();
if (geometryList == null || geometryList.size() != 1) {
continue;
}
Geometry geometry = geometryList.get(0);
if (!(geometry instanceof Point)) {
continue;
}
Point p = (Point) geometry;
Coordinate coordinate = p.getCoordinates();
boolean isGps = isGpsPointRead(placemark.getName());
int wifis = 0;
int cells = 0;
ExtendedData extendedData = placemark.getExtendedData();
if (extendedData != null) {
List<Data> data = extendedData.getDataList();
for (Data d : data) {
if (d.getName().equals(WIFIS)) {
wifis = Integer.parseInt(d.getValue());
} else if (d.getName().equals(CELLS)) {
cells = Integer.parseInt(d.getValue());
}
}
}
ObservationPoint observationPoint = new ObservationPoint(KML_PROVIDER, coordinate, wifis, cells);
if (isGps) {
mPointList.add(observationPoint);
gpsList.put(placemark.getId(), observationPoint);
} else {
mlsList.put(placemark.getId(), coordinate);
}
}
}
for (Map.Entry<String, Coordinate> entry : mlsList.entrySet()) {
ObservationPoint obs = gpsList.get(entry.getKey());
if (obs != null) {
obs.setMLSCoordinate(entry.getValue());
}
}
return true;
}
@Override
protected Boolean doInBackground(Void... params) {
if (mMode == Mode.WRITE) {
return writeOut(mFile);
} else {
return readIn(mFile);
}
}
@Override
protected void onPostExecute(Boolean result) {
IListener listener = mObservationPointSerializerListener.get();
if (listener == null) {
return;
}
if (!result) {
listener.onError();
return;
}
if (mMode == Mode.WRITE) {
listener.onWriteComplete(mFile);
} else {
listener.onReadComplete(mFile);
}
}
public enum Mode {READ, WRITE}
public interface IListener {
void onWriteComplete(File file);
void onReadComplete(File file);
void onError();
}
}