Java tutorial
/* * Copyright (C) 2016 Brockmann Consult GmbH * This code was developed for the EC project "Fidelity and Uncertainty in * Climate Data Records from Earth Observations (FIDUCEO)". * Grant Agreement: 638822 * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 3 of the License, or (at your option) * any later version. * This program 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 General Public License for * more details. * * A copy of the GNU General Public License should have been supplied along * with this program; if not, see http://www.gnu.org/licenses/ * */ package com.bc.fiduceo.db; import com.bc.fiduceo.core.NodeType; import com.bc.fiduceo.core.SatelliteObservation; import com.bc.fiduceo.core.Sensor; import com.bc.fiduceo.geometry.*; import com.mongodb.BasicDBObject; import com.mongodb.MongoClient; import com.mongodb.MongoCredential; import com.mongodb.ServerAddress; import com.mongodb.client.FindIterable; import com.mongodb.client.MongoCollection; import com.mongodb.client.MongoDatabase; import com.mongodb.client.MongoIterable; import com.mongodb.client.model.geojson.PolygonCoordinates; import com.mongodb.client.model.geojson.Position; import org.apache.commons.dbcp2.BasicDataSource; import org.bson.Document; import org.esa.snap.core.util.StringUtils; import java.sql.SQLException; import java.util.ArrayList; import java.util.Date; import java.util.List; @SuppressWarnings("unchecked") public class MongoDbDriver extends AbstractDriver { private static final String DATA_FILE_KEY = "dataFile"; private static final String START_TIME_KEY = "startTime"; private static final String STOP_TIME_KEY = "stopTime"; private static final String NODE_TYPE_KEY = "nodeType"; private static final String GEO_BOUNDS_KEY = "geoBounds"; private static final String SENSOR_KEY = "sensor"; private static final String SATELLITE_DATA_COLLECTION = "SATELLITE_OBSERVATION"; private static final String TIME_AXES_KEY = "timeAxes"; private static final String VERSION_KEY = "version"; private static final String DATABASE_NAME = "FIDUCEO"; private MongoClient mongoClient; private GeometryFactory geometryFactory; private MongoDatabase database; @Override public String getUrlPattern() { return "mongodb"; } @Override public void open(BasicDataSource dataSource) throws SQLException { final String address = parseAddress(dataSource.getUrl()); final String port = parsePort(dataSource.getUrl()); final ServerAddress serverAddress = new ServerAddress(address, Integer.parseInt(port)); final String username = dataSource.getUsername(); final String password = dataSource.getPassword(); if (StringUtils.isNotNullAndNotEmpty(password) && StringUtils.isNotNullAndNotEmpty(username)) { final MongoCredential credential = MongoCredential.createCredential(username, DATABASE_NAME, password.toCharArray()); final List<MongoCredential> credentialsList = new ArrayList<>(); credentialsList.add(credential); mongoClient = new MongoClient(serverAddress, credentialsList); } else { mongoClient = new MongoClient(serverAddress); } database = mongoClient.getDatabase(DATABASE_NAME); } @Override public void close() throws SQLException { if (mongoClient != null) { mongoClient.close(); mongoClient = null; } } @Override public boolean isInitialized() { final MongoIterable<String> collectionNames = database.listCollectionNames(); for (String collectionName : collectionNames) { if (SATELLITE_DATA_COLLECTION.equals(collectionName)) { return true; } } return false; } @Override public void initialize() throws SQLException { final MongoCollection<Document> satelliteObservations = database.getCollection(SATELLITE_DATA_COLLECTION); satelliteObservations.createIndex(new BasicDBObject(START_TIME_KEY, 1)); satelliteObservations.createIndex(new BasicDBObject(STOP_TIME_KEY, 1)); satelliteObservations.createIndex(new BasicDBObject(SENSOR_KEY + ".name", 1)); } @Override public void clear() throws SQLException { final MongoCollection<Document> satelliteObservation = database.getCollection(SATELLITE_DATA_COLLECTION); satelliteObservation.drop(); } @Override public void setGeometryFactory(GeometryFactory geometryFactory) { this.geometryFactory = geometryFactory; } @Override public void insert(SatelliteObservation satelliteObservation) throws SQLException { final MongoCollection<Document> observationCollection = database.getCollection(SATELLITE_DATA_COLLECTION); final Document document = new Document(DATA_FILE_KEY, satelliteObservation.getDataFilePath().toString()); document.append(START_TIME_KEY, satelliteObservation.getStartTime()); document.append(STOP_TIME_KEY, satelliteObservation.getStopTime()); document.append(NODE_TYPE_KEY, satelliteObservation.getNodeType().toId()); final Geometry geoBounds = satelliteObservation.getGeoBounds(); if (geoBounds != null) { document.append(GEO_BOUNDS_KEY, convertToGeoJSON(geoBounds)); } // @todo 2 tb/tb does not work correctly when we extend the sensor class, improve here 2016-02-09 document.append(SENSOR_KEY, new Document("name", satelliteObservation.getSensor().getName())); final TimeAxis[] timeAxes = satelliteObservation.getTimeAxes(); if (timeAxes != null) { document.append(TIME_AXES_KEY, convertToDocument(timeAxes)); } document.append(VERSION_KEY, satelliteObservation.getVersion()); observationCollection.insertOne(document); } @Override public int insert(Sensor sensor) throws SQLException { // we use embedded storage at the moment, no need to separately ingest the sensor tb 2016-02-09 return -1; } @Override public List<SatelliteObservation> get() throws SQLException { return get(null); } @Override public List<SatelliteObservation> get(QueryParameter parameter) throws SQLException { final MongoCollection<Document> observationCollection = database.getCollection(SATELLITE_DATA_COLLECTION); final List<SatelliteObservation> resultList = new ArrayList<>(); final Document queryDocument = createQueryDocument(parameter); final FindIterable<Document> documents = observationCollection.find(queryDocument); for (Document document : documents) { final SatelliteObservation satelliteObservation = getSatelliteObservation(document); resultList.add(satelliteObservation); } return resultList; } private SatelliteObservation getSatelliteObservation(Document document) { final SatelliteObservation satelliteObservation = new SatelliteObservation(); final String dataFile = document.getString(DATA_FILE_KEY); satelliteObservation.setDataFilePath(dataFile); final String version = document.getString(VERSION_KEY); satelliteObservation.setVersion(version); final Date startTime = document.getDate(START_TIME_KEY); satelliteObservation.setStartTime(startTime); final Date stopTime = document.getDate(STOP_TIME_KEY); satelliteObservation.setStopTime(stopTime); final Integer nodeTypeId = document.getInteger(NODE_TYPE_KEY); satelliteObservation.setNodeType(NodeType.fromId(nodeTypeId)); final Document geoBounds = (Document) document.get(GEO_BOUNDS_KEY); if (geoBounds != null) { final Geometry geometry = convertToGeometry(geoBounds); satelliteObservation.setGeoBounds(geometry); } // @todo 2 tb/tb does not work correctly when we extend the sensor class, improve here 2016-02-09 final Document jsonSensor = (Document) document.get(SENSOR_KEY); final Sensor sensor = new Sensor(jsonSensor.getString("name")); satelliteObservation.setSensor(sensor); final Document jsonTimeAxes = (Document) document.get(TIME_AXES_KEY); if (jsonTimeAxes != null) { final TimeAxis[] timeAxes = convertToTimeAxes(jsonTimeAxes); satelliteObservation.setTimeAxes(timeAxes); } return satelliteObservation; } // static access for testing only tb 2016-02-09 @SuppressWarnings("unchecked") Geometry convertToGeometry(Document geoDocument) { final String type = geoDocument.getString("type"); if ("MultiPolygon".equals(type)) { return convertMultiPolygon(geoDocument); } else if ("Polygon".equals(type)) { return convertPolygon(geoDocument); } else if ("GeometryCollection".equals(type)) { return convertGeometryCollection(geoDocument); } else if ("LineString".equals(type)) { return convertLineString(geoDocument); } throw new RuntimeException("Geometry type support not implemented yet: " + type); } private Geometry convertGeometryCollection(Document geoDocument) { final List<Document> geometryList = (List<Document>) geoDocument.get("geometries"); final List<Geometry> convertedGeometriesList = new ArrayList<>(); for (final Document geoListDocument : geometryList) { convertedGeometriesList.add(convertToGeometry(geoListDocument)); } return geometryFactory.createGeometryCollection( convertedGeometriesList.toArray(new Geometry[convertedGeometriesList.size()])); } private Geometry convertMultiPolygon(Document geoDocument) { List<Polygon> polygonList = new ArrayList<>(); ArrayList polycoordinates = (ArrayList) geoDocument.get("coordinates"); for (Object polycoordinate : polycoordinates) { final ArrayList coordinates = (ArrayList) polycoordinate; for (Object coordinate : coordinates) { final ArrayList<Double> point = (ArrayList<Double>) coordinate; List<Point> pointList = new ArrayList<>(); for (Object object : point) { ArrayList<Double> m = (ArrayList<Double>) object; pointList.add(geometryFactory.createPoint(m.get(0), m.get(1))); } polygonList.add(geometryFactory.createPolygon(pointList)); } } return geometryFactory.createMultiPolygon(polygonList); } private Geometry convertPolygon(Document geoDocument) { final ArrayList<Point> polygonPoints = new ArrayList<>(); final ArrayList linearRings = (ArrayList) geoDocument.get("coordinates"); for (Object linearRing : linearRings) { final ArrayList coordinates = (ArrayList) linearRing; for (Object coordinate : coordinates) { final ArrayList<Double> point = (ArrayList<Double>) coordinate; final Point point1 = geometryFactory.createPoint(point.get(0), point.get(1)); polygonPoints.add(point1); } } return geometryFactory.createPolygon(polygonPoints); } private Geometry convertLineString(Document geoDocument) { final ArrayList<Point> lineStringPoints = new ArrayList<>(); final List<ArrayList<Double>> coordinates = (List<ArrayList<Double>>) geoDocument.get("coordinates"); for (ArrayList<Double> point : coordinates) { final Point point1 = geometryFactory.createPoint(point.get(0), point.get(1)); lineStringPoints.add(point1); } return geometryFactory.createLineString(lineStringPoints); } // @todo 2 tb/** make static and add tests 2016-04-21 TimeAxis[] convertToTimeAxes(Document jsonTimeAxes) { final List<Document> timeAxesDocuments = (List<Document>) jsonTimeAxes.get("timeAxes"); final TimeAxis[] timeAxes = new TimeAxis[timeAxesDocuments.size()]; for (int i = 0; i < timeAxesDocuments.size(); i++) { final Document timeAxisDocument = timeAxesDocuments.get(i); final Date startTime = timeAxisDocument.getDate("startTime"); final Date endTime = timeAxisDocument.getDate("endTime"); final LineString geometry = (LineString) convertToGeometry((Document) timeAxisDocument.get("geometry")); timeAxes[i] = geometryFactory.createTimeAxis(geometry, startTime, endTime); } return timeAxes; } // package access for testing only tb 2016-04-20 @SuppressWarnings("unchecked") static List<PolygonCoordinates> gePolygonCoordinates(MultiPolygon multiPolygon) { List<Polygon> s2PolygonList = (List<Polygon>) multiPolygon.getInner(); List<PolygonCoordinates> polygonCoordinatesList = new ArrayList<>(); for (Polygon s2Polygon : s2PolygonList) { ArrayList<Position> positions = extractPointsFromGeometry(s2Polygon.getCoordinates()); if (!positions.get(0).equals(positions.get(positions.size() - 1))) { positions.add(positions.get(0)); } polygonCoordinatesList.add(new PolygonCoordinates(positions)); } return polygonCoordinatesList; } // package access for testing only tb 2016-02-09 static Document createQueryDocument(QueryParameter parameter) { if (parameter == null) { return new Document(); } final Document queryConstraints = new Document(); final Date startTime = parameter.getStartTime(); if (startTime != null) { queryConstraints.append(STOP_TIME_KEY, new Document("$gt", startTime)); } final Date stopTime = parameter.getStopTime(); if (stopTime != null) { queryConstraints.append(START_TIME_KEY, new Document("$lt", stopTime)); } final String sensorName = parameter.getSensorName(); if (StringUtils.isNotNullAndNotEmpty(sensorName)) { queryConstraints.append(SENSOR_KEY + ".name", new Document("$eq", sensorName)); } final String version = parameter.getVersion(); if (StringUtils.isNotNullAndNotEmpty(version)) { queryConstraints.append(VERSION_KEY, new Document("$eq", version)); } final String path = parameter.getPath(); if (StringUtils.isNotNullAndNotEmpty(path)) { queryConstraints.append(DATA_FILE_KEY, new Document("$eq", path)); } return queryConstraints; } // static access for testing only tb 2016-02-09 @SuppressWarnings("unchecked") static com.mongodb.client.model.geojson.Geometry convertToGeoJSON(Geometry geometry) { if (geometry instanceof GeometryCollection) { return convertGeometryCollectionToGeoJSON((GeometryCollection) geometry); } final Point[] coordinates = geometry.getCoordinates(); final ArrayList<Position> geometryPoints = extractPointsFromGeometry(coordinates); if (geometry instanceof MultiPolygon) { List<PolygonCoordinates> polygonCoordinates = gePolygonCoordinates((MultiPolygon) geometry); return new com.mongodb.client.model.geojson.MultiPolygon(polygonCoordinates); } else if (geometry instanceof Polygon) { if (!coordinates[0].equals(coordinates[coordinates.length - 1])) { final Position position = new Position(coordinates[0].getLon(), coordinates[0].getLat()); geometryPoints.add(position); } return new com.mongodb.client.model.geojson.Polygon(geometryPoints); } else if (geometry instanceof LineString) { return new com.mongodb.client.model.geojson.LineString(geometryPoints); } else if (geometry instanceof Point) { return new com.mongodb.client.model.geojson.Point(geometryPoints.get(0)); } throw new RuntimeException("Geometry type support not implemented"); } // package access for testing only tb 2016-03-04 @SuppressWarnings("MismatchedQueryAndUpdateOfCollection") static Document convertToDocument(TimeAxis[] timeAxes) { final List<Document> timeAxesList = new ArrayList<>(); for (final TimeAxis axis : timeAxes) { final Document axisDocument = new Document("startTime", axis.getStartTime()); axisDocument.append("endTime", axis.getEndTime()); axisDocument.append("geometry", convertToGeoJSON(axis.getGeometry())); timeAxesList.add(axisDocument); } return new Document("timeAxes", timeAxesList); } private static com.mongodb.client.model.geojson.Geometry convertGeometryCollectionToGeoJSON( GeometryCollection geometryCollection) { final Geometry[] geometries = geometryCollection.getGeometries(); if (geometries.length == 1) { return convertToGeoJSON(geometries[0]); } final List<com.mongodb.client.model.geojson.Geometry> geometryList = new ArrayList<>(); for (final Geometry geometry : geometries) { geometryList.add(convertToGeoJSON(geometry)); } return new com.mongodb.client.model.geojson.GeometryCollection(geometryList); } private static ArrayList<Position> extractPointsFromGeometry(Point[] coordinates) { final ArrayList<Position> polygonPoints = new ArrayList<>(); for (final Point coordinate : coordinates) { final Position position = new Position(coordinate.getLon(), coordinate.getLat()); polygonPoints.add(position); } return polygonPoints; } // package access for testing only tb 2016-04-21 static String parseAddress(String databaseUrl) { final int slashIndex = databaseUrl.indexOf("//"); final int colonIndex = databaseUrl.indexOf(":", slashIndex); return databaseUrl.substring(slashIndex + 2, colonIndex); } // package access for testing only tb 2016-04-21 static String parsePort(String databaseUrl) { int slashIndex = databaseUrl.indexOf("//"); final int colonIndex = databaseUrl.indexOf(":", slashIndex); slashIndex = databaseUrl.indexOf("/", colonIndex); if (slashIndex > 0) { return databaseUrl.substring(colonIndex + 1, slashIndex); } else { return databaseUrl.substring(colonIndex + 1); } } }