/* 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.graph_builder.impl.shapefile; import java.io.IOException; import java.util.HashMap; import org.geotools.data.FeatureSource; import org.geotools.feature.FeatureCollection; import org.geotools.feature.FeatureIterator; import org.opengis.feature.simple.SimpleFeature; import org.opengis.feature.simple.SimpleFeatureType; import org.opentripplanner.graph_builder.services.shapefile.FeatureSourceFactory; import org.opentripplanner.graph_builder.services.shapefile.SimpleFeatureConverter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * This is a simple feature converter that gets features from a secondary feature source. This is * useful if you have (say) bike lane data in another file. */ public class JoinedFeatureConverter<T> implements SimpleFeatureConverter<T> { private static Logger log = LoggerFactory.getLogger(JoinedFeatureConverter.class); private SimpleFeatureConverter<T> converter; private String joinedKey; private String mainKey; private FeatureSource<SimpleFeatureType, SimpleFeature> joinedSource; private HashMap<String, SimpleFeature> cache; public JoinedFeatureConverter() { } public JoinedFeatureConverter(String mainKey, String joinedKey, SimpleFeatureConverter<T> converter, FeatureSource<SimpleFeatureType, SimpleFeature> joinedSource) { this.mainKey = mainKey; this.joinedKey = joinedKey; this.converter = converter; this.joinedSource = joinedSource; } @Override public T convert(SimpleFeature feature) { ensureCached(); String mainKeyValue = toHashableString(feature.getAttribute(this.mainKey)); if (mainKeyValue == null) { log.warn("Feature " + feature.getID() + " has null value for its mainKey (" + mainKey + ")"); return null; } SimpleFeature joinedFeature = cache.get(mainKeyValue); if (joinedFeature == null) { return null; } else { return converter.convert(joinedFeature); } } /** We have to cache all the features in the supplemental file, because * if we try to load them on the fly, GeoTools wigs out. */ private void ensureCached() { if (cache != null) { return; } cache = new HashMap<String, SimpleFeature>(); try { FeatureCollection<SimpleFeatureType, SimpleFeature> features = joinedSource .getFeatures(); FeatureIterator<SimpleFeature> it = features.features(); while (it.hasNext()) { SimpleFeature feature = it.next(); String joinedKeyValue = toHashableString(feature.getAttribute(joinedKey)); if (joinedKeyValue != null) { cache.put(joinedKeyValue, feature); } else { log.warn("Feature " + feature.getID() + " has null value for its joinedKey (" + joinedKey + ")"); } } it.close(); } catch (IOException e) { throw new RuntimeException("Could not cache values for joined shapefile", e); } } /** * Convert a feature value to a String for hashing. We use this instead of simply calling * toString to avoid issues when the column types for these features are slightly different. See * http://opentripplanner.org/ticket/226 * * @param keyValue * @return a string to use as the hash key */ private String toHashableString(Object keyValue) { if (keyValue == null) { return null; } if (keyValue instanceof Number) { keyValue = ((Number)keyValue).doubleValue(); } return keyValue.toString(); } public void setConverter(SimpleFeatureConverter<T> converter) { this.converter = converter; } /** * The name of the attribute in the joined feature source to use as the join key. */ public void setJoinedKey(String joinedKey) { this.joinedKey = joinedKey; } /** * The name of the attribute in the main feature source to use as the join key. */ public void setMainKey(String mainKey) { this.mainKey = mainKey; } public void setJoinedSourceFactory(FeatureSourceFactory factory) { this.joinedSource = factory.getFeatureSource(); } public void setJoinedSource(FeatureSource<SimpleFeatureType, SimpleFeature> joinedSource) { this.joinedSource = joinedSource; } }