/* Copyright (c) 2013-2014 Boundless and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Distribution License v1.0 * which accompanies this distribution, and is available at * https://www.eclipse.org/org/documents/edl-v10.html * * Contributors: * Victor Olaya (Boundless) - initial implementation */ package org.locationtech.geogig.osm.internal; import static com.google.common.base.Preconditions.checkNotNull; import java.util.Iterator; import java.util.List; import java.util.Map; import javax.annotation.Nullable; import org.locationtech.geogig.api.AbstractGeoGigOp; import org.locationtech.geogig.api.FeatureBuilder; import org.locationtech.geogig.api.NodeRef; import org.locationtech.geogig.api.ObjectId; import org.locationtech.geogig.api.RevFeature; import org.locationtech.geogig.api.RevFeatureTypeImpl; import org.locationtech.geogig.api.RevTree; import org.locationtech.geogig.api.plumbing.LsTreeOp; import org.locationtech.geogig.api.plumbing.LsTreeOp.Strategy; import org.locationtech.geogig.api.plumbing.RevObjectParse; import org.locationtech.geogig.api.plumbing.RevParse; import org.locationtech.geogig.api.porcelain.AddOp; import org.locationtech.geogig.api.porcelain.CommitOp; import org.locationtech.geogig.osm.internal.log.OSMMappingLogEntry; import org.locationtech.geogig.osm.internal.log.WriteOSMMappingEntries; import org.opengis.feature.Feature; import com.google.common.base.Function; import com.google.common.base.Optional; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterators; /** * Creates new data in a geogig repository, based on the current OSM data in the repository and a * mapping that defines the schema to use for creating new features and the destination trees. * * The source data used is the working tree data in the "node" and "way" trees. * */ public class OSMMapOp extends AbstractGeoGigOp<RevTree> { /** * The mapping to use */ private Mapping mapping; /** * The message to use for the commit to create */ private String message; /** * Sets the mapping to use * * @param mapping the mapping to use * @return {@code this} */ public OSMMapOp setMapping(Mapping mapping) { this.mapping = mapping; return this; } /** * Sets the message to use for the commit that is created * * @param message the commit message * @return {@code this} */ public OSMMapOp setMessage(String message) { this.message = message; return this; } @Override protected RevTree _call() { checkNotNull(mapping); long staged = index().countStaged(null).count(); long unstaged = workingTree().countUnstaged(null).count(); Preconditions.checkState((staged == 0 && unstaged == 0), "You must have a clean working tree and index to perform a mapping."); ObjectId oldTreeId = workingTree().getTree().getId(); Iterator<Feature> nodes; if (mapping.canUseNodes()) { nodes = getFeatures("WORK_HEAD:node"); } else { nodes = Iterators.emptyIterator(); } Iterator<Feature> ways; if (mapping.canUseWays()) { ways = getFeatures("WORK_HEAD:way"); } else { ways = Iterators.emptyIterator(); } Iterator<Feature> iterator = Iterators.concat(nodes, ways); if (iterator.hasNext()) { FeatureMapFlusher insertsByParent = new FeatureMapFlusher(workingTree()); while (iterator.hasNext()) { Feature feature = iterator.next(); List<MappedFeature> mappedFeatures = mapping.map(feature); if (!mappedFeatures.isEmpty()) { for (MappedFeature mapped : mappedFeatures) { String path = mapped.getPath(); insertsByParent.put(path, mapped); } } } insertsByParent.flushAll(); ObjectId newTreeId = workingTree().getTree().getId(); // If the mapping generates the same mapped features that already exist, we do nothing if (!newTreeId.equals(oldTreeId)) { command(AddOp.class).call(); command(CommitOp.class).setMessage(message).call(); command(WriteOSMMappingEntries.class).setMapping(mapping) .setMappingLogEntry(new OSMMappingLogEntry(oldTreeId, newTreeId)).call(); } } return workingTree().getTree(); } private Iterator<Feature> getFeatures(String ref) { Optional<ObjectId> id = command(RevParse.class).setRefSpec(ref).call(); if (!id.isPresent()) { return Iterators.emptyIterator(); } LsTreeOp op = command(LsTreeOp.class).setStrategy(Strategy.DEPTHFIRST_ONLY_FEATURES) .setReference(ref); Iterator<NodeRef> iterator = op.call(); Function<NodeRef, Feature> nodeRefToFeature = new Function<NodeRef, Feature>() { private final Map<String, FeatureBuilder> builders = // ImmutableMap.<String, FeatureBuilder> of(// OSMUtils.NODE_TYPE_NAME, // new FeatureBuilder(RevFeatureTypeImpl.build(OSMUtils.nodeType())), // OSMUtils.WAY_TYPE_NAME,// new FeatureBuilder(RevFeatureTypeImpl.build(OSMUtils.wayType()))); private final RevObjectParse parseCommand = command(RevObjectParse.class); @Override @Nullable public Feature apply(@Nullable NodeRef ref) { RevFeature revFeature = parseCommand.setObjectId(ref.objectId()) .call(RevFeature.class).get(); final String parentPath = ref.getParentPath(); FeatureBuilder featureBuilder = builders.get(parentPath); String fid = ref.name(); Feature feature = featureBuilder.build(fid, revFeature); return feature; } }; return Iterators.transform(iterator, nodeRefToFeature); } }