/** * Copyright (C) 2016 eBusiness Information * * This file is part of OSM Contributor. * * OSM Contributor 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. * * OSM Contributor 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 OSM Contributor. If not, see <http://www.gnu.org/licenses/>. */ package io.jawg.osmcontributor.rest; import android.support.annotation.NonNull; import org.greenrobot.eventbus.EventBus; import java.util.ArrayList; import java.util.List; import java.util.Map; import io.jawg.osmcontributor.BuildConfig; import io.jawg.osmcontributor.ui.managers.PoiManager; import io.jawg.osmcontributor.model.entities.Poi; import io.jawg.osmcontributor.model.entities.PoiType; import io.jawg.osmcontributor.model.entities.PoiTypeTag; import io.jawg.osmcontributor.database.PoiAssetLoader; import io.jawg.osmcontributor.rest.mappers.PoiMapper; import io.jawg.osmcontributor.rest.dtos.osm.ChangeSetDto; import io.jawg.osmcontributor.rest.dtos.osm.NodeDto; import io.jawg.osmcontributor.rest.dtos.osm.OsmDto; import io.jawg.osmcontributor.rest.dtos.osm.TagDto; import io.jawg.osmcontributor.rest.dtos.osm.WayDto; import io.jawg.osmcontributor.rest.events.error.SyncDownloadRetrofitErrorEvent; import io.jawg.osmcontributor.rest.events.error.SyncUploadRetrofitErrorEvent; import io.jawg.osmcontributor.rest.clients.OsmRestClient; import io.jawg.osmcontributor.rest.clients.OverpassRestClient; import io.jawg.osmcontributor.utils.Box; import retrofit.RetrofitError; import retrofit.mime.TypedString; import timber.log.Timber; import static java.util.Collections.singletonList; /** * Implementation of a {@link Backend} for an OpenStreetMap backend. */ public class OsmBackend implements Backend { PoiManager poiManager; PoiAssetLoader poiAssetLoader; OSMProxy osmProxy; OverpassRestClient overpassRestClient; OsmRestClient osmRestClient; PoiMapper poiMapper; EventBus bus; public static final String[] OBJECT_TYPES = new String[]{"node", "way"}; public OsmBackend(EventBus bus, OSMProxy osmProxy, OverpassRestClient overpassRestClient, OsmRestClient osmRestClient, PoiMapper poiMapper, PoiManager poiManager, PoiAssetLoader poiAssetLoader) { this.bus = bus; this.osmProxy = osmProxy; this.overpassRestClient = overpassRestClient; this.osmRestClient = osmRestClient; this.poiMapper = poiMapper; this.poiManager = poiManager; this.poiAssetLoader = poiAssetLoader; } /** * {@inheritDoc} */ @Override public String initializeTransaction(String comment) { final OsmDto osmDto = new OsmDto(); ChangeSetDto changeSetDto = new ChangeSetDto(); List<TagDto> tagDtos = new ArrayList<>(); osmDto.setChangeSetDto(changeSetDto); tagDtos.add(new TagDto(comment, "comment")); tagDtos.add(new TagDto(BuildConfig.APP_NAME + " " + BuildConfig.VERSION_NAME, "created_by")); changeSetDto.setTagDtoList(tagDtos); OSMProxy.Result<String> result = osmProxy.proceed(new OSMProxy.NetworkAction<String>() { @Override public String proceed() { String changeSetId = osmRestClient.addChangeSet(osmDto); Timber.d("Retrieved changeSet Id: %s", changeSetId); return changeSetId; } }); if (result.isSuccess()) { return result.getResult(); } else if (result.getRetrofitError() != null) { RetrofitError retrofitError = result.getRetrofitError(); Timber.e(retrofitError, "Retrofit error, couldn't create Changeset!"); bus.post(new SyncUploadRetrofitErrorEvent(-1L)); } return null; } /** * {@inheritDoc} */ @Override @NonNull public List<Poi> getPoisInBox(final Box box) { Timber.d("Requesting overpass for download"); OSMProxy.Result<OsmDto> result = osmProxy.proceed(new OSMProxy.NetworkAction<OsmDto>() { @Override public OsmDto proceed() { String request = generateOverpassRequest(box); return overpassRestClient.sendRequest(new TypedString(request)); } }); if (!result.isSuccess()) { if (result.getRetrofitError() != null) { Timber.e(result.getRetrofitError(), "Retrofit error, couldn't download from overpass"); } bus.post(new SyncDownloadRetrofitErrorEvent()); return new ArrayList<>(); } OsmDto osmDto = result.getResult(); return convertPois(osmDto); } @NonNull private List<Poi> convertPois(OsmDto osmDto) { List<Poi> pois = poiMapper.convertDtosToPois(osmDto.getNodeDtoList()); pois.addAll(poiMapper.convertDtosToPois(osmDto.getWayDtoList())); return pois; } private String generateOverpassRequest(Box box) { StringBuilder cmplReq = new StringBuilder("("); Map<Long, PoiType> poiTypes = poiManager.loadPoiTypes(); if (poiTypes.size() > 15) { // we've got lots of pois, overpath will struggle with the finer request, download all the pois in the box for (String type : OBJECT_TYPES) { for (String key : poiManager.loadPoiTypeKeysWithDefaultValues()) { cmplReq.append(type) .append("[\"") .append(key) .append("\"]") .append("(") .append(box.getSouth()).append(",") .append(box.getWest()).append(",") .append(box.getNorth()).append(",") .append(box.getEast()) .append(");"); } } } else { // Download all the pois in the box who are of one of the PoiType contained in the database for (String type : OBJECT_TYPES) { // For each poiTypes, add the corresponding part to the request for (PoiType poiTypeDto : poiTypes.values()) { // Check for tags who have a value and add a ["key"~"value"] string to the request boolean valid = false; for (PoiTypeTag poiTypeTag : poiTypeDto.getTags()) { if (poiTypeTag.getValue() != null) { if (!valid) { cmplReq.append(type); } valid = true; cmplReq.append("[\"") .append(poiTypeTag.getKey()) .append("\"~\"") .append(poiTypeTag.getValue()) .append("\"]"); } } // If there was at least one tag with a value, add the box coordinates to the request if (valid) { cmplReq.append("(") .append(box.getSouth()).append(",") .append(box.getWest()).append(",") .append(box.getNorth()).append(",") .append(box.getEast()) .append(");"); } } } } cmplReq.append(");out meta center;"); return cmplReq.toString(); } /** * {@inheritDoc} */ @Override public CreationResult addPoi(final Poi poi, String transactionId) { final OsmDto osmDto = new OsmDto(); if (poi.getWay()) { WayDto nodeDto = poiMapper.convertPoiToWayDto(poi, transactionId); osmDto.setWayDtoList(singletonList(nodeDto)); } else { NodeDto nodeDto = poiMapper.convertPoiToNodeDto(poi, transactionId); osmDto.setNodeDtoList(singletonList(nodeDto)); } OSMProxy.Result<String> result = osmProxy.proceed(new OSMProxy.NetworkAction<String>() { @Override public String proceed() { String nodeId; if (poi.getWay()) { nodeId = osmRestClient.addWay(osmDto); Timber.d("Created way with id: %s", nodeId); } else { nodeId = osmRestClient.addNode(osmDto); Timber.d("Created node with id: %s", nodeId); } return nodeId; } }); if (result.isSuccess()) { return new CreationResult(ModificationStatus.SUCCESS, result.getResult()); } Timber.e(result.getRetrofitError(), "Couldn't add node %s", poi); return new CreationResult(ModificationStatus.FAILURE_UNKNOWN, null); } /** * {@inheritDoc} */ @Override public UpdateResult updatePoi(final Poi poi, String transactionId) { final OsmDto osmDto = new OsmDto(); if (poi.getWay()) { WayDto nodeDto = poiMapper.convertPoiToWayDto(poi, transactionId); osmDto.setWayDtoList(singletonList(nodeDto)); } else { NodeDto nodeDto = poiMapper.convertPoiToNodeDto(poi, transactionId); osmDto.setNodeDtoList(singletonList(nodeDto)); } OSMProxy.Result<String> result = osmProxy.proceed(new OSMProxy.NetworkAction<String>() { @Override public String proceed() { String version; if (poi.getWay()) { version = osmRestClient.updateWay(poi.getBackendId(), osmDto); Timber.d("Updated way with new version: %s", version); } else { version = osmRestClient.updateNode(poi.getBackendId(), osmDto); Timber.d("Updated node with new version: %s", version); } return version; } }); if (result.isSuccess()) { return new UpdateResult(ModificationStatus.SUCCESS, result.getResult()); } if (result.getRetrofitError() != null) { RetrofitError e = result.getRetrofitError(); if (e.getResponse() != null && e.getResponse().getStatus() == 400) { Timber.e(e, "Couldn't update node, conflicting version"); return new UpdateResult(ModificationStatus.FAILURE_CONFLICT, null); } else if (e.getResponse() != null && e.getResponse().getStatus() == 404) { Timber.e(e, "Couldn't update node, no existing node found with the id " + poi.getId()); return new UpdateResult(ModificationStatus.FAILURE_NOT_EXISTING, null); } else { Timber.e(e, "Couldn't update node"); return new UpdateResult(ModificationStatus.FAILURE_UNKNOWN, null); } } return new UpdateResult(ModificationStatus.FAILURE_UNKNOWN, null); } /** * {@inheritDoc} */ @Override public ModificationStatus deletePoi(final Poi poi, String transactionId) { final OsmDto osmDto = new OsmDto(); if (poi.getWay()) { WayDto nodeDto = poiMapper.convertPoiToWayDto(poi, transactionId); osmDto.setWayDtoList(singletonList(nodeDto)); } else { NodeDto nodeDto = poiMapper.convertPoiToNodeDto(poi, transactionId); osmDto.setNodeDtoList(singletonList(nodeDto)); } OSMProxy.Result<Void> result = osmProxy.proceed(new OSMProxy.NetworkAction<Void>() { @Override public Void proceed() { if (poi.getWay()) { osmRestClient.deleteWay(poi.getBackendId(), osmDto); Timber.d("Deleted way %s", poi.getBackendId()); } else { osmRestClient.deleteNode(poi.getBackendId(), osmDto); Timber.d("Deleted node %s", poi.getBackendId()); } poiManager.deletePoi(poi); return null; } }); if (result.isSuccess()) { return ModificationStatus.SUCCESS; } RetrofitError retrofitError = result.getRetrofitError(); if (retrofitError != null) { if (retrofitError.getResponse() != null && retrofitError.getResponse().getStatus() == 400) { return ModificationStatus.FAILURE_CONFLICT; } else if (retrofitError.getResponse() != null && retrofitError.getResponse().getStatus() == 404) { //the point doesn't exist return ModificationStatus.FAILURE_NOT_EXISTING; } else if (retrofitError.getResponse() != null && retrofitError.getResponse().getStatus() == 410) { //the poi was already deleted return ModificationStatus.FAILURE_NOT_EXISTING; } } return ModificationStatus.FAILURE_UNKNOWN; } /** * {@inheritDoc} */ @Override public Poi getPoiById(final String backendId) { OSMProxy.Result<Poi> result = osmProxy.proceed(new OSMProxy.NetworkAction<Poi>() { @Override public Poi proceed() { OsmDto osmDtoRetrievedNode = osmRestClient.getNode(backendId); List<NodeDto> nodeDtoList = osmDtoRetrievedNode.getNodeDtoList(); if (nodeDtoList != null && nodeDtoList.size() > 0) { List<Poi> pois = poiMapper.convertDtosToPois(nodeDtoList); if (pois.size() > 0) { return pois.get(0); } } return null; } }); return result.getResult(); } /** * {@inheritDoc} */ @Override public List<PoiType> getPoiTypes() { return poiAssetLoader.loadPoiTypesByDefault(); } }