/* Copyright (c) 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: * Gabriel Roldan (Boundless) - initial implementation */ package org.locationtech.geogig.rest.osm; import java.io.File; import java.net.URL; import java.util.List; import javax.annotation.Nullable; import org.locationtech.geogig.api.AbstractGeoGigOp; import org.locationtech.geogig.api.Context; import org.locationtech.geogig.osm.internal.OSMDownloadOp; import org.locationtech.geogig.osm.internal.OSMReport; import org.locationtech.geogig.osm.internal.OSMUpdateOp; import org.locationtech.geogig.osm.internal.OSMUtils; import org.locationtech.geogig.rest.AsyncContext; import org.locationtech.geogig.rest.AsyncContext.AsyncCommand; import org.locationtech.geogig.rest.TransactionalResource; import org.locationtech.geogig.rest.Variants; import org.locationtech.geogig.web.api.CommandSpecException; import org.restlet.data.Form; import org.restlet.data.MediaType; import org.restlet.data.Request; import org.restlet.data.Response; import org.restlet.resource.Representation; import org.restlet.resource.Variant; import com.google.common.base.Optional; import com.google.common.base.Splitter; import com.google.common.base.Strings; /** * Imports data from OSM using the Overpass API. * <ul> * <li>filter: Optional, or mandatory if {@code bbox} is not give. The filter file to use. Must * exist in the server filesystem and contain an <a * href="http://wiki.openstreetmap.org/wiki/Overpass_API/Overpass_QL">Overpass QL filter</a>. * <li>bbox: Mandatory if {@code filter} is not given. The bounding box to use as filter, in WGS84 * coordinates. Format: {@code <S>,<W>,<N>,<E>}. * <li>message: Message for the commit to create. * <li>update: Boolean. Default: false. Update the OSM data currently in the geogig repository * <li>rebase: Boolean. Default: false. Use rebase instead of merge when updating. Can only be true * if upate = true. * <li>mapping: The file that contains the data mapping to use". * </ul> * */ public class OsmDownloadWebOp extends TransactionalResource { @Override public void init(org.restlet.Context context, Request request, Response response) { super.init(context, request, response); getVariants().add(Variants.XML); getVariants().add(Variants.JSON); } @Override public Representation getRepresentation(final Variant variant) { final Request request = getRequest(); Context context = getContext(request); Form options = getRequest().getResourceRef().getQueryAsForm(); final String filterFileArg = options.getFirstValue("filter"); final String bboxArg = options.getFirstValue("bbox"); final String messageArg = options.getFirstValue("message"); final boolean update = Boolean.valueOf(options.getFirstValue("update")); final boolean rebase = Boolean.valueOf(options.getFirstValue("rebase")); final String mappingFileArg = options.getFirstValue("mapping"); checkArgSpec(filterFileArg != null ^ bboxArg != null || update, "You must specify a filter file or a bounding box"); checkArgSpec((filterFileArg != null || bboxArg != null) ^ update, "Filters cannot be used when updating"); checkArgSpec(context.index().isClean() && context.workingTree().isClean(), "Working tree and index are not clean"); checkArgSpec(!rebase || update, "rebase switch can only be used when updating"); final File filterFile = parseFile(filterFileArg); final File mappingFile = parseFile(mappingFileArg); final List<String> bbox = parseBbox(bboxArg); checkArgSpec(filterFile == null || filterFile.exists(), "The specified filter file does not exist"); checkArgSpec(mappingFile == null || mappingFile.exists(), "The specified mapping file does not exist"); AbstractGeoGigOp<Optional<OSMReport>> command; if (update) { command = context.command(OSMUpdateOp.class).setRebase(rebase).setMessage(messageArg) .setAPIUrl(OSMUtils.DEFAULT_API_ENDPOINT); } else { command = context.command(OSMDownloadOp.class).setBbox(bbox).setFilterFile(filterFile) .setMessage(messageArg).setMappingFile(mappingFile) .setOsmAPIUrl(OSMUtils.DEFAULT_API_ENDPOINT); } AsyncCommand<Optional<OSMReport>> asyncCommand; URL repo = context.repository().getLocation(); String description = String .format("osm download filter: %s, bbox: %s, mapping: %s, update: %s, rebase: %s, repository: %s", filterFileArg, bboxArg, mappingFileArg, update, rebase, repo); asyncCommand = AsyncContext.get().run(command, description); final String rootPath = request.getRootRef().toString(); MediaType mediaType = variant.getMediaType(); Representation rep = new OSMReportRepresentation(mediaType, asyncCommand, rootPath); return rep; } private void checkArgSpec(boolean expression, String messageHeader) throws CommandSpecException { if (!expression) { throw usageMessage(messageHeader); } } @Nullable private File parseFile(@Nullable String filePath) throws CommandSpecException { if (Strings.isNullOrEmpty(filePath)) { return null; } File f = new File(filePath); return f; } @Nullable private List<String> parseBbox(@Nullable String bboxArg) { if (bboxArg == null) { return null; } List<String> bbox = Splitter.on(",").splitToList(bboxArg); checkArgSpec(bbox.size() == 4, "Invalid bbox format: " + bboxArg + ". Expected S,W,N,E"); double s = parseOrdinate(bbox.get(0)); double w = parseOrdinate(bbox.get(1)); double n = parseOrdinate(bbox.get(2)); double e = parseOrdinate(bbox.get(3)); checkArgSpec(n >= s, "South ordinate must be less than or equal to North ordinate"); checkArgSpec(e >= w, "East ordinate must be less than or equal to West ordinate"); return bbox; } private double parseOrdinate(String ordinate) { try { return Double.parseDouble(ordinate); } catch (NumberFormatException e) { throw usageMessage("Invalid ordinate: " + ordinate); } } private CommandSpecException usageMessage(String messageHeader) throws CommandSpecException { String msg = messageHeader + "\nUsage: GET <repo context>/osm/download?<[filter=<filterfile>]|[bbox=S,W,N,E]>[&message=<commit message>]" + "[&mapping=<mapping file>][&update=true|false*][&rebase=true|false*]\n" + "Arguments:\n" + " * filter: Optional, or mandatory if {@code bbox} is not give. The filter file to use. Must exist in the server filesystem and contain an Overpass QL filter.\n" + " * bbox: Mandatory if {@code filter} is not given. The bounding box to use as filter, in WGS84 coordinates. Format: {@code <S>,<W>,<N>,<E>}.\n" + " * message: Message for the commit to create.\n" + " * update: Boolean. Default: false. Update the OSM data currently in the geogig repository.\n" + " * rebase: Boolean. Default: false. Use rebase instead of merge when updating. Can only be true of update is true.\n" + " * mapping: The file that contains the data mapping to use\n"; throw new CommandSpecException(msg); } }