/* This program 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, 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.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package org.opentripplanner.updater.bike_park;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.prefs.Preferences;
import com.fasterxml.jackson.databind.JsonNode;
import org.opentripplanner.graph_builder.linking.SimpleStreetSplitter;
import org.opentripplanner.routing.bike_park.BikePark;
import org.opentripplanner.routing.bike_rental.BikeRentalStationService;
import org.opentripplanner.routing.edgetype.BikeParkEdge;
import org.opentripplanner.routing.graph.Graph;
import org.opentripplanner.routing.vertextype.BikeParkVertex;
import org.opentripplanner.updater.GraphUpdaterManager;
import org.opentripplanner.updater.GraphWriterRunnable;
import org.opentripplanner.updater.PollingGraphUpdater;
import org.opentripplanner.updater.JsonConfigurable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Dynamic bike park updater which encapsulate one BikeParkDataSource.
*
* Usage example ('fietsstalling' name is an example) in the file 'Graph.properties':
*
* <pre>
* fietsstalling.type = bike-park
* fietsstalling.frequencySec = 600
* fietsstalling.sourceType = kml-placemarks
* fietsstalling.url = http://host.tld/fietsstalling.kml
* </pre>
*
* Bike park-and-ride and "OV-fiets mode" development has been funded by GoAbout
* (https://goabout.com/).
*
* @author laurent
* @author GoAbout
*/
public class BikeParkUpdater extends PollingGraphUpdater {
private static final Logger LOG = LoggerFactory.getLogger(BikeParkUpdater.class);
private GraphUpdaterManager updaterManager;
Map<BikePark, BikeParkVertex> verticesByPark = new HashMap<BikePark, BikeParkVertex>();
private BikeParkDataSource source;
private Graph graph;
private SimpleStreetSplitter linker;
private BikeRentalStationService bikeService;
public BikeParkUpdater() {
}
@Override
public void setGraphUpdaterManager(GraphUpdaterManager updaterManager) {
this.updaterManager = updaterManager;
}
@Override
protected void configurePolling(Graph graph, JsonNode config) throws Exception {
// Set source from preferences
String sourceType = config.path("sourceType").asText();
BikeParkDataSource source = null;
if (sourceType != null) {
if (sourceType.equals("kml-placemarks")) {
source = new KmlBikeParkDataSource();
}
}
if (source == null) {
throw new IllegalArgumentException("Unknown bike rental source type: " + sourceType);
} else if (source instanceof JsonConfigurable) {
((JsonConfigurable) source).configure(graph, config);
}
// Configure updater
this.graph = graph;
this.source = source;
LOG.info("Creating bike-park updater running every {} seconds : {}", frequencySec, source);
}
@Override
public void setup() throws InterruptedException, ExecutionException {
// Creation of network linker library will not modify the graph
linker = new SimpleStreetSplitter(graph);
// Adding a bike park station service needs a graph writer runnable
updaterManager.executeBlocking(new GraphWriterRunnable() {
@Override
public void run(Graph graph) {
bikeService = graph.getService(BikeRentalStationService.class, true);
}
});
}
@Override
protected void runPolling() throws Exception {
LOG.debug("Updating bike parks from " + source);
if (!source.update()) {
LOG.debug("No updates");
return;
}
List<BikePark> bikeParks = source.getBikeParks();
// Create graph writer runnable to apply these stations to the graph
BikeParkGraphWriterRunnable graphWriterRunnable = new BikeParkGraphWriterRunnable(bikeParks);
updaterManager.execute(graphWriterRunnable);
}
@Override
public void teardown() {
}
private class BikeParkGraphWriterRunnable implements GraphWriterRunnable {
private List<BikePark> bikeParks;
private BikeParkGraphWriterRunnable(List<BikePark> bikeParks) {
this.bikeParks = bikeParks;
}
@Override
public void run(Graph graph) {
// Apply stations to graph
Set<BikePark> bikeParkSet = new HashSet<BikePark>();
/* Add any new park and update space available for existing parks */
for (BikePark bikePark : bikeParks) {
bikeService.addBikePark(bikePark);
bikeParkSet.add(bikePark);
BikeParkVertex bikeParkVertex = verticesByPark.get(bikePark);
if (bikeParkVertex == null) {
bikeParkVertex = new BikeParkVertex(graph, bikePark);
if (!linker.link(bikeParkVertex)) {
// the toString includes the text "Bike park"
LOG.warn("{} not near any streets; it will not be usable.", bikePark);
}
verticesByPark.put(bikePark, bikeParkVertex);
new BikeParkEdge(bikeParkVertex);
} else {
bikeParkVertex.setSpacesAvailable(bikePark.spacesAvailable);
}
}
/* Remove existing parks that were not present in the update */
List<BikePark> toRemove = new ArrayList<BikePark>();
for (Entry<BikePark, BikeParkVertex> entry : verticesByPark.entrySet()) {
BikePark bikePark = entry.getKey();
if (bikeParkSet.contains(bikePark))
continue;
BikeParkVertex vertex = entry.getValue();
if (graph.containsVertex(vertex)) {
graph.removeVertexAndEdges(vertex);
}
toRemove.add(bikePark);
bikeService.removeBikePark(bikePark);
// TODO: need to unsplit any streets that were split
}
for (BikePark bikePark : toRemove) {
// post-iteration removal to avoid concurrent modification
verticesByPark.remove(bikePark);
}
}
}
}