diff --git a/.circleci/config.yml b/.circleci/config.yml index 27eea45240..ec9151ee72 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -3,7 +3,7 @@ jobs: build: working_directory: /home/circleci/app docker: - - image: circleci/python:3-stretch-browsers + - image: circleci/python:3-buster-browsers steps: - checkout - setup_remote_docker @@ -15,6 +15,14 @@ jobs: sudo apt-get install -y nodejs libgeos-dev # Required for shapely sudo npm install -g @mapbox/cfn-config @mapbox/cloudfriend sudo pip3 install awscli --upgrade + - run: + name: Install GDAL and dependencies for pip gdal + command: | + echo "deb-src http://deb.debian.org/debian buster main" | sudo tee /etc/apt/sources.list.d/source-repos-tmp.list > /dev/null + cat "/etc/apt/sources.list.d/source-repos-tmp.list" + sudo apt update + sudo apt build-dep -y python3-gdal + sudo apt install gdal-bin libgdal-dev - run: name: Set folder permissions command: | @@ -39,14 +47,17 @@ jobs: virtualenv env $CIRCLE_WORKING_DIRECTORY/env/bin/pip install --upgrade pip $CIRCLE_WORKING_DIRECTORY/env/bin/pip install -r requirements.txt + # Install python gdal -- it MUST match the version of gdal installed + $CIRCLE_WORKING_DIRECTORY/env/bin/pip install gdal==$(gdalinfo --version | awk '{print $2}' | awk -F, '{print $1}') - run: name: Run JS Unit tests command: | # JS Unit Tests cd $CIRCLE_WORKING_DIRECTORY/tests/client mkdir $CIRCLE_WORKING_DIRECTORY/tests/client/junit + export OPENSSL_CONF=/etc/ssl/ $CIRCLE_WORKING_DIRECTORY/client/node_modules/.bin/karma start ./karma.conf.js \ - --single-run --browsers PhantomJS --reporters junit + --single-run --browsers FirefoxHeadless,ChromeHeadless --reporters junit environment: JUNIT_REPORT_PATH: $CIRCLE_WORKING_DIRECTORY/tests/client/junit/ JUNIT_REPORT_NAME: test-results.xml @@ -248,7 +259,7 @@ workflows: - deployment/hot-tasking-manager requires: - build - stack_name: "prod-restored" + stack_name: "production" environment_name: "production" postgres_db: POSTGRES_DB_PRODUCTION postgres_password: POSTGRES_PASSWORD_PRODUCTION diff --git a/.gitignore b/.gitignore index 265de0398e..9bf9297ce2 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,8 @@ tasking-manager*.env client/node_modules/ client/assets/styles/css client/app/taskingmanager.config.js +client/id_preset_categories.json +client/app/id_preset_categories.js client/package-lock.json client/yarn.lock @@ -13,8 +15,6 @@ client/yarn.lock server/web/static/dist server/web/static/dist/ -# Ignore project files # -server/project-files # Ignore local logs logs/ @@ -47,6 +47,14 @@ Thumbs.db .elasticbeanstalk/ venv_aws/ +# VSCode +.vscode + +# Elastic Beanstalk Files +.elasticbeanstalk/* +!.elasticbeanstalk/*.cfg.yml +!.elasticbeanstalk/*.global.yml + # emacs files # \#*\# -*~ \ No newline at end of file +*~ diff --git a/Makefile b/Makefile index 0d918d54ad..9d4784a16a 100644 --- a/Makefile +++ b/Makefile @@ -30,7 +30,7 @@ refresh-translations: tests:test-client test-server test-client: - docker-compose exec app sh -c "cd /usr/src/app/tests/client && ../../client/node_modules/.bin/karma start ./karma.conf.js --single-run --browsers PhantomJS" + docker-compose exec app sh -c "cd /usr/src/app/tests/client && ../../client/node_modules/.bin/karma start ./karma.conf.js --single-run --browsers FirefoxHeadless,ChromeHeadless" test-server: docker-compose exec app sh -c "python -m unittest discover tests/server" diff --git a/client/app/about/about.html b/client/app/about/about.html index cdc8bdf644..afb6e38fd9 100644 --- a/client/app/about/about.html +++ b/client/app/about/about.html @@ -35,7 +35,7 @@

{{ 'Contact Information' | translate }}

{{ 'Partnership and Funding' | translate }}

{{ 'The Tasking Manager was designed and built for the' | translate }} - {{'Kaart Team' | translate}}. + Kaart Team.

{{ 'With the invaluable support of:' | translate }} diff --git a/client/app/about/faq.html b/client/app/about/faq.html index ce7b0763c7..37136fec98 100644 --- a/client/app/about/faq.html +++ b/client/app/about/faq.html @@ -17,7 +17,7 @@

{{'What is Tasking Manager 3 (TM3)?'| translate }}

{{'A new and improved version of the Tasking Manager was developed by HOT with support by USAID and DFAT during 2017. More information about what’s new in TM3 can be found here: '| translate }} https://tasks.kaart.com/what-is-new. + href="https://tasks.kaart.com/what-is-new">https://tasks.kaart.com/what-is-new.

{{'When will TM3 be launched?'| translate }}

diff --git a/client/app/admin/create-project/create-project.html b/client/app/admin/create-project/create-project.html index f5d4e7dc96..5bfa2468fc 100644 --- a/client/app/admin/create-project/create-project.html +++ b/client/app/admin/create-project/create-project.html @@ -86,8 +86,9 @@

{{ 'Step 1: Define Area' | translate }}

-
Option 4:
+
Option 3:
+

Create tasks from Mapillary Sequences.

Draw Bounding Box. Necessary to create Mapillary Sequences.

+

+

+ + + +
+
+ +

{{ 'This will remove the custom editor from the project. Are you sure you don\'t want to disable the custom editor by toggling the "Enabled" checkbox above?' | translate }}

+ +
+ + + +
@@ -723,6 +823,9 @@

{{ 'In this area' | translate }}

{{ 'Instructions for the default locale' | translate }} "{{ editProjectCtrl.project.defaultLocale }}" {{ 'are missing.' | translate }}

+

+ {{ 'Select at least one iD preset or disable iD preset limiting.' | translate }} +

{{ 'Please make sure you only have one campaign tag.' | translate }}

diff --git a/client/app/home/home.html b/client/app/home/home.html index 8e5c43e8dd..d73f5f5031 100644 --- a/client/app/home/home.html +++ b/client/app/home/home.html @@ -3,7 +3,7 @@

{{ 'Mapping Tasks Managed by Kaart' | translate }}

-

{{ 'Join coordinated humanitarian mapping projects by taking a task and mapping a part of the world in' | translate }} OpenStreetMap{{', the free and editable map of the world. Communities, organizations and governments worldwide have access to these maps to address local development challenges and aid disaster response. You can join thousands of others to map' | translate}} OpenStreetMap {{ 'and support these communities in need.' | translate }}

+

{{ 'Join coordinated Kaart mapping projects by taking a task and mapping a part of the world in '}} OpenStreetMap{{', the free and editable map of the world. Communities, organizations and governments across the world have access to these maps to address local development challenges. You can join thousands of others to map '}}OpenStreetMap {{'and support these communities in need.' | translate }}

{{ 'Start Mapping' | translate }} @@ -44,6 +44,48 @@

+
mapping on phones and computers diff --git a/client/app/message/message.controller.js b/client/app/message/message.controller.js index 8ecd412ddd..cb9f5c5b4e 100644 --- a/client/app/message/message.controller.js +++ b/client/app/message/message.controller.js @@ -55,8 +55,8 @@ vm.messageIdToBeDeleted = messageId; vm.showDeleteMessageModal = true; }; - - /** + + /** * Redirect to inbox after message is deleted **/ vm.redirectAfterDelete = function(){ @@ -72,11 +72,11 @@ var resultsPromise = messageService.deleteMessage(id); resultsPromise.then(function (data) { // success - vm.redirectAfterDelete(); + vm.redirectAfterDelete(); }, function () { // an error occurred vm.deleteMessageFail = true; - }); + }); }; diff --git a/client/app/profile/profile.html b/client/app/profile/profile.html index 490259dbee..57cdd61d09 100644 --- a/client/app/profile/profile.html +++ b/client/app/profile/profile.html @@ -120,8 +120,21 @@

OSM details

- +

{{ 'Thanks for providing your contact info. We’ve sent you a confirmation email that you can check at a later time. Now you can get started mapping! If you’ve kept a task open, make sure to refresh the page.' | translate }}

+

+ {{ 'Not a valid email address' | translate }} +

+

+ {{ 'Error: setting your contact details' | translate }} +

+

+ {{ 'Error: sending your verification email' }} +

+

+ {{ 'A verification email was sent.' | translate }} +

+

  • @@ -179,7 +192,6 @@

    OSM details

    {{ "Please provide an email address to receive notifications about your mapping" | translate }}
  • - -

    - {{ 'Not a valid email address' | translate }} -

    -

    - {{ 'Error: setting your contact details' | translate }} -

    -

    - {{ 'Error: sending your verification email' }} -

    -

    - {{ 'A verification email was sent.' | translate }} -


    diff --git a/client/app/project/project.controller.js b/client/app/project/project.controller.js index 7ececa1d7c..80304dd34f 100644 --- a/client/app/project/project.controller.js +++ b/client/app/project/project.controller.js @@ -96,6 +96,9 @@ // Augmented diff or attic query selection vm.selectedItem = null; + // Project Files + vm.project_files = [] + //interval timer promise for autorefresh var autoRefresh = undefined; @@ -547,11 +550,19 @@ vm.selectedMappingEditor = vm.mappingEditors[0].value; vm.validationEditors = createEditorList(vm.projectData.validationEditors); vm.selectedValidationEditor = vm.validationEditors[0].value; - vm.userCanMap = vm.user && projectService.userCanMapProject(vm.user.mappingLevel, vm.projectData.mapperLevel, vm.projectData.enforceMapperLevel); - vm.userCanValidate = vm.user && projectService.userCanValidateProject(vm.user.role, vm.user.mappingLevel, vm.projectData.enforceValidatorRole, vm.projectData.allowNonBeginners); + // No idea why we had to do it this way but the old way wasn't working + // no touchy + if (vm.user) { + vm.userCanMap = projectService.userCanMapProject(vm.user.mappingLevel, vm.projectData.mapperLevel, vm.projectData.enforceMapperLevel); + vm.userCanValidate = projectService.userCanValidateProject(vm.user.role, vm.user.mappingLevel, vm.projectData.enforceValidatorRole, vm.projectData.allowNonBeginners); // If validator role enforced, show validator+ OSMCha buttons; otherwise show experts too vm.showOSMCha = vm.projectData.enforceValidatorRole ? vm.userCanValidate : - (projectService.userCanValidateProject(vm.user.role, true) || vm.user.isExpert); + (projectService.userCanValidateProject(vm.user.role, true) || vm.user.isExpert); + } + else { + vm.userCanMap, vm.userCanValidate, vm.showOSMCha = false; + } + addAoiToMap(vm.projectData.areaOfInterest); addPriorityAreasToMap(vm.projectData.priorityAreas); addProjectTasksToMap(vm.projectData.tasks, true); @@ -1378,7 +1389,6 @@ * Call api to lock currently selected task for mapping. Will update view and map after unlock. */ vm.lockSelectedTaskMapping = function () { - // console.log(vm.user); // if(vm.user.isEmailVerified){ vm.lockingReason = 'MAPPING'; var projectId = vm.projectData.projectId; @@ -1624,7 +1634,8 @@ changesetComment, imageryUrl, vm.projectData.projectId, - vm.getSelectTaskIds() + vm.getSelectTaskIds(), + vm.projectData.idPresets ); } else if (editor === 'potlatch2') { @@ -1643,79 +1654,99 @@ ); } else if (editor === 'josm') { - - if (taskCount > 1) { - // load a new empty layer in josm for task square(s). This step required to get custom name for layer - // use empty, uri encoded osmxml with upload=never for the data para - var emptyTaskLayerParams = { - new_layer: true, - mime_type: encodeURIComponent('application/x-osm+xml'), - layer_name: encodeURIComponent('Task Boundaries #' + vm.projectData.projectId + '- Do not edit or upload'), - data: encodeURIComponent('') - }; - editorService.sendJOSMCmd('http://127.0.0.1:8111/load_data', emptyTaskLayerParams) - .catch(function() { - //warn that JSOM couldn't be started - vm.editorStartError = 'josm-error'; - }); - - //load task square(s) into JOSM - var taskImportParams = { - url: editorService.getOSMXMLUrl(vm.projectData.projectId, vm.getSelectTaskIds()), - new_layer: false - }; - editorService.sendJOSMCmd('http://127.0.0.1:8111/import', taskImportParams) - .catch(function() { - //warn that JSOM couldn't be started - vm.editorStartError = 'josm-error'; - }); - } - - //load aerial photography if present - // TODO: make changeset source part of project info - var changesetSource = "Kaart Ground Survey 2019"; - var hasImagery = false; - if (imageryUrl && typeof imageryUrl != "undefined" && imageryUrl !== '') { - changesetSource = imageryUrl; - hasImagery = true; + launchJosm( + taskCount, + imageryUrl, + extentTransformed, + changesetComment + ) + } + else if (editor === 'josmmapwithai') { + launchJosm( + taskCount, + imageryUrl, + extentTransformed, + changesetComment, + 'http://127.0.0.1:8111/mapwithai' + ); + } + else if (editor === 'custom'){ + var domain = vm.projectData.customEditor.url.replace('http://','').replace('https://','').split(/[/?#]/)[0]; + var josm_endpoints = ["127.0.0.1:8111", "127.0.0.1:8112", "localhost:8111", "localhost:8112"]; + if (josm_endpoints.includes(domain)) { + launchJosm( + taskCount, + imageryUrl, + extentTransformed, + changesetComment, + vm.projectData.customEditor.url + ) } - if (hasImagery) { - var imageryParams = { - title: encodeURIComponent('Tasking Manager Imagery - #' + vm.projectData.projectId), - type: imageryUrl.toLowerCase().substring(0, 3), - url: encodeURIComponent(imageryUrl) - }; - editorService.sendJOSMCmd('http://127.0.0.1:8111/imagery', imageryParams) - .catch(function() { - //warn that imagery couldn't be loaded - vm.editorStartError = 'josm-imagery-error'; - }); + else{ + editorService.launchCustomIdEditor( + center, + changesetComment, + imageryUrl, + vm.projectData.projectId, + vm.getSelectTaskIds(), + vm.projectData.customEditor.url + ); } + } + }; - // load a new empty layer in josm for osm data, this step necessary to have a custom name for the layer - // use empty, uri encoded osmxml for the data param - var emptyOSMLayerParams = { + function launchJosm( + taskCount, + imageryUrl, + extentTransformed, + changesetComment, + customUrl) { + if (taskCount > 1) { + // load a new empty layer in josm for task square(s). This step required to get custom name for layer + // use empty, uri encoded osmxml with upload=never for the data para + var emptyTaskLayerParams = { new_layer: true, - mime_type: 'application/x-osm+xml', - layer_name: 'OSM Data', - data: encodeURIComponent('') - } - editorService.sendJOSMCmd('http://127.0.0.1:8111/load_data', emptyOSMLayerParams) + mime_type: encodeURIComponent('application/x-osm+xml'), + layer_name: encodeURIComponent('Task Boundaries #' + vm.projectData.projectId + '- Do not edit or upload'), + data: encodeURIComponent('') + }; + editorService.sendJOSMCmd('http://127.0.0.1:8111/load_data', emptyTaskLayerParams) .catch(function() { //warn that JSOM couldn't be started vm.editorStartError = 'josm-error'; }); - var loadAndZoomParams = { - left: extentTransformed[0], - bottom: extentTransformed[1], - right: extentTransformed[2], - top: extentTransformed[3], - changeset_comment: encodeURIComponent(changesetComment), - changeset_source: encodeURIComponent(changesetSource), + //load task square(s) into JOSM + var taskImportParams = { + url: editorService.getOSMXMLUrl(vm.projectData.projectId, vm.getSelectTaskIds()), new_layer: false }; + editorService.sendJOSMCmd('http://127.0.0.1:8111/import', taskImportParams) + .catch(function() { + //warn that JSOM couldn't be started + vm.editorStartError = 'josm-error'; + }); + } + //load aerial photography if present + var changesetSource = "Kaart Ground Survey 2020"; + var hasImagery = false; + if (imageryUrl && typeof imageryUrl != "undefined" && imageryUrl !== '') { + changesetSource = imageryUrl; + hasImagery = true; + } + if (hasImagery) { + var imageryParams = { + title: encodeURIComponent('Tasking Manager Imagery - #' + vm.projectData.projectId), + type: imageryUrl.toLowerCase().substring(0, 3), + url: encodeURIComponent(imageryUrl) + }; + editorService.sendJOSMCmd('http://127.0.0.1:8111/imagery', imageryParams) + .catch(function() { + //warn that imagery couldn't be loaded + vm.editorStartError = 'josm-imagery-error'; + }); + } // this is a future feature to be able to use an overpass query in lieu of the osm data // if (overpassQuery){ @@ -1723,14 +1754,41 @@ // ('http://127.0.0.1:8111/import?url=https://lz4.overpass-api.de/api/interpreter/?data=[out:xml];%20way[highway](53.2987342,-6.3870259,53.4105416,-6.1148829);%20(._;%3E;);%20out%20meta;'); // } - if (taskCount == 1) { - //load OSM data and zoom to the bbox - editorService.sendJOSMCmd('http://127.0.0.1:8111/load_and_zoom', loadAndZoomParams); - } else { - //probably too much OSM data to download, just zoom to the bbox - editorService.sendJOSMCmd('http://127.0.0.1:8111/zoom', loadAndZoomParams); - } + // load a new empty layer in josm for osm data, this step necessary to have a custom name for the layer + // use empty, uri encoded osmxml for the data param + var emptyOSMLayerParams = { + new_layer: true, + mime_type: 'application/x-osm+xml', + layer_name: 'OSM Data', + data: encodeURIComponent('') + } + editorService.sendJOSMCmd('http://127.0.0.1:8111/load_data', emptyOSMLayerParams) + .catch(function() { + //warn that JSOM couldn't be started + vm.editorStartError = 'josm-error'; + }); + var loadAndZoomParams = { + left: extentTransformed[0], + bottom: extentTransformed[1], + right: extentTransformed[2], + top: extentTransformed[3], + changeset_comment: encodeURIComponent(changesetComment), + changeset_source: encodeURIComponent(changesetSource), + new_layer: false + }; + + if (taskCount == 1) { + //load OSM data and zoom to the bbox + editorService.sendJOSMCmd('http://127.0.0.1:8111/load_and_zoom', loadAndZoomParams); + } else { + //probably too much OSM data to download, just zoom to the bbox + editorService.sendJOSMCmd('http://127.0.0.1:8111/zoom', loadAndZoomParams); + } + if (customUrl) { + //give it an empty param dict to avoid breaking things + editorService.sendJOSMCmd(customUrl, {}); + } // if there are project files, send the josm command with the api url to extract data if (vm.currentTab === 'mapping' && vm.project_files.length > 0) { @@ -1760,7 +1818,6 @@ } } } - }; /** * Load a project file into JOSM for validation purposes @@ -1820,7 +1877,39 @@ } return userList; - } + }; + + /** + * Inspects the task history of the currently selected task and, if + * available, returns a moment instance representing the earliest + * action date in the history or null otherwise + */ + vm.earliestHistoryActionDate = function() { + var history = vm.selectedTaskData.taskHistory; + if (history && history.length > 0) { + return moment.min(history.map(function(entry) { + return moment.utc(entry.actionDate); + })); + } + + return null; + }; + + /** + * Inspects the task history of the currently selected task and, if + * available, returns a moment instance representing the earliest + * action date in the history or null otherwise + */ + vm.earliestHistoryActionDate = function() { + var history = vm.selectedTaskData.taskHistory; + if (history && history.length > 0) { + return moment.min(history.map(function(entry) { + return moment.utc(entry.actionDate); + })); + } + + return null; + }; /** * Inspects the task history of the currently selected task and, if @@ -1948,6 +2037,35 @@ $window.open(achaviURL); }; + /** + * Load a project file into JOSM for validation purposes + * @param file + */ + vm.loadProjectFile = function (file) { + var emptyTaskLayerParams = { + new_layer: true, + upload_policy: enumerateUploadPolicy(file.uploadPolicy), + layer_name: encodeURIComponent(file.fileName.replace(/\.[^/.]+$/,"")), + mime_type: encodeURIComponent('application/x-osm+xml'), + data: encodeURIComponent('') + } + editorService.sendJOSMCmd('http://127.0.0.1:8111/load_data', emptyTaskLayerParams) + .catch(function() { + //warn that JSOM couldn't be started + vm.editorStartError = 'josm-error'; + }); + + var projectFileParams = { + new_layer: false, + url: editorService.getProjectFileOSMXMLUrl(vm.projectData.projectId, vm.getSelectTaskIds(), file) + } + editorService.sendJOSMCmd('http://127.0.0.1:8111/import', projectFileParams) + .catch(function() { + //warn that JSOM couldn't be started + vm.editorStartError = 'josm-error'; + }); + }; + /** * Set the accept license modal to visible/invisible * @param showModal @@ -2192,17 +2310,6 @@ }; /** - * Get all project files for a project - * @param project_id - */ - function getProjectFiles(project_id) { - var resultsPromise = projectService.getProjectFiles(project_id); - resultsPromise.then(function (data) { - vm.project_files = data.projectFiles - }) - } - - /* * Creates json objects for editors * @params editors */ @@ -2232,14 +2339,37 @@ "value": "fieldpapers" }); } + if (editors.includes("CUSTOM")) { + result.push({ + "name": vm.projectData.customEditor.name, + "value": "custom" + }); + } if (editors.includes("RAPID")) { result.push({ "name": "RapiD", "value": "rapid" }); } + if (editors.includes("JOSMMAPWITHAI")) { + result.push({ + "name": "JOSM MapWithAI", + "value": "josmmapwithai" + }); + } return result; }; + + /** + * Get all project files for a project + * @param project_id + */ + function getProjectFiles(project_id) { + var resultsPromise = projectService.getProjectFiles(project_id); + resultsPromise.then(function (data) { + vm.project_files = data.projectFiles + }) + } } }) (); diff --git a/client/app/project/project.html b/client/app/project/project.html index 9cb6a500c5..861c93f154 100644 --- a/client/app/project/project.html +++ b/client/app/project/project.html @@ -133,8 +133,7 @@

      - -
    • @@ -142,8 +141,7 @@

      {{ 'Instructions' | translate }}

    • - -
    • @@ -151,8 +149,7 @@

      {{ 'Map' | translate }}

    • - -
    • @@ -160,8 +157,7 @@

      {{ 'Validate' | translate }}

    • - -
    • @@ -255,18 +251,25 @@

      {{ 'Choose a task' | translate }}

    +
    +

    + {{ 'When you contribute to the Tasking Manager, it is important that notifications about the tasks and projects you contributed to are reaching you.' | translate }} {{ 'Before you begin mapping:' | translate }} + {{ 'Please go to your profile and add your email address' | translate }}.

    + {{ 'See here' | translate }} {{ 'for more information about how HOT will use and share your data.' | translate }} +

    +
    - -
    +

    {{ 'Choose a task for mapping:' | translate }}

    @@ -287,7 +290,7 @@

    {{ 'Choose a task' | translate }}

    -
    +

    {{ 'You must randomly select tasks' | translate }}

    -
    - - +
    +

    + {{ 'When you contribute to the Tasking Manager, it is important that notifications about the tasks and projects you contributed to are reaching you.' | translate }} {{ 'Before you begin mapping:' | translate }} + {{ 'Please go to your profile and add your email address' | translate }}.

    + {{ 'See here' | translate }} {{ 'for more information about how HOT will use and share your data.' | translate }} +

    +
    -
    -

    - {{ 'When you contribute on the Tasking Manager, it is important that notifications around the tasks and projects you participated are reaching you. Thus we require your email address.' | translate }} - {{ 'Please go to your profile and add your email address' | translate }}.

    - {{ 'You can inform yourself on our website about' | translate }} - {{ 'how we handle your data' | translate }}. -

    -
    -

    +
    +

    +
    {{ 'This project has accompanying files. To load these files, you must use JOSM to edit.' | translate }}
    @@ -996,8 +1008,8 @@
    {{ 'The following files can be loaded into JOSM as separate layers within th -
    +

    {{ 'Project Questions and Comments' | translate }}

    -
    +
    -
    +

    - {{ 'When you contribute on the Tasking Manager, it is important that notifications around the tasks and projects you participated are reaching you. Thus we require your email address.' | translate }} + {{ 'When you contribute to the Tasking Manager, it is important that notifications about the tasks and projects you contributed to are reaching you.' | translate }} {{ 'Before you begin mapping:' | translate }} {{ 'Please go to your profile and add your email address' | translate }}.

    - {{ 'You can inform yourself on our website about' | translate }} - {{ 'how we handle your data' | translate }}. + {{ 'See here' | translate }} {{ 'for more information about how HOT will use and share your data.' | translate }}

    +
    + +
    diff --git a/client/app/services/account.service.js b/client/app/services/account.service.js index 180f335a46..c6551f2b24 100644 --- a/client/app/services/account.service.js +++ b/client/app/services/account.service.js @@ -81,5 +81,12 @@ return $q.reject("error"); }) } + + /** + * Returns true if the logged-in user has expert mode enabled + */ + function isExpert() { + return accountService.getAccount() && accountService.getAccount().isExpert; + } } })(); diff --git a/client/app/services/editor.service.js b/client/app/services/editor.service.js index 684655974e..f3a31f2852 100644 --- a/client/app/services/editor.service.js +++ b/client/app/services/editor.service.js @@ -6,22 +6,25 @@ angular .module('taskingManager') - .service('editorService', ['$window', '$location', '$q', 'mapService', 'configService', editorService]); + .service('editorService', ['$window', '$location', '$q', 'mapService', 'configService', 'categories', editorService]); var JOSM_COMMAND_TIMEOUT = 1000; var josmLastCommand = 0; - function editorService($window, $location, $q, mapService, configService) { + function editorService($window, $location, $q, mapService, configService, idPresetCategories) { var service = { sendJOSMCmd: sendJOSMCmd, launchFieldPapersEditor: launchFieldPapersEditor, launchPotlatch2Editor: launchPotlatch2Editor, launchIdEditor: launchIdEditor, + launchCustomIdEditor: launchCustomIdEditor, launchRapidEditor: launchRapidEditor, getGPXUrl: getGPXUrl, getOSMXMLUrl: getOSMXMLUrl, - getProjectFileOSMXMLUrl: getProjectFileOSMXMLUrl + idPresetCategories: idPresetCategories, + allIdPresets: allIdPresets, + getProjectFileOSMXMLUrl: getProjectFileOSMXMLUrl, }; return service; @@ -56,7 +59,7 @@ * @param projectId * @param taskId */ - function launchIdEditor(centroid, changesetComment, imageryUrl, projectId, taskId){ + function launchIdEditor(centroid, changesetComment, imageryUrl, projectId, taskId, presets){ var base = 'http://www.openstreetmap.org/edit?editor=id&'; var zoom = mapService.getOSMMap().getView().getZoom(); var url = base + '#map=' + @@ -78,6 +81,77 @@ if (projectId && projectId !== '' && taskId && taskId !== '') { url += "&gpx=" + getGPXUrl(projectId, taskId); } + // Add preset limits + if (presets && presets.length > 0) { + url += "&presets=" + encodeURIComponent(presets.join(',')); + } + $window.open(url); + } + + /** + * Launch custom iD editor + * @param centroid + * @param changesetComment + * @param imageryUrl + * @param projectId + * @param taskId + * @param url + */ + function launchCustomIdEditor(centroid, changesetComment, imageryUrl, projectId, taskId, url){ + var base = url; + var zoom = mapService.getOSMMap().getView().getZoom(); + var url = base + '#map=' + + [zoom, centroid[1], centroid[0]].join('/'); + // Add changeset comment + var changeset = ''; // default to empty string + if (changesetComment && changesetComment !== ''){ + changeset = changesetComment; + } + url += '&comment=' + encodeURIComponent(changeset); + // Add imagery + if (imageryUrl && imageryUrl !== '') { + // url is supposed to look like tms[22]:http://hiu... + var urlForImagery = imageryUrl.substring(imageryUrl.indexOf('http')); + urlForImagery = urlForImagery.replace('zoom', 'z'); + url += "&background=custom:" + encodeURIComponent(urlForImagery); + } + // Add GPX + if (projectId && projectId !== '' && taskId && taskId !== '') { + url += "&gpx=" + getGPXUrl(projectId, taskId); + } + $window.open(url); + } + + /** + * Launch the RapiD editor + * @param centroid + * @param changesetComment + * @param imageryUrl + * @param projectId + * @param taskId + */ + function launchRapidEditor(centroid, changesetComment, imageryUrl, projectId, taskId){ + var base = 'https://www.mapwith.ai/rapid?'; + var zoom = mapService.getOSMMap().getView().getZoom(); + var url = base + '#map=' + + [zoom, centroid[1], centroid[0]].join('/'); + // Add changeset comment + var changeset = ''; // default to empty string + if (changesetComment && changesetComment !== ''){ + changeset = changesetComment; + } + url += '&comment=' + encodeURIComponent(changeset); + // Add imagery + if (imageryUrl && imageryUrl !== '') { + // url is supposed to look like tms[22]:http://hiu... + var urlForImagery = imageryUrl.substring(imageryUrl.indexOf('http')); + urlForImagery = urlForImagery.replace('zoom', 'z'); + url += "&background=custom:" + encodeURIComponent(urlForImagery); + } + // Add GPX + if (projectId && projectId !== '' && taskId && taskId !== '') { + url += "&gpx=" + getGPXUrl(projectId, taskId); + } $window.open(url); } @@ -221,6 +295,27 @@ return encodeURIComponent(osmUrl); } + /** + * Returns a flat (uncategorized) array of all available iD presets in + * alphabetical order + */ + function allIdPresets() { + var presets = []; + Object.keys(idPresetCategories).forEach(function(category) { + // Some presets are duplicated between categories, so only add + // if unseen + var categoryMembers = idPresetCategories[category].members; + for (var i = 0; i < categoryMembers.length; i++) { + if (presets.indexOf(categoryMembers[i]) === -1) { + presets.push(categoryMembers[i]); + } + } + }); + + presets.sort(); + return presets; + } + /** * Format the OSM url for the project files * @param projectId diff --git a/client/app/services/project.service.js b/client/app/services/project.service.js index 76417d75e1..3d5d418bf7 100644 --- a/client/app/services/project.service.js +++ b/client/app/services/project.service.js @@ -398,12 +398,10 @@ * @returns {geojson} */ function getMapillarySequences(bbox, startDate, endDate, usernames) { - if (usernames) { - var url = configService.tmAPI + '/admin/mapillary-tasks?bbox=' + bbox + '&start_time=' + startDate + '&end_time=' + endDate + '&usernames=' + usernames - } - else { - var url = configService.tmAPI + '/admin/mapillary-tasks?bbox=' + bbox + '&start_time=' + startDate + '&end_time=' + endDate - } + var url = configService.tmAPI + '/admin/mapillary-tasks?bbox=' + bbox + '&start_time=' + startDate.toISOString() + '&end_time=' + endDate.toISOString(); + if (usernames) { + url += '&usernames=' + usernames; + } return $http({ method: 'GET', url: url diff --git a/client/app/services/user.service.js b/client/app/services/user.service.js index 5b90020fc1..84661d9d10 100644 --- a/client/app/services/user.service.js +++ b/client/app/services/user.service.js @@ -173,7 +173,6 @@ if (typeof isProjectManager === undefined){ isProjectManager = false; } - var params = ''; if (typeof projectId === "number" && !isNaN(projectId)) { params = '?projectId=' + projectId; diff --git a/client/app/taskingmanager.app.js b/client/app/taskingmanager.app.js index 766f542e8e..85585a9b00 100644 --- a/client/app/taskingmanager.app.js +++ b/client/app/taskingmanager.app.js @@ -2,7 +2,7 @@ (function () { - angular.module('taskingManager', ['ngRoute', 'ngFileUpload', 'ng-showdown', 'ui.bootstrap', 'angularMoment', 'chart.js', 'ngTagsInput', 'mentio', '720kb.socialshare', 'pascalprecht.translate', 'ngTable', 'ngClickCopy', 'taskingmanager.config']) + angular.module('taskingManager', ['ngRoute', 'ngFileUpload', 'ng-showdown', 'ui.bootstrap', 'angularMoment', 'chart.js', 'ngTagsInput', 'mentio', '720kb.socialshare', 'pascalprecht.translate', 'ngTable', 'ngClickCopy', 'taskingmanager.config', 'idpresets']) /** * Factory that returns the configuration settings for the current environment diff --git a/client/assets/styles/sass/_landingpage.scss b/client/assets/styles/sass/_landingpage.scss index 4d7aa18e3f..cf464b7300 100644 --- a/client/assets/styles/sass/_landingpage.scss +++ b/client/assets/styles/sass/_landingpage.scss @@ -29,6 +29,22 @@ margin: 0 auto; } + a { + color: #fff; + font-weight: $base-font-bold; + } + } + .mapper-encouragement__message { + padding: 0 0.5em 0.5em 0.5em; + color: #fff; + text-align: center; + font-size: 1.5em; + font-weight: $base-font-regular; + + p { + margin: 0 auto; + } + a { color: #fff; font-weight: $base-font-bold; diff --git a/client/assets/styles/sass/_project.scss b/client/assets/styles/sass/_project.scss index 04e939ee47..fb12808cf5 100644 --- a/client/assets/styles/sass/_project.scss +++ b/client/assets/styles/sass/_project.scss @@ -264,3 +264,16 @@ .email-warning{ color: $danger-color !important; } + +.edit-mapping-types { + display: flex; + justify-content: space-between; +} + +.limit-presets { + min-width: 350px; +} + +input.narrow-presets { + margin-bottom: 1em; +} diff --git a/client/gulpfile.js b/client/gulpfile.js index b5cd50c5bf..35df978722 100644 --- a/client/gulpfile.js +++ b/client/gulpfile.js @@ -5,8 +5,11 @@ var gulp = require('gulp'), cssnano = require('gulp-cssnano'), del = require('del'), eslint = require("gulp-eslint"), + fs = require('fs'), modRewrite = require('connect-modrewrite'), processhtml = require('gulp-processhtml'), + remoteSrc = require('gulp-remote-src'), + rename = require('gulp-rename'), runSequence = require('run-sequence'), sass = require('gulp-sass'), uglify = require('gulp-uglify'); @@ -132,10 +135,37 @@ gulp.task('create-release-config', function () { .pipe(gulp.dest('app')); }); +gulp.task('create-presets-config', function () { + /** + * Creates a config file for Angular containing iD editor preset categories + * from the iD preset categories file, which will be fetched directly from + * Github if no presets file exists locally + * + * There is a plan to break iD presets out into their own NPM package, at + * which time this will no longer be necessary + */ + var presetData = fs.existsSync('./id_preset_categories.json') ? + gulp.src('id_preset_categories.json') : + fetchIdPresets() + + return presetData + .pipe(config('idpresets')) + .pipe(gulp.dest('app')) +}); + +gulp.task('fetch-id-presets', function () { + /** + * Explicitly fetch presets file from Github, intended to be run manually on + * demand + */ + return fetchIdPresets(); +}); + /** Build task for will minify the app and copy it to the dist folder ready to deploy */ gulp.task('build', function (callback) { runSequence('clean', 'create-release-config', + 'create-presets-config', 'compile-sass', 'copy_images_to_dist', 'copy_icons_to_dist', @@ -150,6 +180,7 @@ gulp.task('build', function (callback) { gulp.task('run', function (callback) { runSequence('create-dev-config', + 'create-presets-config', 'compile-sass', 'browser-sync', 'sass:watch', @@ -157,3 +188,15 @@ gulp.task('run', function (callback) { ) ; }); + +function fetchIdPresets() { + /** + * Fetches the iD preset categories from github and saves them as + * ./id_preset_categories.json + */ + return remoteSrc('presets/categories.json', { + base: 'https://raw.githubusercontent.com/openstreetmap/iD/v2.15.3/data/' + }) + .pipe(rename('id_preset_categories.json')) + .pipe(gulp.dest('./')); +} diff --git a/client/index.html b/client/index.html index 97bd8c9e83..76f32159de 100644 --- a/client/index.html +++ b/client/index.html @@ -226,6 +226,7 @@

    + diff --git a/client/locale/en.json b/client/locale/en.json index 80f2708ba3..588368b70d 100644 --- a/client/locale/en.json +++ b/client/locale/en.json @@ -1,19 +1,23 @@ { "": "", + " Bad Imagery": " Bad Imagery", " If checked, only users with the experience level of Intermediate and Advanced will be able to validate tasks in this project.": " If checked, only users with the experience level of Intermediate and Advanced will be able to validate tasks in this project.", " Mark as Bad Imagery": " Mark as Bad Imagery", " Mark as Completely Mapped": " Mark as Completely Mapped", " Split Task": " Split Task", " Stop Mapping": " Stop Mapping", + " Tasks Validated": " Tasks Validated", + " Total Tasks": " Total Tasks", " for an unknown reason.": " for an unknown reason.", " left": " left", " or join the discussion with your ": " or join the discussion with your ", " successfully.": " successfully.", "% Mapped": "% Mapped", "% Validated": "% Validated", + "% of Mapped": "% of Mapped", + "% of Validated": "% of Validated", "(What’s new for mappers), ": "(What’s new for mappers), ", "(What’s new for validators).": "(What’s new for validators).", - ", the free and editable map of the world. Communities, organizations and governments worldwide have access to these maps to address local development challenges and aid disaster response. You can join thousands of others to map": ", the free and editable map of the world. Communities, organizations and governments worldwide have access to these maps to address local development challenges and aid disaster response. You can join thousands of others to map", "A comment entered when you save your mapping to the OSM database. When saving your work, please leave the default comment but add what you actually mapped, for example: Added buildings and a residential road.": "A comment entered when you save your mapping to the OSM database. When saving your work, please leave the default comment but add what you actually mapped, for example: Added buildings and a residential road.", "A description for the default locale": "A description for the default locale", "A mapping type selection is missing.": "A mapping type selection is missing.", @@ -34,6 +38,7 @@ "Activity and Stats": "Activity and Stats", "Add OSM XML files to load into JOSM. Each file should encompass the entire project area. An extract of the data using the task\'s extent will be sent to JOSM for editing.": "Add OSM XML files to load into JOSM. Each file should encompass the entire project area. An extract of the data using the task\'s extent will be sent to JOSM for editing.", "Add layer": "Add layer", + "Add or edit a custom editor": "Add or edit a custom editor", "Add user": "Add user", "Add/edit contact details": "Add/edit contact details", "Admin": "Admin", @@ -74,6 +79,7 @@ "Automatically unlocked for validation": "Automatically unlocked for validation", "Back to Previous": "Back to Previous", "Bad imagery": "Bad imagery", + "Before you begin mapping:": "Before you begin mapping:", "Beginner": "Beginner", "Beginner mapper": "Beginner mapper", "Block user": "Block user", @@ -83,8 +89,10 @@ "Campaign": "Campaign", "Campaign Tag": "Campaign Tag", "Cancel": "Cancel", + "Causes You Can Help": "Causes You Can Help", "Changeset Comment": "Changeset Comment", "Changeset comment": "Changeset comment", + "Choose 1 or more presets or disable preset limiting": "Choose 1 or more presets or disable preset limiting", "Choose a task": "Choose a task", "Choose a task for mapping:": "Choose a task for mapping:", "Choose two entries for diff or same once twice for attic data": "Choose two entries for diff or same once twice for attic data", @@ -115,10 +123,12 @@ "Created": "Created", "Created by:": "Created by:", "Creation in progress...": "Creation in progress...", + "Custom Editor": "Custom Editor", "Default comments added to uploaded changeset comment field. Users should also be encouraged to add text describing what they mapped. Example: #hotosm-project-470 #missingmaps Buildings mapping. Hashtags are sometimes used for analysis later, but should be human informative and not overused, #group #event for example.": "Default comments added to uploaded changeset comment field. Users should also be encouraged to add text describing what they mapped. Example: #hotosm-project-470 #missingmaps Buildings mapping. Hashtags are sometimes used for analysis later, but should be human informative and not overused, #group #event for example.", "Default locale": "Default locale", "Delete": "Delete", "Delete All Selected Messages": "Delete All Selected Messages", + "Delete Custom Editor": "Delete Custom Editor", "Delete File": "Delete File", "Delete Selected": "Delete Selected", "Delete message": "Delete message", @@ -157,6 +167,7 @@ "Editors for validation": "Editors for validation", "Email (only visible to you):": "Email (only visible to you):", "Email verification": "Email verification", + "Enabled": "Enabled", "End Date": "End Date", "Enforce Random Task Selection": "Enforce Random Task Selection", "Enforce random task selection on mapping": "Enforce random task selection on mapping", @@ -192,6 +203,9 @@ "Field Papers": "Field Papers", "Filter": "Filter", "Filter projects (on name, campaign, ...)": "Filter projects (on name, campaign, ...)", + "Find Ayeyarwady Delta Projects": "Find Ayeyarwady Delta Projects", + "Find New Disaster Mapping Projects": "Find New Disaster Mapping Projects", + "Find Tanzania Development Trust Projects": "Find Tanzania Development Trust Projects", "Find a task for validation through one of the options below.": "Find a task for validation through one of the options below.", "Fixed Mapping Issues": "Fixed Mapping Issues", "For help with mapping or mapping related questions, please email: ": "For help with mapping or mapping related questions, please email: ", @@ -251,12 +265,13 @@ "Invalidating...": "Invalidating...", "It is recommended to keep the size of the tasks small. Please review the size of the tasks and/or consider creating multiple projects.": "It is recommended to keep the size of the tasks small. Please review the size of the tasks and/or consider creating multiple projects.", "JOSM": "JOSM", + "JOSM (MapWithAI)": "JOSM (MapWithAI)", "JOSM Settings": "JOSM Settings", "JOSM had a problem loading the project imagery.": "JOSM had a problem loading the project imagery.", "JOSM remote control did not respond. Do you have JOSM running and configured to be controlled remotely?": "JOSM remote control did not respond. Do you have JOSM running and configured to be controlled remotely?", - "Join coordinated humanitarian mapping projects by taking a task and mapping a part of the world in": "Join coordinated humanitarian mapping projects by taking a task and mapping a part of the world in", + "Janet Chapman, Tanzania Development Trust": "Janet Chapman, Tanzania Development Trust", + "Join coordinated Kaart mapping projects by taking a task and mapping a part of the world in '}} OpenStreetMap{{', the free and editable map of the world. Communities, organizations and governments across the world have access to these maps to address local development challenges. You can join thousands of others to map '}}OpenStreetMap {{'and support these communities in need.": "Join coordinated Kaart mapping projects by taking a task and mapping a part of the world in '}} OpenStreetMap{{', the free and editable map of the world. Communities, organizations and governments across the world have access to these maps to address local development challenges. You can join thousands of others to map '}}OpenStreetMap {{'and support these communities in need.", "Joined OSM": "Joined OSM", - "Kaart Team": "Kaart Team", "Keep up the validation! We need experts like you to help!": "Keep up the validation! We need experts like you to help!", "Label mapping issues (optional)": "Label mapping issues (optional)", "Land use": "Land use", @@ -275,6 +290,7 @@ "Legend": "Legend", "Level": "Level", "Licenses": "Licenses", + "Limit iD Editor Presets": "Limit iD Editor Presets", "Load GPX": "Load GPX", "Load into JOSM": "Load into JOSM", "Loading...": "Loading...", @@ -319,6 +335,7 @@ "Mark as Valid": "Mark as Valid", "Marked as bad imagery": "Marked as bad imagery", "Marked as ready": "Marked as ready", + "Matt Gibb, Red Cross": "Matt Gibb, Red Cross", "Maximum number of tasks reached. If smaller tasks are required please make the Area of Interest smaller.": "Maximum number of tasks reached. If smaller tasks are required please make the Area of Interest smaller.", "Medium": "Medium", "Message": "Message", @@ -329,6 +346,7 @@ "Metadata and tags are used to allow users to find projects to work on and group projects.": "Metadata and tags are used to allow users to find projects to work on and group projects.", "Missing Maps Badges": "Missing Maps Badges", "N/A": "N/A", + "NOTE: This percentage may be greater than 100 if the tasks aren\'t clipped to the project AOI": "NOTE: This percentage may be greater than 100 if the tasks aren\'t clipped to the project AOI", "Name": "Name", "Name of the project": "Name of the project", "Navigate the results": "Navigate the results", @@ -353,6 +371,7 @@ "No project discussion yet": "No project discussion yet", "No projects found.": "No projects found.", "Not a valid email address": "Not a valid email address", + "Note": "Note", "Note: follow this format for TMS URLs:": "Note: follow this format for TMS URLs:", "Noted Mapping Issues": "Noted Mapping Issues", "Nothing has happened yet.": "Nothing has happened yet.", @@ -367,7 +386,9 @@ "OSM heat map": "OSM heat map", "OSM profile": "OSM profile", "OSMCha Filter Id": "OSMCha Filter Id", + "Offer Presets:": "Offer Presets:", "Older": "Older", + "Ongoing Disaster Mapping": "Ongoing Disaster Mapping", "Only mappers with experience level": "Only mappers with experience level", "Only one campaign tag is allowed.": "Only one campaign tag is allowed.", "Only one organisation tag is allowed.": "Only one organisation tag is allowed.", @@ -399,7 +420,6 @@ "Please get started by creating a new OpenStreetMap account.": "Please get started by creating a new OpenStreetMap account.", "Please go to your profile and add your email address": "Please go to your profile and add your email address", "Please login first.": "Please login first.", - "Please login to start validating.": "Please login to start validating.", "Please make sure you only have one campaign tag.": "Please make sure you only have one campaign tag.", "Please make sure you only have one organisation tag.": "Please make sure you only have one organisation tag.", "Please make sure you only have one user.": "Please make sure you only have one user.", @@ -432,12 +452,14 @@ "Published": "Published", "Put here anything that can be useful to users while taking a task. If you have added extra properties within the GeoJSON of the task, they can be referenced by surrounding them in curly braces. For eg. if you have a property called `import_url` in your GeoJSON, you can reference it like:": "Put here anything that can be useful to users while taking a task. If you have added extra properties within the GeoJSON of the task, they can be referenced by surrounding them in curly braces. For eg. if you have a property called `import_url` in your GeoJSON, you can reference it like:", "Questions and Comments": "Questions and Comments", - "RapiD": "RapiD", + "RapiD Editor": "RapiD Editor", "Read more on the": "Read more on the", "Ready": "Ready", "Ready to get mapping?": "Ready to get mapping?", "Receive automated validation messages": "Receive automated validation messages", + "Red Cross - Ayeyarwady Delta": "Red Cross - Ayeyarwady Delta", "Registered": "Registered", + "Remove Custom Editor": "Remove Custom Editor", "Report an issue (space to see options)": "Report an issue (space to see options)", "Require experience level INTERMEDIATE or ADVANCED": "Require experience level INTERMEDIATE or ADVANCED", "Require users to be validators": "Require users to be validators", @@ -462,6 +484,7 @@ "Road mapping is the next most common type of mapping for HOT and MissingMaps does. Physically mapping roads is very easy, but deciding how to classifiy them is the real challenge. This video will give you basics of how to map roads using the iD editor.": "Road mapping is the next most common type of mapping for HOT and MissingMaps does. Physically mapping roads is very easy, but deciding how to classifiy them is the real challenge. This video will give you basics of how to map roads using the iD editor.", "Roads": "Roads", "Role": "Role", + "Russell Deffner, Humanitarian OpenStreetMap Team": "Russell Deffner, Humanitarian OpenStreetMap Team", "Save": "Save", "Save changes": "Save changes", "Save edits": "Save edits", @@ -470,6 +493,7 @@ "Search for username": "Search for username", "Search project description or project number": "Search project description or project number", "Seasoned mapper? Pick up one of our many projects and start contributing.": "Seasoned mapper? Pick up one of our many projects and start contributing.", + "See here": "See here", "See the changesets in OSMCha for this area.": "See the changesets in OSMCha for this area.", "See the changesets in OSMCha for this project": "See the changesets in OSMCha for this project", "See the changesets in Overpass Turbo for this area.": "See the changesets in Overpass Turbo for this area.", @@ -478,6 +502,7 @@ "Select a task by clicking on the map": "Select a task by clicking on the map", "Select another task": "Select another task", "Select area by drawing a polygon": "Select area by drawing a polygon", + "Select at least one iD preset or disable iD preset limiting.": "Select at least one iD preset or disable iD preset limiting.", "Select by user below": "Select by user below", "Sending...": "Sending...", "Set mapper level": "Set mapper level", @@ -521,6 +546,8 @@ "Stop using JOSM": "Stop using JOSM", "Subject": "Subject", "TM3 introduces an enhanced backend which supports many new features, but doesn’t allow to run TM2 from the same database. Therefore the switch from TM2 to TM3 will involve a “hard cutover”, meaning that once TM3 is activated at tasks.hotosm.org, the TM2 platform will be turned off and become inaccessible. All projects from TM2 will automatically be ported to TM3 and mappers will be able to continue mapping in TM3 from where they left in TM2.": "TM3 introduces an enhanced backend which supports many new features, but doesn’t allow to run TM2 from the same database. Therefore the switch from TM2 to TM3 will involve a “hard cutover”, meaning that once TM3 is activated at tasks.hotosm.org, the TM2 platform will be turned off and become inaccessible. All projects from TM2 will automatically be ported to TM3 and mappers will be able to continue mapping in TM3 from where they left in TM2.", + "Tanzania Development Trust": "Tanzania Development Trust", + "Tanzania has a high incidence of extreme poverty, early marriage and Gender Based Violence. NGOs on the ground need better road and residential area data to facilitate their outreach work. Community mappers from a HOT microgrant, together with YouthMappers from the Institute of Rural Development Planning will map employment opportunities as part of a project to reduce youth vulnerability starting on 26th August. This base map will assist with this field mapping.": "Tanzania has a high incidence of extreme poverty, early marriage and Gender Based Violence. NGOs on the ground need better road and residential area data to facilitate their outreach work. Community mappers from a HOT microgrant, together with YouthMappers from the Institute of Rural Development Planning will map employment opportunities as part of a project to reduce youth vulnerability starting on 26th August. This base map will assist with this field mapping.", "Task": "Task", "Task Link": "Task Link", "Task automatically unlocked for mapping": "Task automatically unlocked for mapping", @@ -545,18 +572,21 @@ "Tasks as": "Tasks as", "Tasks must be randomly selected": "Tasks must be randomly selected", "Thank you for verifying your email.": "Thank you for verifying your email.", + "Thanks for providing your contact info. We’ve sent you a confirmation email that you can check at a later time. Now you can get started mapping! If you’ve kept a task open, make sure to refresh the page.": "Thanks for providing your contact info. We’ve sent you a confirmation email that you can check at a later time. Now you can get started mapping! If you’ve kept a task open, make sure to refresh the page.", "The AOI contains geometries which are not polygons or multipolygons": "The AOI contains geometries which are not polygons or multipolygons", "The AOI contains self intersections": "The AOI contains self intersections", "The Area of Interest contains self intersections.": "The Area of Interest contains self intersections.", "The Project could not be created. This may be because the combined data size of the AOI and the grid is too large to send to the server. Try again with a smaller AOI or fewer tasks.": "The Project could not be created. This may be because the combined data size of the AOI and the grid is too large to send to the server. Try again with a smaller AOI or fewer tasks.", "The Project could not be trimmed. This may be because the combined data size of the AOI and the grid is too large to send to the server. Try again with a smaller AOI or fewer tasks.": "The Project could not be trimmed. This may be because the combined data size of the AOI and the grid is too large to send to the server. Try again with a smaller AOI or fewer tasks.", "The Project trim timed out. Try again with a smaller AOI or fewer tasks.": "The Project trim timed out. Try again with a smaller AOI or fewer tasks.", + "The Red Cross is mapping the Ayeyarwady Delta area in Myanmar as part of a multi-year mapping and data readiness activity to better understand where critical infrastructure and roads are to inform decision making during potential disasters. As recently as 2008 a cyclone killed at least 77,000 people with over 55,900 missing, and left about 2.5 million homeless.": "The Red Cross is mapping the Ayeyarwady Delta area in Myanmar as part of a multi-year mapping and data readiness activity to better understand where critical infrastructure and roads are to inform decision making during potential disasters. As recently as 2008 a cyclone killed at least 77,000 people with over 55,900 missing, and left about 2.5 million homeless.", "The Tasking Manager is Open Source software. Please feel free to report issues and contribute.": "The Tasking Manager is Open Source software. Please feel free to report issues and contribute.", "The Tasking Manager is a mapping tool designed and built for the Humanitarian OpenStreetMap Team's collaborative mapping process in OpenStreetMap. The purpose of the tool is to divide up a mapping project into smaller tasks that can be completed rapidly with many people working on the same overall area. It shows which areas need to be mapped and which areas need the mapping validated.": "The Tasking Manager is a mapping tool designed and built for the Humanitarian OpenStreetMap Team's collaborative mapping process in OpenStreetMap. The purpose of the tool is to divide up a mapping project into smaller tasks that can be completed rapidly with many people working on the same overall area. It shows which areas need to be mapped and which areas need the mapping validated.", "The Tasking Manager is the tool / website you are using right now. Its main purpose is to divide up large areas that need mapping into small, easy to map Tasks. It organizes the small Tasks into a larger group called a Project. It provides a user interface to find a Project and then Check-Out and Check-In the small Tasks so you can map them in OpenStreetMap using one of the standard OpenStreetMap editors. Learning how to actually map is shown in the videos below.": "The Tasking Manager is the tool / website you are using right now. Its main purpose is to divide up large areas that need mapping into small, easy to map Tasks. It organizes the small Tasks into a larger group called a Project. It provides a user interface to find a Project and then Check-Out and Check-In the small Tasks so you can map them in OpenStreetMap using one of the standard OpenStreetMap editors. Learning how to actually map is shown in the videos below.", "The Tasking Manager was designed and built for the": "The Tasking Manager was designed and built for the", "The application code is available on GitHub.": "The application code is available on GitHub.", "The code and community around Tasking Manager v2 will still exist. If you have questions about the older version, reach out via the ": "The code and community around Tasking Manager v2 will still exist. If you have questions about the older version, reach out via the ", + "The description will appear when a user selects the editor from the editor dropdown.": "The description will appear when a user selects the editor from the editor dropdown.", "The following files can be loaded into JOSM as separate layers within the task boundary:": "The following files can be loaded into JOSM as separate layers within the task boundary:", "The following files can be loaded into JOSM as separate layers within the tasks\' boundaries:": "The following files can be loaded into JOSM as separate layers within the tasks\' boundaries:", "The following files will be loaded into JOSM as separate layers:": "The following files will be loaded into JOSM as separate layers:", @@ -598,11 +628,13 @@ "This will mark all tasks (except bad imagery tasks) as mapped. Please use this only if you are sure of what you are doing.": "This will mark all tasks (except bad imagery tasks) as mapped. Please use this only if you are sure of what you are doing.", "This will mark all tasks (except bad imagery) as valid. Please use this only if you are sure of what you are doing.": "This will mark all tasks (except bad imagery) as valid. Please use this only if you are sure of what you are doing.", "This will mark all tasks (except non completed and bad imagery tasks) as invalid. Please use this only if you are sure of what you are doing.": "This will mark all tasks (except non completed and bad imagery tasks) as invalid. Please use this only if you are sure of what you are doing.", + "This will remove the custom editor from the project. Are you sure you don\'t want to disable the custom editor by toggling the "Enabled" checkbox above?": "This will remove the custom editor from the project. Are you sure you don\'t want to disable the custom editor by toggling the "Enabled" checkbox above?", "This will reset all tasks to ready to map (preserving existing history). Please use this only if you are sure of what you are doing.": "This will reset all tasks to ready to map (preserving existing history). Please use this only if you are sure of what you are doing.", "This will send a Tasking Manager message to every contributor of the current project. Please use this feature carefully.": "This will send a Tasking Manager message to every contributor of the current project. Please use this feature carefully.", "This will upload an OSM File that will be used to send to JOSM. The OSM data sent to JOSM will be within the tasks\'s area.": "This will upload an OSM File that will be used to send to JOSM. The OSM data sent to JOSM will be within the tasks\'s area.", "Time Spent Mapping (on this instance)": "Time Spent Mapping (on this instance)", "Tip": "Tip", + "Tip: choose Types of Mapping to view preset options": "Tip: choose Types of Mapping to view preset options", "Toggle the Area of Interest of existing projects": "Toggle the Area of Interest of existing projects", "Total Mappers": "Total Mappers", "Total OSM changesets": "Total OSM changesets", @@ -615,6 +647,7 @@ "Trimming in progress": "Trimming in progress", "Type a message": "Type a message", "Type a username": "Type a username", + "Type to narrow presets": "Type to narrow presets", "Type(s) of mapping": "Type(s) of mapping", "URL to service": "URL to service", "Un-Mark": "Un-Mark", @@ -666,14 +699,14 @@ "When a task is opened, a file obtained from the Overpass API using this query will be loaded into JOSM. ": "When a task is opened, a file obtained from the Overpass API using this query will be loaded into JOSM. ", "When should project manager stop creating new projects in TM2?": "When should project manager stop creating new projects in TM2?", "When will TM3 be launched?": "When will TM3 be launched?", - "When you contribute on the Tasking Manager, it is important that notifications around the tasks and projects you participated are reaching you. Thus we require your email address.": "When you contribute on the Tasking Manager, it is important that notifications around the tasks and projects you participated are reaching you. Thus we require your email address.", + "When you contribute to the Tasking Manager, it is important that notifications about the tasks and projects you contributed to are reaching you.": "When you contribute to the Tasking Manager, it is important that notifications about the tasks and projects you contributed to are reaching you.", "Where can I learn more about how TM3 works?": "Where can I learn more about how TM3 works?", "Will there be any downtime during the transition?": "Will there be any downtime during the transition?", + "With our disaster mapping program, what we are trying to provide is a common operating picture for the first responders, aid organizations, governments and the affected people to make informed decisions. When all parties are working with the same information, the better situated the community is for quick and effective recovery.": "With our disaster mapping program, what we are trying to provide is a common operating picture for the first responders, aid organizations, governments and the affected people to make informed decisions. When all parties are working with the same information, the better situated the community is for quick and effective recovery.", "With the invaluable support of:": "With the invaluable support of:", "Yes": "Yes", "Yes, all projects created in TM2 will automatically be transferred to TM3, including all data and current progress on each task.": "Yes, all projects created in TM2 will automatically be transferred to TM3, including all data and current progress on each task.", "Yes, there might be up to one hour when both TM2 and TM3 will not be accessible. This will happen during late afternoon Pacific time / night European time, hoping to minimize any potential disruption.": "Yes, there might be up to one hour when both TM2 and TM3 will not be accessible. This will happen during late afternoon Pacific time / night European time, hoping to minimize any potential disruption.", - "You can inform yourself on our website about": "You can inform yourself on our website about", "You can not comment": "You can not comment", "You can only delete projects with no contributions.": "You can only delete projects with no contributions.", "You can report any issue at": "You can report any issue at", @@ -700,7 +733,6 @@ "Your profile": "Your profile", "Your project has been updated successfully.": "Your project has been updated successfully.", "Your project was created successfully.": "Your project was created successfully.", - "and support these communities in need.": "and support these communities in need.", "are locked for validation.": "are locked for validation.", "are missing.": "are missing.", "characters remaining": "characters remaining", @@ -709,9 +741,10 @@ "choose locale": "choose locale", "choose mapping level": "choose mapping level", "choose results view": "choose results view", + "for more information about how HOT will use and share your data.": "for more information about how HOT will use and share your data.", "get started with the README": "get started with the README", - "how we handle your data": "how we handle your data", "iD Editor": "iD Editor", + "iD Editor Presets *": "iD Editor Presets *", "is missing.": "is missing.", "learn how to validate!": "learn how to validate!", "local OSM community": "local OSM community", diff --git a/client/package.json b/client/package.json index 461727e55f..c396a66f70 100644 --- a/client/package.json +++ b/client/package.json @@ -32,16 +32,18 @@ "gulp-eslint": "4.0.2", "gulp-ng-config": "^1.4.0", "gulp-processhtml": "^1.1.0", + "gulp-remote-src": "^0.4.4", + "gulp-rename": "^1.4.0", "gulp-sass": "^3.1.0", "gulp-uglify": "^2.0.1", "hot-design-system": "git+https://github.com/hotosm/hot-design-system.git", "jasmine-core": "~2.5.2", "jeet": "^7.1.0", - "karma": "^1.5.0", - "karma-chrome-launcher": "^2.0.0", + "karma": "^4.4.1", + "karma-chrome-launcher": ">=3.1.0", + "karma-firefox-launcher": ">=1.3.0", "karma-jasmine": "^1.1.0", "karma-junit-reporter": "^1.2.0", - "karma-phantomjs-launcher": "^1.0.2", "@mapbox/geo-viewport": "^0.4.0", "ment.io": "^0.9.23", "moment": "^2.24.0", @@ -54,7 +56,7 @@ "ol3-layerswitcher": "^1.1.0", "openlayers": "^4.0.1", "osmtogeojson": "^2.2.12", - "phantomjs-prebuilt": "^2.1.14", + "phantomjs-prebuilt": "^2.1.16", "react": "^15.4.2", "react-addons-css-transition-group": "^15.4.2", "react-dom": "^15.4.2", @@ -63,5 +65,6 @@ "showdown": "^1.9.0", "@turf/turf": "5.x", "ng-table": "^3.0.1" - } + }, + "dependencies": {} } diff --git a/client/taskingmanager.config.json b/client/taskingmanager.config.json index fa7c38a130..f84023407b 100644 --- a/client/taskingmanager.config.json +++ b/client/taskingmanager.config.json @@ -29,6 +29,7 @@ "atticQueryOffsetMinutes": 10, "maxCommentLength": 5000, "maxChatLength": 5000, + "idPresetsLimitedByDefault": false, "mapillaryAPI": { "base": "https://a.mapillary.com/v3/", "clientId": "LVZRT2ZMZkl5RFpGZFp3NzZKaGhaQTpmMGVmNDU1NDI0NmI2YjNm", @@ -66,7 +67,8 @@ ], "atticQueryOffsetMinutes": 10, "maxCommentLength": 5000, - "maxChatLength": 5000 + "maxChatLength": 5000, + "idPresetsLimitedByDefault": false } } } \ No newline at end of file diff --git a/devops/cloudformation/tasking-manager.template.js b/devops/cloudformation/tasking-manager.template.js index dec0d183d7..021b82fa45 100644 --- a/devops/cloudformation/tasking-manager.template.js +++ b/devops/cloudformation/tasking-manager.template.js @@ -163,7 +163,7 @@ const Resources = { 'dpkg-reconfigure --frontend=noninteractive locales', 'sudo apt-get -y update', 'sudo DEBIAN_FRONTEND=noninteractive apt-get -y -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" dist-upgrade', - 'sudo add-apt-repository ppa:jonathonf/python-3.6 -y', + 'sudo add-apt-repository ppa:deadsnakes/ppa -y', 'sudo apt-get update', 'sudo apt-get -y install python3.6', 'sudo apt-get -y install python3.6-dev', @@ -425,7 +425,7 @@ const Resources = { Properties: { Engine: 'postgres', DBName: cf.if('UseASnapshot', cf.noValue, cf.ref('PostgresDB')), - EngineVersion: '11.2', + EngineVersion: '11.5', MasterUsername: cf.if('UseASnapshot', cf.noValue, cf.ref('PostgresUser')), MasterUserPassword: cf.if('UseASnapshot', cf.noValue, cf.ref('PostgresPassword')), AllocatedStorage: cf.ref('DatabaseSize'), @@ -433,7 +433,7 @@ const Resources = { StorageType: 'gp2', DBParameterGroupName: 'tm3-logging-postgres11', EnableCloudwatchLogsExports: ['postgresql'], - DBInstanceClass: cf.if('IsTaskingManagerProduction', 'db.m5.xlarge', 'db.t2.small'), + DBInstanceClass: cf.if('IsTaskingManagerProduction', 'db.t3.2xlarge', 'db.t2.small'), DBSnapshotIdentifier: cf.if('UseASnapshot', cf.ref('DBSnapshot'), cf.noValue), VPCSecurityGroups: [cf.importValue(cf.join('-', ['hotosm-network-production', cf.ref('Environment'), 'ec2s-security-group', cf.region]))], } diff --git a/devops/database/duplicate-priority-area-cleanup.sql b/devops/database/duplicate-priority-area-cleanup.sql new file mode 100644 index 0000000000..2fd065957c --- /dev/null +++ b/devops/database/duplicate-priority-area-cleanup.sql @@ -0,0 +1,41 @@ +-- Filter out projects with duplicate priorities +-- Stores results in a temporary table `priority_duplicates` +-- X (lng), Y (lat) +DROP TABLE IF EXISTS priority_duplicates; +SELECT + project_id, priority_area_id, geometry, count(*) + INTO TEMPORARY TABLE + priority_duplicates + FROM project_priority_areas, priority_areas + WHERE priority_areas.id = project_priority_areas.priority_area_id + GROUP BY project_id, priority_area_id, geometry + HAVING count(*) > 1 + ORDER BY project_id DESC; + + + + +-- Function to iterate over duplicate priority areas and delete table entries +-- Affected tables: +-- priority_areas, project_priority_areas +CREATE OR REPLACE FUNCTION delete_duplicate_priority_geom() + RETURNS SETOF text AS +$func$ +DECLARE + proj int; + priority int; + bounds geometry; +BEGIN + FOR proj, priority, bounds IN + SELECT project_id, priority_area_id, geometry FROM priority_duplicates + LOOP + DELETE FROM public.project_priority_areas WHERE priority_area_id = priority AND project_id = proj; + DELETE FROM public.priority_areas WHERE id = priority ; + INSERT INTO public.priority_areas (id, geometry) VALUES (priority, bounds); + INSERT INTO public.project_priority_areas (project_id, priority_area_id) VALUES (proj, priority); + RETURN NEXT proj; + END LOOP; +END + $func$ LANGUAGE plpgsql; + +SELECT * FROM delete_duplicate_priority_geom(); diff --git a/dump.osm b/dump.osm deleted file mode 100644 index 33be223195..0000000000 --- a/dump.osm +++ /dev/null @@ -1,2934 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/manage.py b/manage.py index fd8a3395a4..6a93ad343f 100644 --- a/manage.py +++ b/manage.py @@ -8,22 +8,36 @@ from server.services.users.authentication_service import AuthenticationService from server.services.users.user_service import UserService from server.services.translation_service import TranslationService +from server.services.stats_service import StatsService import os import warnings # Load configuration from file -load_dotenv(os.path.join(os.path.dirname(__file__), 'tasking-manager.env')) +load_dotenv(os.path.join(os.path.dirname(__file__), "tasking-manager.env")) # Temporarily here - to support backwards compatibility with TM_DB key. -if os.getenv('TM_DB', False): - for key in ['TM_APP_BASE_URL', 'TM_SECRET', 'TM_CONSUMER_KEY', 'TM_CONSUMER_SECRET']: +if os.getenv("TM_DB", False): + for key in [ + "TM_APP_BASE_URL", + "TM_SECRET", + "TM_CONSUMER_KEY", + "TM_CONSUMER_SECRET", + ]: if not os.getenv(key): warnings.warn("%s environmental variable not set." % (key,)) else: # Check that required environmental variables are set - for key in ['TM_APP_BASE_URL', 'POSTGRES_DB', 'POSTGRES_USER', 'POSTGRES_PASSWORD', 'TM_SECRET', - 'TM_CONSUMER_KEY', 'TM_CONSUMER_SECRET', 'TM_DEFAULT_CHANGESET_COMMENT']: + for key in [ + "TM_APP_BASE_URL", + "POSTGRES_DB", + "POSTGRES_USER", + "POSTGRES_PASSWORD", + "TM_SECRET", + "TM_CONSUMER_KEY", + "TM_CONSUMER_SECRET", + "TM_DEFAULT_CHANGESET_COMMENT", + ]: if not os.getenv(key): warnings.warn("%s environmental variable not set." % (key,)) @@ -32,28 +46,41 @@ try: init_counters(application) except Exception: - warnings.warn('Homepage counters not initialized.') + warnings.warn("Homepage counters not initialized.") manager = Manager(application) # Enable db migrations to be run via the command line -manager.add_command('db', MigrateCommand) +manager.add_command("db", MigrateCommand) -@manager.option('-u', '--user_id', help='Test User ID') +@manager.option("-u", "--user_id", help="Test User ID") def gen_token(user_id): """ Helper method for generating valid base64 encoded session tokens """ token = AuthenticationService.generate_session_token_for_user(user_id) - print(f'Raw token is: {token}') + print(f"Raw token is: {token}") b64_token = base64.b64encode(token.encode()) - print(f'Your base64 encoded session token: {b64_token}') + print(f"Your base64 encoded session token: {b64_token}") @manager.command def refresh_levels(): - print('Started updating mapper levels...') + print("Started updating mapper levels...") users_updated = UserService.refresh_mapper_level() - print(f'Updated {users_updated} user mapper levels') + print(f"Updated {users_updated} user mapper levels") + + +@manager.command +def refresh_translatables(): + print("Exporting translatable strings") + TranslationService.refresh_translatables() + + +@manager.command +def refresh_project_stats(): + print("Started updating project stats...") + StatsService.update_all_project_stats() + print("Project stats updated") @manager.command diff --git a/migrations/versions/0eeaa5aed53b_.py b/migrations/versions/0eeaa5aed53b_.py index f0313c4e78..77d1b9c5dc 100644 --- a/migrations/versions/0eeaa5aed53b_.py +++ b/migrations/versions/0eeaa5aed53b_.py @@ -10,19 +10,27 @@ # revision identifiers, used by Alembic. -revision = '0eeaa5aed53b' -down_revision = 'e36f1d0c4947' +revision = "0eeaa5aed53b" +down_revision = "e36f1d0c4947" branch_labels = None depends_on = None def upgrade(): # ### commands auto generated by Alembic - please adjust! ### - op.add_column('projects', sa.Column('enforce_random_task_selection', sa.Boolean(), nullable=True, server_default='false')) + op.add_column( + "projects", + sa.Column( + "enforce_random_task_selection", + sa.Boolean(), + nullable=True, + server_default="FALSE" + ) + ) # ### end Alembic commands ### def downgrade(): # ### commands auto generated by Alembic - please adjust! ### - op.drop_column('projects', 'enforce_random_task_selection') + op.drop_column("projects", "enforce_random_task_selection") # ### end Alembic commands ### diff --git a/migrations/versions/4b9fb4d3f349_.py b/migrations/versions/4b9fb4d3f349_.py new file mode 100644 index 0000000000..fd8b90ccd9 --- /dev/null +++ b/migrations/versions/4b9fb4d3f349_.py @@ -0,0 +1,28 @@ +"""empty message + +Revision ID: 4b9fb4d3f349 +Revises: 451f6bd05a19 +Create Date: 2019-07-19 13:38:08.010157 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = '4b9fb4d3f349' +down_revision = '451f6bd05a19' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('projects', sa.Column('id_presets', postgresql.ARRAY(sa.String()), nullable=True)) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('projects', 'id_presets') + # ### end Alembic commands ### diff --git a/migrations/versions/51ccc46f1b8a_.py b/migrations/versions/51ccc46f1b8a_.py new file mode 100644 index 0000000000..19d2a697d7 --- /dev/null +++ b/migrations/versions/51ccc46f1b8a_.py @@ -0,0 +1,51 @@ +"""empty message + +Revision ID: 51ccc46f1b8a +Revises: 4b9fb4d3f349 +Create Date: 2019-10-16 19:30:51.064696 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '51ccc46f1b8a' +down_revision = '4b9fb4d3f349' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('project_custom_editors', + sa.Column('project_id', sa.Integer(), nullable=False), + sa.Column('name', sa.String(length=50), nullable=False), + sa.Column('description', sa.String(), nullable=True), + sa.Column('url', sa.String(), nullable=False), + sa.Column('enabled', sa.Boolean(), nullable=False), + sa.ForeignKeyConstraint(['project_id'], ['projects.id'], ), + sa.PrimaryKeyConstraint('project_id') + ) + + op.alter_column( + "projects", + "mapping_editors", + type_=sa.ARRAY(sa.Integer), + postgresql_using="array_replace(validation_editors, 4, 10005);" + ) + + op.alter_column( + "projects", + "validation_editors", + type_=sa.ARRAY(sa.Integer), + postgresql_using="array_replace(validation_editors, 4, 10005);" + ) + + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('project_custom_editors') + # ### end Alembic commands ### diff --git a/requirements.txt b/requirements.txt index fe44f2ba98..44f0a61394 100644 --- a/requirements.txt +++ b/requirements.txt @@ -16,6 +16,8 @@ Flask-RESTful==0.3.6 Flask-Script==2.0.5 Flask-SQLAlchemy==2.2 flask-swagger==0.2.14 +# pip install gdal==$(gdal-config --version) # GDAL MUST match the installed GDAL version +# GDAL==2.1.0 GeoAlchemy2==0.4.0 geojson==1.3.4 gevent==1.4.0 @@ -32,7 +34,7 @@ mccabe==0.6.1 newrelic==3.2.0.91 nose==1.3.7 oauthlib==2.0.2 -psycopg2==2.7.3.2 +psycopg2==2.8.4 pycodestyle==2.5.0 pyflakes==2.1.1 python-dateutil==2.6.0 @@ -41,15 +43,15 @@ python-editor==1.0.3 python-slugify==1.2.6 pytz==2018.9 PyYAML==5.1 -requests==2.21.0 +requests>=2.21.0 requests-oauthlib==0.8.0 schematics==2.0.0a1 Shapely==1.6.4.post2 six==1.11.0 SQLAlchemy==1.3.0 -transifex-client==0.13.6 +transifex-client==0.13.7 Unidecode==1.0.23 -urllib3==1.23 +urllib3>=1.23 webencodings==0.5.1 Werkzeug==0.12.2 newrelic==3.2.0.91 diff --git a/server/__init__.py b/server/__init__.py index c7b680f944..4792399420 100644 --- a/server/__init__.py +++ b/server/__init__.py @@ -195,8 +195,8 @@ def init_flask_restful_routes(app): api.add_resource(IntersectingTilesAPI, '/api/v1/grid/intersecting-tiles') api.add_resource(SplitTaskAPI, '/api/v1/project//task//split') api.add_resource(LanguagesAPI, '/api/v1/settings') - api.add_resource(ProjectFilesAPI, '/api/v1/admin/project//project-files') - api.add_resource(TasksAsProjectFile, '/api/v1/project//project-file') - api.add_resource(ProjectFileAPI, '/api/v1/admin/project//project-file') - api.add_resource(MapillaryTasksAPI, '/api/v1/admin/mapillary-tasks') - api.add_resource(SequencesAsGPX, '/api/v1/project//sequences-as-gpx') \ No newline at end of file + api.add_resource(ProjectFilesAPI, '/api/v1/admin/project//project-files') + api.add_resource(TasksAsProjectFile, '/api/v1/project//project-file') + api.add_resource(ProjectFileAPI, '/api/v1/admin/project//project-file') + api.add_resource(MapillaryTasksAPI, '/api/v1/admin/mapillary-tasks') + api.add_resource(SequencesAsGPX, '/api/v1/project//sequences-as-gpx') diff --git a/server/api/messaging/message_apis.py b/server/api/messaging/message_apis.py index cba040454a..f2dc481d9a 100644 --- a/server/api/messaging/message_apis.py +++ b/server/api/messaging/message_apis.py @@ -358,6 +358,6 @@ def post(self): MessageService.resend_email_validation(tm.authenticated_user_id) return {"Success": "Verification email resent"}, 200 except Exception as e: - error_msg = f'User GET - unhandled error: {str(e)}' + error_msg = f'Message GET - unhandled error: {str(e)}' current_app.logger.critical(error_msg) return {"error": error_msg}, 500 diff --git a/server/api/project_admin_api.py b/server/api/project_admin_api.py index 05ce8c59d8..1c1ad66ea4 100644 --- a/server/api/project_admin_api.py +++ b/server/api/project_admin_api.py @@ -213,12 +213,12 @@ def post(self, project_id): type: array items: type: string - default: [ID, RAPID, JOSM, POTLATCH_2, FIELD_PAPERS] + default: [ID, JOSM, POTLATCH_2, FIELD_PAPERS] validationEditors: type: array items: type: string - default: [ID, RAPID, JOSM, POTLATCH_2, FIELD_PAPERS] + default: [ID, JOSM, POTLATCH_2, FIELD_PAPERS] campaignTag: type: string default: malaria diff --git a/server/config.py b/server/config.py index 95d7fce73c..3190a024b7 100644 --- a/server/config.py +++ b/server/config.py @@ -88,3 +88,69 @@ class EnvironmentConfig: SQLALCHEMY_TRACK_MODIFICATIONS = False SQLALCHEMY_POOL_SIZE = 10 SQLALCHEMY_MAX_OVERFLOW = 10 + SECRET_KEY = os.getenv('TM_SECRET', None) + SMTP_SETTINGS = { + 'host': os.getenv('TM_SMTP_HOST', None), + 'smtp_user': os.getenv('TM_SMTP_USER', None), + 'smtp_port': os.getenv('TM_SMTP_PORT', 25), # GMail SMTP is over port 587 and will fail on the default port + 'smtp_password': os.getenv('TM_SMTP_PASSWORD', None), + } + # Note that there must be exactly the same number of Codes as languages, or errors will occur + SUPPORTED_LANGUAGES = { + 'codes': 'ar, cs, da, de, en, es, fr, hu, id, it, ja, lt, mg, nb, nl_NL, pl, pt, pt_BR, ru, si, sl, ta, uk, zh_TW', + 'languages': 'Arabic, Česky, Dansk, Deutsch, English, Español, Français, Magyar, Indonesia, Italiano, 日本語, Lietuvos, Malagasy, Bokmål, Nederlands, Polish, Português, Português (Brasil), Русский, සිංහල, Slovenščina, தமிழ், Українська, 中文' + } + PROJECT_FILES_DIR = './server/project_files' + MAPILLARY_API = { + "base": "https://a.mapillary.com/v3/", + "clientId": "LVZRT2ZMZkl5RFpGZFp3NzZKaGhaQTpmMGVmNDU1NDI0NmI2YjNm" + } + + +class ProdConfig(EnvironmentConfig): + APP_BASE_URL = 'https://tasks.kaart.com' + API_DOCS_URL = f'{APP_BASE_URL}/api-docs/swagger-ui/index.html?' + \ + f'url={APP_BASE_URL}/api/docs' + LOG_DIR = '/var/log/tasking-manager-logs' + LOG_LEVEL = logging.ERROR + + +class StageConfig(EnvironmentConfig): + APP_BASE_URL = 'https://tasks-stage.kaart.com' + API_DOCS_URL = f'{APP_BASE_URL}/api-docs/swagger-ui/index.html?' + \ + f'url={APP_BASE_URL}/api/docs' + LOG_DIR = '/var/log/tasking-manager-stage-logs' + LOG_LEVEL = logging.DEBUG + + +class DemoConfig(EnvironmentConfig): + APP_BASE_URL = 'https://tasks-demo.hotosm.org' + API_DOCS_URL = f'{APP_BASE_URL}/api-docs/swagger-ui/index.html?' + \ + f'url={APP_BASE_URL}/api/docs' + LOG_DIR = '/var/log/tasking-manager-logs' + LOG_LEVEL = logging.DEBUG + + +class StagingConfig(EnvironmentConfig): + # Currently being used by Thinkwhere + APP_BASE_URL = 'http://tasking-manager-staging.eu-west-1.elasticbeanstalk.com' + API_DOCS_URL = f'{APP_BASE_URL}/api-docs/swagger-ui/index.html?' + \ + f'url={APP_BASE_URL}/api/docs' + LOG_DIR = '/var/log/tasking-manager-logs' + LOG_LEVEL = logging.DEBUG + + +class DevConfig(EnvironmentConfig): + APP_BASE_URL = 'http://127.0.0.1:5000' + API_DOCS_URL = f'{APP_BASE_URL}/api-docs/swagger-ui/index.html?' + \ + f'url={APP_BASE_URL}/api/docs' + LOG_DIR = 'logs' + LOG_LEVEL = logging.DEBUG + + +class DevIPv6Config(EnvironmentConfig): + APP_BASE_URL = 'http://[::1]:5000' + API_DOCS_URL = f'{APP_BASE_URL}/api-docs/swagger-ui/index.html?' + \ + f'url={APP_BASE_URL}/api/docs' + LOG_DIR = 'logs' + LOG_LEVEL = logging.DEBUG diff --git a/server/models/dtos/project_dto.py b/server/models/dtos/project_dto.py index 4898a50928..81d4061893 100644 --- a/server/models/dtos/project_dto.py +++ b/server/models/dtos/project_dto.py @@ -7,7 +7,6 @@ from server.models.dtos.stats_dto import Pagination from server.models.postgis.statuses import ProjectStatus, ProjectPriority, MappingTypes, TaskCreationMode, Editors, UploadPolicy - def is_known_project_status(value): """ Validates that Project Status is known value """ if type(value) == list: @@ -53,7 +52,8 @@ def is_known_editor(value): except KeyError: raise ValidationError(f'Unknown editor: {value} Valid values are {Editors.ID.name}, ' f'{Editors.JOSM.name}, {Editors.POTLATCH_2.name}, ' - f'{Editors.FIELD_PAPERS.name}, {Editors.RAPID.name}') + f'{Editors.RAPID.name}, {Editors.JOSMMAPWITHAI.name}, ' + f'{Editors.FIELD_PAPERS.name}, {Editors.CUSTOM.name}') def is_known_task_creation_mode(value): @@ -95,6 +95,14 @@ class ProjectInfoDTO(Model): per_task_instructions = StringType(default='', serialized_name='perTaskInstructions') +class CustomEditorDTO(Model): + """ DTO to define a custom editor """ + name = StringType(required=True) + description = StringType() + url = StringType(required=True) + enabled = BooleanType(default=False) + + class ProjectDTO(Model): """ Describes JSON model for a tasking manager project """ project_id = IntType(serialized_name='projectId') @@ -121,6 +129,7 @@ class ProjectDTO(Model): due_date = DateTimeType(serialized_name='dueDate') imagery = StringType() josm_preset = StringType(serialized_name='josmPreset', serialize_when_none=False) + id_presets = ListType(StringType, serialized_name='idPresets', default=[]) mapping_types = ListType(StringType, serialized_name='mappingTypes', validators=[is_known_mapping_type]) campaign_tag = StringType(serialized_name='campaignTag') organisation_tag = StringType(serialized_name='organisationTag') @@ -135,6 +144,7 @@ class ProjectDTO(Model): validators=[is_known_task_creation_mode], serialize_when_none=False) mapping_editors = ListType(StringType, min_size=1, required=True, serialized_name='mappingEditors', validators=[is_known_editor]) validation_editors = ListType(StringType, min_size=1, required=True, serialized_name='validationEditors', validators=[is_known_editor]) + custom_editor = ModelType(CustomEditorDTO, serialized_name='customEditor', serialize_when_none=False) class ProjectSearchDTO(Model): @@ -271,24 +281,6 @@ def __init__(self): active_projects = ListType(ModelType(ProjectSummary), serialized_name='activeProjects') archived_projects = ListType(ModelType(ProjectSummary), serialized_name='archivedProjects') -class ProjectFileDTO(Model): - """ Contains project file info """ - id = IntType(serialized_name='id') - path = StringType(required=True, serialized_name='path') - file_name = StringType(required=True, serialized_name="fileName") - project_id = IntType(required=True, serialized_name="projectId") - upload_policy = StringType(required=True, validators=[is_known_upload_policy], serialized_name='uploadPolicy') - - -class ProjectFilesDTO(Model): - """ DTO used to return all files in a project """ - def __init__(self): - """ DTO constructor initialise all arrays to empty """ - super().__init__() - self.project_files = [] - - project_files = ListType(ModelType(ProjectFileDTO), serialized_name='projectFiles') - class ProjectTaskAnnotationsDTO(Model): """ DTO for task annotations of a project """ @@ -328,3 +320,21 @@ class ProjectUserStatsDTO(Model): time_spent_mapping = IntType(serialized_name='timeSpentMapping') time_spent_validating = IntType(serialized_name='timeSpentValidating') total_time_spent = IntType(serialized_name='totalTimeSpent') + +class ProjectFileDTO(Model): + """ Contains project file info """ + id = IntType(serialized_name='id') + path = StringType(required=True, serialized_name='path') + file_name = StringType(required=True, serialized_name="fileName") + project_id = IntType(required=True, serialized_name="projectId") + upload_policy = StringType(required=True, validators=[is_known_upload_policy], serialized_name='uploadPolicy') + + +class ProjectFilesDTO(Model): + """ DTO used to return all files in a project """ + def __init__(self): + """ DTO constructor initialise all arrays to empty """ + super().__init__() + self.project_files = [] + + project_files = ListType(ModelType(ProjectFileDTO), serialized_name='projectFiles') diff --git a/server/models/postgis/custom_editors.py b/server/models/postgis/custom_editors.py new file mode 100644 index 0000000000..bfb3430e58 --- /dev/null +++ b/server/models/postgis/custom_editors.py @@ -0,0 +1,48 @@ +from server import db +from server.models.dtos.project_dto import CustomEditorDTO + + +class CustomEditor(db.Model): + """ Model for user defined editors for a project """ + __tablename__ = "project_custom_editors" + project_id = db.Column(db.Integer, db.ForeignKey('projects.id'), primary_key=True) + name = db.Column(db.String(50), nullable=False) + description = db.Column(db.String) + url = db.Column(db.String, nullable=False) + enabled = db.Column(db.Boolean, nullable=False, default=False) + + @staticmethod + def get_by_project_id(project_id: int): + """ Get custom editor by it's project id """ + return CustomEditor.query.get(project_id) + + @classmethod + def create_from_dto(cls, project_id: int, dto: CustomEditorDTO): + """ Creates a new CustomEditor from dto, used in project edit """ + new_editor = cls() + new_editor.project_id = project_id + new_editor.update_editor(dto) + return new_editor + + def update_editor(self, dto: CustomEditorDTO): + """ Upates existing CustomEditor form DTO """ + self.name = dto.name + self.description = dto.description + self.url = dto.url + self.enabled = dto.enabled + + def delete(self): + """ Deletes the current model from the DB """ + db.session.delete(self) + db.session.commit() + + def as_dto(self) -> CustomEditorDTO: + """ Returns the CustomEditor as a DTO """ + dto = CustomEditorDTO() + dto.project_id = self.project_id + dto.name = self.name + dto.description = self.description + dto.url = self.url + dto.enabled = self.enabled + + return dto diff --git a/server/models/postgis/project.py b/server/models/postgis/project.py index 59f3645951..47dbd05730 100644 --- a/server/models/postgis/project.py +++ b/server/models/postgis/project.py @@ -19,8 +19,9 @@ import datetime from server import db -from server.models.dtos.project_dto import ProjectDTO, DraftProjectDTO, ProjectSummary, PMDashboardDTO, ProjectStatsDTO, ProjectUserStatsDTO +from server.models.dtos.project_dto import ProjectDTO, DraftProjectDTO, ProjectSummary, PMDashboardDTO, ProjectStatsDTO, ProjectUserStatsDTO, CustomEditorDTO from server.models.dtos.tags_dto import TagsDTO +from server.models.postgis.custom_editors import CustomEditor from server.models.postgis.priority_area import PriorityArea, project_priority_areas from server.models.postgis.project_info import ProjectInfo from server.models.postgis.project_chat import ProjectChat @@ -73,6 +74,7 @@ class Project(db.Model): geometry = db.Column(Geometry('MULTIPOLYGON', srid=4326)) centroid = db.Column(Geometry('POINT', srid=4326)) task_creation_mode = db.Column(db.Integer, default=TaskCreationMode.GRID.value, nullable=False) + id_presets = db.Column(ARRAY(db.String)) # Tags mapping_types = db.Column(ARRAY(db.Integer), index=True) @@ -82,15 +84,17 @@ class Project(db.Model): # Editors mapping_editors = db.Column(ARRAY(db.Integer), default=[ Editors.ID.value, - Editors.RAPID.value, Editors.JOSM.value, + Editors.RAPID.value, + Editors.JOSMMAPWITHAI.value, Editors.POTLATCH_2.value, Editors.FIELD_PAPERS.value], index=True, nullable=False) validation_editors = db.Column(ARRAY(db.Integer), default=[ Editors.ID.value, - Editors.RAPID.value, Editors.JOSM.value, + Editors.RAPID.value, + Editors.JOSMMAPWITHAI.value, Editors.POTLATCH_2.value, Editors.FIELD_PAPERS.value], index=True, nullable=False) @@ -109,6 +113,7 @@ class Project(db.Model): allowed_users = db.relationship(User, secondary=project_allowed_users) priority_areas = db.relationship(PriorityArea, secondary=project_priority_areas, cascade="all, delete-orphan", single_parent=True) + custom_editor = db.relationship(CustomEditor, uselist=False) def create_draft_project(self, draft_project_dto: DraftProjectDTO): """ @@ -244,6 +249,7 @@ def update(self, project_dto: ProjectDTO): self.due_date = project_dto.due_date self.imagery = project_dto.imagery self.josm_preset = project_dto.josm_preset + self.id_presets = project_dto.id_presets self.last_updated = timestamp() self.license_id = project_dto.license_id @@ -306,6 +312,16 @@ def update(self, project_dto: ProjectDTO): pa = PriorityArea.from_dict(priority_area) self.priority_areas.append(pa) + if project_dto.custom_editor: + if not self.custom_editor: + new_editor = CustomEditor.create_from_dto(self.id, project_dto.custom_editor) + self.custom_editor = new_editor + else: + self.custom_editor.update_editor(project_dto.custom_editor) + else: + if self.custom_editor: + self.custom_editor.delete() + db.session.commit() def delete(self): @@ -623,6 +639,7 @@ def _get_project_and_base_dto(self): base_dto.due_date = self.due_date base_dto.imagery = self.imagery base_dto.josm_preset = self.josm_preset + base_dto.id_presets = self.id_presets base_dto.campaign_tag = self.campaign_tag base_dto.organisation_tag = self.organisation_tag base_dto.license_id = self.license_id @@ -632,6 +649,9 @@ def _get_project_and_base_dto(self): base_dto.active_mappers = Project.get_active_mappers(self.id) base_dto.task_creation_mode = TaskCreationMode(self.task_creation_mode).name + if self.custom_editor: + base_dto.custom_editor = self.custom_editor.as_dto() + if self.private: # If project is private it should have a list of allowed users allowed_usernames = [] diff --git a/server/models/postgis/statuses.py b/server/models/postgis/statuses.py index afe07dbcca..10e00908c2 100644 --- a/server/models/postgis/statuses.py +++ b/server/models/postgis/statuses.py @@ -75,17 +75,18 @@ class UserRole(Enum): PROJECT_MANAGER = 2 VALIDATOR = 4 - -class UploadPolicy(Enum): - """ Describes the upload policies for a project file """ - BLOCK = 0 - ALLOW = 1 - DISCOURAGE = 2 - class Editors(Enum): """ Enum describing the possible editors for projects """ ID = 0 JOSM = 1 POTLATCH_2 = 2 FIELD_PAPERS = 3 - RAPID = 4 + CUSTOM = 4 + RAPID = 10005 + JOSMMAPWITHAI = 10006 + +class UploadPolicy(Enum): + """ Describes the upload policies for a project file """ + BLOCK = 0 + ALLOW = 1 + DISCOURAGE = 2 diff --git a/server/models/postgis/task.py b/server/models/postgis/task.py index fbafc203e0..1e8146bde3 100644 --- a/server/models/postgis/task.py +++ b/server/models/postgis/task.py @@ -618,7 +618,6 @@ def get_tasks_as_geojson_feature_collection(project_id, task_ids=[]): :param project_id: Owning project ID :return: geojson.FeatureCollection """ - if task_ids: project_tasks = \ db.session.query(Task.id, diff --git a/server/models/postgis/user.py b/server/models/postgis/user.py index f7257c269b..b931116dc2 100644 --- a/server/models/postgis/user.py +++ b/server/models/postgis/user.py @@ -114,7 +114,7 @@ def get_all_users_not_pagainated(): @staticmethod - def filter_users(user_filter: str, project_id: int, page: int, + def filter_users(user_filter: str, project_id: int, page: int, is_project_manager:bool=False) -> UserFilterDTO: """ Finds users that matches first characters, for auto-complete. @@ -130,7 +130,6 @@ def filter_users(user_filter: str, project_id: int, page: int, query = query.filter(User.role.in_([UserRole.ADMIN.value, UserRole.PROJECT_MANAGER.value])) results = query.paginate(page, 20, True) - if results.total == 0: raise NotFound() diff --git a/server/services/grid/split_service.py b/server/services/grid/split_service.py index fa657e4ba4..3e4f04041a 100644 --- a/server/services/grid/split_service.py +++ b/server/services/grid/split_service.py @@ -9,7 +9,7 @@ from server.models.postgis.utils import ST_Transform from server.models.postgis.task import Task, TaskStatus, TaskAction from server.models.postgis.project import Project -from server.models.postgis.utils import NotFound +from server.models.postgis.utils import NotFound, InvalidGeoJson class SplitServiceError(Exception): @@ -30,17 +30,11 @@ def _create_split_tasks(x, y, zoom, task) -> list: """ # If the task's geometry doesn't correspond to an OSM tile identified by an # x, y, zoom then we need to take a different approach to splitting - if x is None or y is None or zoom is None: + if x is None or y is None or zoom is None or not task.is_square: return SplitService._create_split_tasks_from_geometry(task) try: task_geojson = False - - if task and not task.is_square: - query = db.session.query(Task.id, Task.geometry.ST_AsGeoJSON().label('geometry')) \ - .filter(Task.id == task.id, Task.project_id == task.project_id) - - task_geojson = geojson.loads(query[0].geometry) split_geoms = [] for i in range(0, 2): for j in range(0, 2): @@ -50,22 +44,11 @@ def _create_split_tasks(x, y, zoom, task) -> list: new_square = SplitService._create_square(new_x, new_y, new_zoom) feature = geojson.Feature() feature.geometry = new_square - feature_shape = shapely_shape(feature.geometry) - if task and not task.is_square: - intersection = shapely_shape(task_geojson).intersection(feature_shape) - multipolygon = MultiPolygon([intersection]) - feature.geometry = geojson.loads(geojson.dumps(mapping(multipolygon))) - - if task and not task.is_square: - is_square = False - else: - is_square = True - feature.properties = { 'x': new_x, 'y': new_y, 'zoom': new_zoom, - 'isSquare': is_square + 'isSquare': True } if (len(feature.geometry.coordinates) > 0): @@ -171,6 +154,8 @@ def split_task(split_task_dto: SplitTaskDTO) -> TaskDTOs: if original_task is None: raise NotFound() + original_geometry = shape.to_shape(original_task.geometry) + # check its locked for mapping by the current user if TaskStatus(original_task.task_status) != TaskStatus.LOCKED_FOR_MAPPING: raise SplitServiceError('Status must be LOCKED_FOR_MAPPING to split') @@ -187,8 +172,14 @@ def split_task(split_task_dto: SplitTaskDTO) -> TaskDTOs: # create new tasks from the new geojson i = Task.get_max_task_id_for_project(split_task_dto.project_id) + new_tasks = [] new_tasks_dto = [] for new_task_geojson in new_tasks_geojson: + # Sanity check: ensure the new task geometry intersects the original task geometry + new_geometry = shapely_shape(new_task_geojson.geometry) + if not new_geometry.intersects(original_geometry): + raise InvalidGeoJson('New split task does not intersect original task') + # insert new tasks into database i = i + 1 new_task = Task.from_geojson_feature(i, new_task_geojson) @@ -201,11 +192,20 @@ def split_task(split_task_dto: SplitTaskDTO) -> TaskDTOs: new_task.set_task_history(TaskAction.STATE_CHANGE, split_task_dto.user_id, None, TaskStatus.SPLIT) new_task.set_task_history(TaskAction.STATE_CHANGE, split_task_dto.user_id, None, TaskStatus.READY) new_task.task_status = TaskStatus.READY.value + new_tasks.append(new_task) new_task.update() new_tasks_dto.append(new_task.as_dto_with_instructions(split_task_dto.preferred_locale)) # delete original task from the database - original_task.delete() + try: + original_task.delete() + except: + db.session.rollback() + # Ensure the new tasks are cleaned up + for new_task in new_tasks: + new_task.delete() + db.session.commit() + raise # update project task counts project = Project.get(split_task_dto.project_id) diff --git a/server/services/mapillary_service.py b/server/services/mapillary_service.py index 3766c6b01b..24601b0ae8 100644 --- a/server/services/mapillary_service.py +++ b/server/services/mapillary_service.py @@ -15,25 +15,25 @@ from server.models.postgis.utils import NotFound -# Given 2 mapillary sequences WITHIN a buffer of one, check the properties to determine if the +# Given 2 mapillary sequences WITHIN a buffer of each other, check the properties to determine if the # sequence was uploaded twice to Mapillary def duplicateSequences(props1, props2): - # If they're the same sequence -- shouldn't happen but just in case - if props1['key'] == props2['key']: - return True + # If they're the same sequence -- shouldn't happen but just in case + if props1['key'] == props2['key']: + return True - if props1['coordinateProperties']['cas'] != props2['coordinateProperties']['cas']: - return False + if props1['coordinateProperties']['cas'] != props2['coordinateProperties']['cas']: + return False - return True + return True def fetch(url, session): - with closing(session.get(url)) as response: - return response + with closing(session.get(url)) as response: + return response -async def run(urls, features, old_urls = None): +async def run(urls, features, old_urls=None): new_urls = [] if old_urls is None: old_urls = [] @@ -57,7 +57,7 @@ async def run(urls, features, old_urls = None): if next_url not in old_urls: new_urls.append(next_url) old_urls.append(next_url) - except KeyError as e: + except KeyError: pass json_response = response.json() if 'features' in json_response: @@ -66,7 +66,7 @@ async def run(urls, features, old_urls = None): current_app.logger.critical(f'No features for {response.url}:\r\n{json_response}') pass if new_urls: - await run(new_urls, features, old_urls = old_urls) + await run(new_urls, features, old_urls=old_urls) class MapillaryService: diff --git a/server/services/messaging/smtp_service.py b/server/services/messaging/smtp_service.py index 1c91f9eca2..708a77befe 100644 --- a/server/services/messaging/smtp_service.py +++ b/server/services/messaging/smtp_service.py @@ -71,12 +71,16 @@ def _send_mesage(to_address: str, subject: str, html_message: str, text_message: msg.attach(part1) msg.attach(part2) - sender = SMTPService._init_smtp_client() - - current_app.logger.debug(f'Sending email via SMTP {to_address}') - sender.sendmail(from_address, to_address, msg.as_string()) - sender.quit() - current_app.logger.debug(f'Email sent {to_address}') + try: + sender = SMTPService._init_smtp_client() + + current_app.logger.debug(f'Sending email via SMTP to {to_address}') + sender.sendmail(from_address, to_address, msg.as_string()) + sender.quit() + current_app.logger.debug(f'Email sent {to_address}') + except smtplib.SMTPServerDisconnected as e: + current_app.logger.debug(msg) + current_app.logger.critical(e) @staticmethod def _init_smtp_client(): diff --git a/server/services/project_admin_service.py b/server/services/project_admin_service.py index 07016a6767..4ab7b30bc4 100644 --- a/server/services/project_admin_service.py +++ b/server/services/project_admin_service.py @@ -8,7 +8,7 @@ from server.models.dtos.project_dto import DraftProjectDTO, ProjectDTO, ProjectCommentsDTO, ProjectFileDTO from server.models.postgis.project import Project, Task, ProjectStatus from server.models.postgis.project_files import ProjectFiles -from server.models.postgis.statuses import TaskCreationMode, UploadPolicy, UserRole +from server.models.postgis.statuses import TaskCreationMode, UserRole, UploadPolicy from server.models.postgis.task import TaskHistory, TaskStatus, TaskAction from server.models.postgis.utils import NotFound, InvalidData, InvalidGeoJson from server.services.grid.grid_service import GridService @@ -41,6 +41,10 @@ def create_draft_project(draft_project_dto: DraftProjectDTO) -> int: :raises InvalidGeoJson :returns ID of new draft project """ + # First things first, we need to validate that the author_id is a PM. issue #1715 + if not UserService.is_user_a_project_manager(draft_project_dto.user_id): + raise(ProjectAdminServiceError(f'User {UserService.get_user_by_id(draft_project_dto.user_id).username} is not a project manager')) + # If we're cloning we'll copy all the project details from the clone, otherwise create brand new project if draft_project_dto.cloneFromProjectId: draft_project = Project.clone(draft_project_dto.cloneFromProjectId, draft_project_dto.user_id) @@ -226,6 +230,30 @@ def get_projects_for_admin(admin_id: int, preferred_locale: str): """ Get all projects for provided admin """ return Project.get_projects_for_admin(admin_id, preferred_locale) + @staticmethod + def transfer_project_to(project_id: int, transfering_user_id: int, username: str): + """ Transfers project from old owner (transfering_user_id) to new owner (username) """ + project = Project.get(project_id) + + transfering_user = UserService.get_user_by_id(transfering_user_id) + new_owner = UserService.get_user_by_username(username) + is_pm = new_owner.role in (UserRole.PROJECT_MANAGER.value, UserRole.ADMIN.value) + + if not is_pm: + raise Exception("User must be a project manager") + + if transfering_user.role == UserRole.PROJECT_MANAGER.value: + if project.author_id == transfering_user_id: + project.author_id = new_owner.id + project.save() + else: + raise Exception("Invalid owner_id") + elif transfering_user.role == UserRole.ADMIN.value: + project.author_id = new_owner.id + project.save() + else: + raise Exception("Normal users cannot transfer projects") + @staticmethod def create_project_file(dto: ProjectFileDTO): """ Crate a new project file """ @@ -285,4 +313,3 @@ def transfer_project_to(project_id: int, transfering_user_id: int, username: str project.save() else: raise Exception("Normal users cannot transfer projects") - diff --git a/server/services/stats_service.py b/server/services/stats_service.py index a750746ca1..fdcbba7476 100644 --- a/server/services/stats_service.py +++ b/server/services/stats_service.py @@ -3,10 +3,15 @@ from sqlalchemy import func, text from server import db from server.models.dtos.stats_dto import ( - ProjectContributionsDTO, UserContribution, Pagination, TaskHistoryDTO, - ProjectActivityDTO, HomePageStatsDTO, OrganizationStatsDTO, - CampaignStatsDTO - ) + ProjectContributionsDTO, + UserContribution, + Pagination, + TaskHistoryDTO, + ProjectActivityDTO, + HomePageStatsDTO, + OrganizationStatsDTO, + CampaignStatsDTO, +) from server.models.postgis.project import Project from server.models.postgis.statuses import TaskStatus from server.models.postgis.task import TaskHistory, User, Task @@ -21,17 +26,28 @@ class StatsService: @staticmethod - def update_stats_after_task_state_change(project_id: int, user_id: int, last_state: TaskStatus, - new_state: TaskStatus, action='change'): + def update_stats_after_task_state_change( + project_id: int, + user_id: int, + last_state: TaskStatus, + new_state: TaskStatus, + action="change", + ): """ Update stats when a task has had a state change """ - if new_state in [TaskStatus.READY, TaskStatus.LOCKED_FOR_VALIDATION, TaskStatus.LOCKED_FOR_MAPPING]: + if new_state in [ + TaskStatus.READY, + TaskStatus.LOCKED_FOR_VALIDATION, + TaskStatus.LOCKED_FOR_MAPPING, + ]: return # No stats to record for these states project = ProjectService.get_project_by_id(project_id) user = UserService.get_user_by_id(user_id) - StatsService._update_tasks_stats(project, user, last_state, new_state, action) + project, user = StatsService._update_tasks_stats( + project, user, last_state, new_state, action + ) UserService.upsert_mapped_projects(user_id, project_id) project.last_updated = timestamp() @@ -39,12 +55,20 @@ def update_stats_after_task_state_change(project_id: int, user_id: int, last_sta return project, user @staticmethod - def _update_tasks_stats(project: Project, user: User, last_state: TaskStatus, new_state: TaskStatus, - action='change'): + def _update_tasks_stats( + project: Project, + user: User, + last_state: TaskStatus, + new_state: TaskStatus, + action="change", + ): # Make sure you are aware that users table has it as incrementing counters, # while projects table reflect the actual state, and both increment and decrement happens + if new_state == last_state: + return project, user + # Set counters for new state if new_state == TaskStatus.MAPPED: project.tasks_mapped += 1 @@ -53,7 +77,7 @@ def _update_tasks_stats(project: Project, user: User, last_state: TaskStatus, ne elif new_state == TaskStatus.BADIMAGERY: project.tasks_bad_imagery += 1 - if action == 'change': + if action == "change": if new_state == TaskStatus.MAPPED: user.tasks_mapped += 1 elif new_state == TaskStatus.VALIDATED: @@ -69,7 +93,7 @@ def _update_tasks_stats(project: Project, user: User, last_state: TaskStatus, ne elif last_state == TaskStatus.BADIMAGERY: project.tasks_bad_imagery -= 1 - if action == 'undo': + if action == "undo": if last_state == TaskStatus.MAPPED: user.tasks_mapped -= 1 elif last_state == TaskStatus.VALIDATED: @@ -77,19 +101,28 @@ def _update_tasks_stats(project: Project, user: User, last_state: TaskStatus, ne elif last_state == TaskStatus.INVALIDATED: user.tasks_invalidated -= 1 + return project, user + @staticmethod def get_latest_activity(project_id: int, page: int) -> ProjectActivityDTO: """ Gets all the activity on a project """ - results = db.session.query( - TaskHistory.id, TaskHistory.task_id, TaskHistory.action, TaskHistory.action_date, - TaskHistory.action_text, User.username - ).join(User).filter( - TaskHistory.project_id == project_id, - TaskHistory.action != 'COMMENT' - ).order_by( - TaskHistory.action_date.desc() - ).paginate(page, 10, True) + results = ( + db.session.query( + TaskHistory.id, + TaskHistory.task_id, + TaskHistory.action, + TaskHistory.action_date, + TaskHistory.action_text, + User.username, + ) + .join(User) + .filter( + TaskHistory.project_id == project_id, TaskHistory.action != "COMMENT" + ) + .order_by(TaskHistory.action_date.desc()) + .paginate(page, 10, True) + ) if results.total == 0: raise NotFound() @@ -111,7 +144,7 @@ def get_latest_activity(project_id: int, page: int) -> ProjectActivityDTO: @staticmethod def get_user_contributions(project_id: int) -> ProjectContributionsDTO: """ Get all user contributions on a project""" - contrib_query = '''select m.mapped_by, m.username, m.mapped, v.validated_by, v.username, v.validated + contrib_query = """select m.mapped_by, m.username, m.mapped, v.validated_by, v.username, v.validated from (select t.mapped_by, u.username, count(t.mapped_by) mapped from tasks t, users u @@ -127,7 +160,7 @@ def get_user_contributions(project_id: int) -> ProjectContributionsDTO: and t.validated_by is not null group by t.validated_by, u.username) v ON m.mapped_by = v.validated_by - ''' + """ results = db.engine.execute(text(contrib_query), project_id=project_id) if results.rowcount == 0: @@ -155,58 +188,68 @@ def get_homepage_stats() -> HomePageStatsDTO: dto = HomePageStatsDTO() dto.total_projects = Project.query.count() - dto.mappers_online = Task.query.filter( - Task.locked_by is not None - ).distinct(Task.locked_by).count() + dto.mappers_online = ( + Task.query.filter(Task.locked_by is not None) + .distinct(Task.locked_by) + .count() + ) dto.total_mappers = User.query.count() - dto.total_validators = Task.query.filter( - Task.task_status == TaskStatus.VALIDATED.value - ).distinct(Task.validated_by).count() + dto.total_validators = ( + Task.query.filter(Task.task_status == TaskStatus.VALIDATED.value) + .distinct(Task.validated_by) + .count() + ) dto.tasks_mapped = Task.query.filter( - Task.task_status.in_( - (TaskStatus.MAPPED.value, TaskStatus.VALIDATED.value) - ) - ).count() + Task.task_status.in_((TaskStatus.MAPPED.value, TaskStatus.VALIDATED.value)) + ).count() dto.tasks_validated = Task.query.filter( Task.task_status == TaskStatus.VALIDATED.value - ).count() + ).count() - org_proj_count = db.session.query( - Project.organisation_tag, - func.count(Project.organisation_tag) - ).group_by(Project.organisation_tag).all() + org_proj_count = ( + db.session.query( + Project.organisation_tag, func.count(Project.organisation_tag) + ) + .group_by(Project.organisation_tag) + .all() + ) untagged_count = 0 # total_area = 0 - - - # dto.total_area = 0 + # dto.total_area = 0 # total_area_sql = """select sum(ST_Area(geometry)) from public.projects as area""" # total_area_result = db.engine.execute(total_area_sql) # current_app.logger.debug(total_area_result) # for rowproxy in total_area_result: - # rowproxy.items() returns an array like [(key0, value0), (key1, value1)] - # for tup in rowproxy.items(): - # total_area += tup[1] - # current_app.logger.debug(total_area) + # rowproxy.items() returns an array like [(key0, value0), (key1, value1)] + # for tup in rowproxy.items(): + # total_area += tup[1] + # current_app.logger.debug(total_area) # dto.total_area = total_area tasks_mapped_sql = "select coalesce(sum(ST_Area(geometry)), 0) as sum from public.tasks where task_status = :task_status" - tasks_mapped_result = db.engine.execute(text(tasks_mapped_sql), task_status=TaskStatus.MAPPED.value) + tasks_mapped_result = db.engine.execute( + text(tasks_mapped_sql), task_status=TaskStatus.MAPPED.value + ) - dto.total_mapped_area = tasks_mapped_result.fetchone()['sum'] + dto.total_mapped_area = tasks_mapped_result.fetchone()["sum"] tasks_validated_sql = "select coalesce(sum(ST_Area(geometry)), 0) as sum from public.tasks where task_status = :task_status" - tasks_validated_result = db.engine.execute(text(tasks_validated_sql), task_status=TaskStatus.VALIDATED.value) + tasks_validated_result = db.engine.execute( + text(tasks_validated_sql), task_status=TaskStatus.VALIDATED.value + ) - dto.total_validated_area = tasks_validated_result.fetchone()['sum'] + dto.total_validated_area = tasks_validated_result.fetchone()["sum"] - campaign_count = db.session.query(Project.campaign_tag, func.count(Project.campaign_tag))\ - .group_by(Project.campaign_tag).all() + campaign_count = ( + db.session.query(Project.campaign_tag, func.count(Project.campaign_tag)) + .group_by(Project.campaign_tag) + .all() + ) no_campaign_count = 0 unique_campaigns = 0 @@ -219,12 +262,17 @@ def get_homepage_stats() -> HomePageStatsDTO: no_campaign_count += campaign_stats.projects_created if no_campaign_count: - no_campaign_proj = CampaignStatsDTO(('Untagged', no_campaign_count)) + no_campaign_proj = CampaignStatsDTO(("Untagged", no_campaign_count)) dto.campaigns.append(no_campaign_proj) dto.total_campaigns = unique_campaigns - org_proj_count = db.session.query(Project.organisation_tag, func.count(Project.organisation_tag))\ - .group_by(Project.organisation_tag).all() + org_proj_count = ( + db.session.query( + Project.organisation_tag, func.count(Project.organisation_tag) + ) + .group_by(Project.organisation_tag) + .all() + ) no_org_count = 0 unique_orgs = 0 @@ -237,8 +285,31 @@ def get_homepage_stats() -> HomePageStatsDTO: no_org_count += org_stats.projects_created if no_org_count: - no_org_proj = OrganizationStatsDTO(('Untagged', no_org_count)) + no_org_proj = OrganizationStatsDTO(("Untagged", no_org_count)) dto.organizations.append(no_org_proj) dto.total_organizations = unique_orgs return dto + + @staticmethod + def update_all_project_stats(): + projects = db.session.query(Project.id) + for project_id in projects.all(): + StatsService.update_project_stats(project_id) + + @staticmethod + def update_project_stats(project_id: int): + project = ProjectService.get_project_by_id(project_id) + tasks = Task.query.filter(Task.project_id == project_id) + + project.total_tasks = tasks.count() + project.tasks_mapped = tasks.filter( + Task.task_status == TaskStatus.MAPPED.value + ).count() + project.tasks_validated = tasks.filter( + Task.task_status == TaskStatus.VALIDATED.value + ).count() + project.tasks_bad_imagery = tasks.filter( + Task.task_status == TaskStatus.BADIMAGERY.value + ).count() + project.save() diff --git a/server/services/users/user_service.py b/server/services/users/user_service.py index 2c281707cc..5dedc93920 100644 --- a/server/services/users/user_service.py +++ b/server/services/users/user_service.py @@ -175,7 +175,7 @@ def get_all_users(query: UserSearchQuery) -> UserSearchDTO: @staticmethod @cached(user_filter_cache) - def filter_users(username: str, project_id: int, + def filter_users(username: str, project_id: int, page: int, is_project_manager: bool = False) -> UserFilterDTO: """ Gets paginated list of users, filtered by username, for autocomplete """ return User.filter_users(username, project_id, page, is_project_manager) diff --git a/server/services/validator_service.py b/server/services/validator_service.py index bd71793678..8e20ecc51d 100644 --- a/server/services/validator_service.py +++ b/server/services/validator_service.py @@ -214,7 +214,6 @@ def get_user_invalidated_tasks(as_validator, username: str, preferred_locale: st if project_id is not None: query = query.filter_by(project_id=project_id) - results = query.order_by(text(sort_by + " " + sort_direction)).paginate(page, page_size, True) project_names = {} invalidated_tasks_dto = InvalidatedTasks() diff --git a/server/tools/osmosis/copying.txt b/server/tools/osmosis/copying.txt index 4e6d150af9..ebc12ed2b5 100644 --- a/server/tools/osmosis/copying.txt +++ b/server/tools/osmosis/copying.txt @@ -13,4 +13,4 @@ software useful. However: law. * Unless required by applicable law, no liability will be accepted by the authors and distributors of this software for any damages caused -as a result of its use. +as a result of its use. diff --git a/server/tools/osmosis/script/contrib/CreateGeometryForWays.sql b/server/tools/osmosis/script/contrib/CreateGeometryForWays.sql index 7e4603af5c..0523f106c3 100644 --- a/server/tools/osmosis/script/contrib/CreateGeometryForWays.sql +++ b/server/tools/osmosis/script/contrib/CreateGeometryForWays.sql @@ -23,26 +23,26 @@ SELECT AddGeometryColumn('', 'way_geometry', 'geom', 4326, 'GEOMETRY', 2); ------------------------------------------------------------------------------- -- add a linestring for every way (create a polyline) -INSERT INTO way_geometry select id, ( select ST_LineFromMultiPoint( Collect(nodes.geom) ) from nodes +INSERT INTO way_geometry select id, ( select ST_LineFromMultiPoint( Collect(nodes.geom) ) from nodes left join way_nodes on nodes.id=way_nodes.node_id where way_nodes.way_id=ways.id ) FROM ways; --- after creating a line for every way (polyline), we want closed ways to be stored as polygones. +-- after creating a line for every way (polyline), we want closed ways to be stored as polygones. -- So we need to delete the previously created polylines for these ways first. -DELETE FROM way_geometry WHERE way_id IN - ( SELECT ways.id FROM ways - WHERE ST_IsClosed( (SELECT ST_LineFromMultiPoint( Collect(n.geom) ) FROM nodes n LEFT JOIN way_nodes wn ON n.id=wn.node_id WHERE ways.id=wn.way_id) ) - AND ST_NumPoints( (SELECT ST_LineFromMultiPoint( Collect(n.geom) ) FROM nodes n LEFT JOIN way_nodes wn ON n.id=wn.node_id WHERE ways.id=wn.way_id) ) >= 3 +DELETE FROM way_geometry WHERE way_id IN + ( SELECT ways.id FROM ways + WHERE ST_IsClosed( (SELECT ST_LineFromMultiPoint( Collect(n.geom) ) FROM nodes n LEFT JOIN way_nodes wn ON n.id=wn.node_id WHERE ways.id=wn.way_id) ) + AND ST_NumPoints( (SELECT ST_LineFromMultiPoint( Collect(n.geom) ) FROM nodes n LEFT JOIN way_nodes wn ON n.id=wn.node_id WHERE ways.id=wn.way_id) ) >= 3 ) ; -- now we need to add the polyline geometry for every closed way -INSERT INTO way_geometry SELECT ways.id, - ( SELECT ST_MakePolygon( ST_LineFromMultiPoint(Collect(nodes.geom)) ) FROM nodes - LEFT JOIN way_nodes ON nodes.id=way_nodes.node_id WHERE way_nodes.way_id=ways.id - ) -FROM ways -WHERE ST_IsClosed( (SELECT ST_LineFromMultiPoint( Collect(n.geom) ) FROM nodes n LEFT JOIN way_nodes wn ON n.id=wn.node_id WHERE ways.id=wn.way_id) ) -AND ST_NumPoints( (SELECT ST_LineFromMultiPoint( Collect(n.geom) ) FROM nodes n LEFT JOIN way_nodes wn ON n.id=wn.node_id WHERE ways.id=wn.way_id) ) >= 3 +INSERT INTO way_geometry SELECT ways.id, + ( SELECT ST_MakePolygon( ST_LineFromMultiPoint(Collect(nodes.geom)) ) FROM nodes + LEFT JOIN way_nodes ON nodes.id=way_nodes.node_id WHERE way_nodes.way_id=ways.id + ) +FROM ways +WHERE ST_IsClosed( (SELECT ST_LineFromMultiPoint( Collect(n.geom) ) FROM nodes n LEFT JOIN way_nodes wn ON n.id=wn.node_id WHERE ways.id=wn.way_id) ) +AND ST_NumPoints( (SELECT ST_LineFromMultiPoint( Collect(n.geom) ) FROM nodes n LEFT JOIN way_nodes wn ON n.id=wn.node_id WHERE ways.id=wn.way_id) ) >= 3 ; ------------------------------------------------------------------------------- diff --git a/server/tools/osmosis/script/contrib/apidb_0.6.sql b/server/tools/osmosis/script/contrib/apidb_0.6.sql index ef88ab833c..057f1361e3 100644 --- a/server/tools/osmosis/script/contrib/apidb_0.6.sql +++ b/server/tools/osmosis/script/contrib/apidb_0.6.sql @@ -1901,7 +1901,7 @@ SET default_tablespace = ''; SET default_with_oids = false; -- --- Name: acls; Type: TABLE; Schema: public; Owner: -; Tablespace: +-- Name: acls; Type: TABLE; Schema: public; Owner: -; Tablespace: -- CREATE TABLE acls ( @@ -1933,7 +1933,7 @@ ALTER SEQUENCE acls_id_seq OWNED BY acls.id; -- --- Name: changeset_tags; Type: TABLE; Schema: public; Owner: -; Tablespace: +-- Name: changeset_tags; Type: TABLE; Schema: public; Owner: -; Tablespace: -- CREATE TABLE changeset_tags ( @@ -1944,7 +1944,7 @@ CREATE TABLE changeset_tags ( -- --- Name: changesets; Type: TABLE; Schema: public; Owner: -; Tablespace: +-- Name: changesets; Type: TABLE; Schema: public; Owner: -; Tablespace: -- CREATE TABLE changesets ( @@ -1980,7 +1980,7 @@ ALTER SEQUENCE changesets_id_seq OWNED BY changesets.id; -- --- Name: client_applications; Type: TABLE; Schema: public; Owner: -; Tablespace: +-- Name: client_applications; Type: TABLE; Schema: public; Owner: -; Tablespace: -- CREATE TABLE client_applications ( @@ -2023,7 +2023,7 @@ ALTER SEQUENCE client_applications_id_seq OWNED BY client_applications.id; -- --- Name: countries; Type: TABLE; Schema: public; Owner: -; Tablespace: +-- Name: countries; Type: TABLE; Schema: public; Owner: -; Tablespace: -- CREATE TABLE countries ( @@ -2056,7 +2056,7 @@ ALTER SEQUENCE countries_id_seq OWNED BY countries.id; -- --- Name: current_node_tags; Type: TABLE; Schema: public; Owner: -; Tablespace: +-- Name: current_node_tags; Type: TABLE; Schema: public; Owner: -; Tablespace: -- CREATE TABLE current_node_tags ( @@ -2067,7 +2067,7 @@ CREATE TABLE current_node_tags ( -- --- Name: current_nodes; Type: TABLE; Schema: public; Owner: -; Tablespace: +-- Name: current_nodes; Type: TABLE; Schema: public; Owner: -; Tablespace: -- CREATE TABLE current_nodes ( @@ -2102,7 +2102,7 @@ ALTER SEQUENCE current_nodes_id_seq OWNED BY current_nodes.id; -- --- Name: current_relation_members; Type: TABLE; Schema: public; Owner: -; Tablespace: +-- Name: current_relation_members; Type: TABLE; Schema: public; Owner: -; Tablespace: -- CREATE TABLE current_relation_members ( @@ -2115,7 +2115,7 @@ CREATE TABLE current_relation_members ( -- --- Name: current_relation_tags; Type: TABLE; Schema: public; Owner: -; Tablespace: +-- Name: current_relation_tags; Type: TABLE; Schema: public; Owner: -; Tablespace: -- CREATE TABLE current_relation_tags ( @@ -2126,7 +2126,7 @@ CREATE TABLE current_relation_tags ( -- --- Name: current_relations; Type: TABLE; Schema: public; Owner: -; Tablespace: +-- Name: current_relations; Type: TABLE; Schema: public; Owner: -; Tablespace: -- CREATE TABLE current_relations ( @@ -2158,7 +2158,7 @@ ALTER SEQUENCE current_relations_id_seq OWNED BY current_relations.id; -- --- Name: current_way_nodes; Type: TABLE; Schema: public; Owner: -; Tablespace: +-- Name: current_way_nodes; Type: TABLE; Schema: public; Owner: -; Tablespace: -- CREATE TABLE current_way_nodes ( @@ -2169,7 +2169,7 @@ CREATE TABLE current_way_nodes ( -- --- Name: current_way_tags; Type: TABLE; Schema: public; Owner: -; Tablespace: +-- Name: current_way_tags; Type: TABLE; Schema: public; Owner: -; Tablespace: -- CREATE TABLE current_way_tags ( @@ -2180,7 +2180,7 @@ CREATE TABLE current_way_tags ( -- --- Name: current_ways; Type: TABLE; Schema: public; Owner: -; Tablespace: +-- Name: current_ways; Type: TABLE; Schema: public; Owner: -; Tablespace: -- CREATE TABLE current_ways ( @@ -2212,7 +2212,7 @@ ALTER SEQUENCE current_ways_id_seq OWNED BY current_ways.id; -- --- Name: diary_comments; Type: TABLE; Schema: public; Owner: -; Tablespace: +-- Name: diary_comments; Type: TABLE; Schema: public; Owner: -; Tablespace: -- CREATE TABLE diary_comments ( @@ -2246,7 +2246,7 @@ ALTER SEQUENCE diary_comments_id_seq OWNED BY diary_comments.id; -- --- Name: diary_entries; Type: TABLE; Schema: public; Owner: -; Tablespace: +-- Name: diary_entries; Type: TABLE; Schema: public; Owner: -; Tablespace: -- CREATE TABLE diary_entries ( @@ -2283,7 +2283,7 @@ ALTER SEQUENCE diary_entries_id_seq OWNED BY diary_entries.id; -- --- Name: friends; Type: TABLE; Schema: public; Owner: -; Tablespace: +-- Name: friends; Type: TABLE; Schema: public; Owner: -; Tablespace: -- CREATE TABLE friends ( @@ -2313,7 +2313,7 @@ ALTER SEQUENCE friends_id_seq OWNED BY friends.id; -- --- Name: gps_points; Type: TABLE; Schema: public; Owner: -; Tablespace: +-- Name: gps_points; Type: TABLE; Schema: public; Owner: -; Tablespace: -- CREATE TABLE gps_points ( @@ -2328,7 +2328,7 @@ CREATE TABLE gps_points ( -- --- Name: gpx_file_tags; Type: TABLE; Schema: public; Owner: -; Tablespace: +-- Name: gpx_file_tags; Type: TABLE; Schema: public; Owner: -; Tablespace: -- CREATE TABLE gpx_file_tags ( @@ -2358,7 +2358,7 @@ ALTER SEQUENCE gpx_file_tags_id_seq OWNED BY gpx_file_tags.id; -- --- Name: gpx_files; Type: TABLE; Schema: public; Owner: -; Tablespace: +-- Name: gpx_files; Type: TABLE; Schema: public; Owner: -; Tablespace: -- CREATE TABLE gpx_files ( @@ -2396,7 +2396,7 @@ ALTER SEQUENCE gpx_files_id_seq OWNED BY gpx_files.id; -- --- Name: languages; Type: TABLE; Schema: public; Owner: -; Tablespace: +-- Name: languages; Type: TABLE; Schema: public; Owner: -; Tablespace: -- CREATE TABLE languages ( @@ -2407,7 +2407,7 @@ CREATE TABLE languages ( -- --- Name: messages; Type: TABLE; Schema: public; Owner: -; Tablespace: +-- Name: messages; Type: TABLE; Schema: public; Owner: -; Tablespace: -- CREATE TABLE messages ( @@ -2443,7 +2443,7 @@ ALTER SEQUENCE messages_id_seq OWNED BY messages.id; -- --- Name: node_tags; Type: TABLE; Schema: public; Owner: -; Tablespace: +-- Name: node_tags; Type: TABLE; Schema: public; Owner: -; Tablespace: -- CREATE TABLE node_tags ( @@ -2455,7 +2455,7 @@ CREATE TABLE node_tags ( -- --- Name: nodes; Type: TABLE; Schema: public; Owner: -; Tablespace: +-- Name: nodes; Type: TABLE; Schema: public; Owner: -; Tablespace: -- CREATE TABLE nodes ( @@ -2472,7 +2472,7 @@ CREATE TABLE nodes ( -- --- Name: oauth_nonces; Type: TABLE; Schema: public; Owner: -; Tablespace: +-- Name: oauth_nonces; Type: TABLE; Schema: public; Owner: -; Tablespace: -- CREATE TABLE oauth_nonces ( @@ -2504,7 +2504,7 @@ ALTER SEQUENCE oauth_nonces_id_seq OWNED BY oauth_nonces.id; -- --- Name: oauth_tokens; Type: TABLE; Schema: public; Owner: -; Tablespace: +-- Name: oauth_tokens; Type: TABLE; Schema: public; Owner: -; Tablespace: -- CREATE TABLE oauth_tokens ( @@ -2549,7 +2549,7 @@ ALTER SEQUENCE oauth_tokens_id_seq OWNED BY oauth_tokens.id; -- --- Name: relation_members; Type: TABLE; Schema: public; Owner: -; Tablespace: +-- Name: relation_members; Type: TABLE; Schema: public; Owner: -; Tablespace: -- CREATE TABLE relation_members ( @@ -2563,7 +2563,7 @@ CREATE TABLE relation_members ( -- --- Name: relation_tags; Type: TABLE; Schema: public; Owner: -; Tablespace: +-- Name: relation_tags; Type: TABLE; Schema: public; Owner: -; Tablespace: -- CREATE TABLE relation_tags ( @@ -2575,7 +2575,7 @@ CREATE TABLE relation_tags ( -- --- Name: relations; Type: TABLE; Schema: public; Owner: -; Tablespace: +-- Name: relations; Type: TABLE; Schema: public; Owner: -; Tablespace: -- CREATE TABLE relations ( @@ -2589,7 +2589,7 @@ CREATE TABLE relations ( -- --- Name: schema_migrations; Type: TABLE; Schema: public; Owner: -; Tablespace: +-- Name: schema_migrations; Type: TABLE; Schema: public; Owner: -; Tablespace: -- CREATE TABLE schema_migrations ( @@ -2598,7 +2598,7 @@ CREATE TABLE schema_migrations ( -- --- Name: sessions; Type: TABLE; Schema: public; Owner: -; Tablespace: +-- Name: sessions; Type: TABLE; Schema: public; Owner: -; Tablespace: -- CREATE TABLE sessions ( @@ -2630,7 +2630,7 @@ ALTER SEQUENCE sessions_id_seq OWNED BY sessions.id; -- --- Name: user_blocks; Type: TABLE; Schema: public; Owner: -; Tablespace: +-- Name: user_blocks; Type: TABLE; Schema: public; Owner: -; Tablespace: -- CREATE TABLE user_blocks ( @@ -2666,7 +2666,7 @@ ALTER SEQUENCE user_blocks_id_seq OWNED BY user_blocks.id; -- --- Name: user_preferences; Type: TABLE; Schema: public; Owner: -; Tablespace: +-- Name: user_preferences; Type: TABLE; Schema: public; Owner: -; Tablespace: -- CREATE TABLE user_preferences ( @@ -2677,7 +2677,7 @@ CREATE TABLE user_preferences ( -- --- Name: user_roles; Type: TABLE; Schema: public; Owner: -; Tablespace: +-- Name: user_roles; Type: TABLE; Schema: public; Owner: -; Tablespace: -- CREATE TABLE user_roles ( @@ -2710,7 +2710,7 @@ ALTER SEQUENCE user_roles_id_seq OWNED BY user_roles.id; -- --- Name: user_tokens; Type: TABLE; Schema: public; Owner: -; Tablespace: +-- Name: user_tokens; Type: TABLE; Schema: public; Owner: -; Tablespace: -- CREATE TABLE user_tokens ( @@ -2742,7 +2742,7 @@ ALTER SEQUENCE user_tokens_id_seq OWNED BY user_tokens.id; -- --- Name: users; Type: TABLE; Schema: public; Owner: -; Tablespace: +-- Name: users; Type: TABLE; Schema: public; Owner: -; Tablespace: -- CREATE TABLE users ( @@ -2792,7 +2792,7 @@ ALTER SEQUENCE users_id_seq OWNED BY users.id; -- --- Name: way_nodes; Type: TABLE; Schema: public; Owner: -; Tablespace: +-- Name: way_nodes; Type: TABLE; Schema: public; Owner: -; Tablespace: -- CREATE TABLE way_nodes ( @@ -2804,7 +2804,7 @@ CREATE TABLE way_nodes ( -- --- Name: way_tags; Type: TABLE; Schema: public; Owner: -; Tablespace: +-- Name: way_tags; Type: TABLE; Schema: public; Owner: -; Tablespace: -- CREATE TABLE way_tags ( @@ -2816,7 +2816,7 @@ CREATE TABLE way_tags ( -- --- Name: ways; Type: TABLE; Schema: public; Owner: -; Tablespace: +-- Name: ways; Type: TABLE; Schema: public; Owner: -; Tablespace: -- CREATE TABLE ways ( @@ -2970,7 +2970,7 @@ ALTER TABLE users ALTER COLUMN id SET DEFAULT nextval('users_id_seq'::regclass); -- --- Name: acls_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: +-- Name: acls_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: -- ALTER TABLE ONLY acls @@ -2978,7 +2978,7 @@ ALTER TABLE ONLY acls -- --- Name: changesets_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: +-- Name: changesets_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: -- ALTER TABLE ONLY changesets @@ -2986,7 +2986,7 @@ ALTER TABLE ONLY changesets -- --- Name: client_applications_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: +-- Name: client_applications_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: -- ALTER TABLE ONLY client_applications @@ -2994,7 +2994,7 @@ ALTER TABLE ONLY client_applications -- --- Name: countries_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: +-- Name: countries_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: -- ALTER TABLE ONLY countries @@ -3002,7 +3002,7 @@ ALTER TABLE ONLY countries -- --- Name: current_node_tags_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: +-- Name: current_node_tags_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: -- ALTER TABLE ONLY current_node_tags @@ -3010,7 +3010,7 @@ ALTER TABLE ONLY current_node_tags -- --- Name: current_nodes_pkey1; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: +-- Name: current_nodes_pkey1; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: -- ALTER TABLE ONLY current_nodes @@ -3018,7 +3018,7 @@ ALTER TABLE ONLY current_nodes -- --- Name: current_relation_members_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: +-- Name: current_relation_members_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: -- ALTER TABLE ONLY current_relation_members @@ -3026,7 +3026,7 @@ ALTER TABLE ONLY current_relation_members -- --- Name: current_relation_tags_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: +-- Name: current_relation_tags_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: -- ALTER TABLE ONLY current_relation_tags @@ -3034,7 +3034,7 @@ ALTER TABLE ONLY current_relation_tags -- --- Name: current_relations_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: +-- Name: current_relations_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: -- ALTER TABLE ONLY current_relations @@ -3042,7 +3042,7 @@ ALTER TABLE ONLY current_relations -- --- Name: current_way_nodes_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: +-- Name: current_way_nodes_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: -- ALTER TABLE ONLY current_way_nodes @@ -3050,7 +3050,7 @@ ALTER TABLE ONLY current_way_nodes -- --- Name: current_way_tags_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: +-- Name: current_way_tags_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: -- ALTER TABLE ONLY current_way_tags @@ -3058,7 +3058,7 @@ ALTER TABLE ONLY current_way_tags -- --- Name: current_ways_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: +-- Name: current_ways_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: -- ALTER TABLE ONLY current_ways @@ -3066,7 +3066,7 @@ ALTER TABLE ONLY current_ways -- --- Name: diary_comments_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: +-- Name: diary_comments_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: -- ALTER TABLE ONLY diary_comments @@ -3074,7 +3074,7 @@ ALTER TABLE ONLY diary_comments -- --- Name: diary_entries_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: +-- Name: diary_entries_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: -- ALTER TABLE ONLY diary_entries @@ -3082,7 +3082,7 @@ ALTER TABLE ONLY diary_entries -- --- Name: friends_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: +-- Name: friends_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: -- ALTER TABLE ONLY friends @@ -3090,7 +3090,7 @@ ALTER TABLE ONLY friends -- --- Name: gpx_file_tags_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: +-- Name: gpx_file_tags_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: -- ALTER TABLE ONLY gpx_file_tags @@ -3098,7 +3098,7 @@ ALTER TABLE ONLY gpx_file_tags -- --- Name: gpx_files_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: +-- Name: gpx_files_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: -- ALTER TABLE ONLY gpx_files @@ -3106,7 +3106,7 @@ ALTER TABLE ONLY gpx_files -- --- Name: languages_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: +-- Name: languages_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: -- ALTER TABLE ONLY languages @@ -3114,7 +3114,7 @@ ALTER TABLE ONLY languages -- --- Name: messages_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: +-- Name: messages_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: -- ALTER TABLE ONLY messages @@ -3122,7 +3122,7 @@ ALTER TABLE ONLY messages -- --- Name: node_tags_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: +-- Name: node_tags_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: -- ALTER TABLE ONLY node_tags @@ -3130,7 +3130,7 @@ ALTER TABLE ONLY node_tags -- --- Name: nodes_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: +-- Name: nodes_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: -- ALTER TABLE ONLY nodes @@ -3138,7 +3138,7 @@ ALTER TABLE ONLY nodes -- --- Name: oauth_nonces_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: +-- Name: oauth_nonces_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: -- ALTER TABLE ONLY oauth_nonces @@ -3146,7 +3146,7 @@ ALTER TABLE ONLY oauth_nonces -- --- Name: oauth_tokens_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: +-- Name: oauth_tokens_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: -- ALTER TABLE ONLY oauth_tokens @@ -3154,7 +3154,7 @@ ALTER TABLE ONLY oauth_tokens -- --- Name: relation_members_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: +-- Name: relation_members_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: -- ALTER TABLE ONLY relation_members @@ -3162,7 +3162,7 @@ ALTER TABLE ONLY relation_members -- --- Name: relation_tags_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: +-- Name: relation_tags_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: -- ALTER TABLE ONLY relation_tags @@ -3170,7 +3170,7 @@ ALTER TABLE ONLY relation_tags -- --- Name: relations_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: +-- Name: relations_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: -- ALTER TABLE ONLY relations @@ -3178,7 +3178,7 @@ ALTER TABLE ONLY relations -- --- Name: sessions_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: +-- Name: sessions_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: -- ALTER TABLE ONLY sessions @@ -3186,7 +3186,7 @@ ALTER TABLE ONLY sessions -- --- Name: user_blocks_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: +-- Name: user_blocks_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: -- ALTER TABLE ONLY user_blocks @@ -3194,7 +3194,7 @@ ALTER TABLE ONLY user_blocks -- --- Name: user_preferences_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: +-- Name: user_preferences_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: -- ALTER TABLE ONLY user_preferences @@ -3202,7 +3202,7 @@ ALTER TABLE ONLY user_preferences -- --- Name: user_roles_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: +-- Name: user_roles_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: -- ALTER TABLE ONLY user_roles @@ -3210,7 +3210,7 @@ ALTER TABLE ONLY user_roles -- --- Name: user_tokens_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: +-- Name: user_tokens_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: -- ALTER TABLE ONLY user_tokens @@ -3218,7 +3218,7 @@ ALTER TABLE ONLY user_tokens -- --- Name: users_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: +-- Name: users_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: -- ALTER TABLE ONLY users @@ -3226,7 +3226,7 @@ ALTER TABLE ONLY users -- --- Name: way_nodes_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: +-- Name: way_nodes_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: -- ALTER TABLE ONLY way_nodes @@ -3234,7 +3234,7 @@ ALTER TABLE ONLY way_nodes -- --- Name: way_tags_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: +-- Name: way_tags_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: -- ALTER TABLE ONLY way_tags @@ -3242,7 +3242,7 @@ ALTER TABLE ONLY way_tags -- --- Name: ways_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: +-- Name: ways_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: -- ALTER TABLE ONLY ways @@ -3250,406 +3250,406 @@ ALTER TABLE ONLY ways -- --- Name: acls_k_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: +-- Name: acls_k_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: -- CREATE INDEX acls_k_idx ON acls USING btree (k); -- --- Name: changeset_tags_id_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: +-- Name: changeset_tags_id_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: -- CREATE INDEX changeset_tags_id_idx ON changeset_tags USING btree (changeset_id); -- --- Name: changesets_bbox_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: +-- Name: changesets_bbox_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: -- CREATE INDEX changesets_bbox_idx ON changesets USING gist (min_lat, max_lat, min_lon, max_lon); -- --- Name: changesets_closed_at_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: +-- Name: changesets_closed_at_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: -- CREATE INDEX changesets_closed_at_idx ON changesets USING btree (closed_at); -- --- Name: changesets_created_at_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: +-- Name: changesets_created_at_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: -- CREATE INDEX changesets_created_at_idx ON changesets USING btree (created_at); -- --- Name: changesets_user_id_created_at_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: +-- Name: changesets_user_id_created_at_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: -- CREATE INDEX changesets_user_id_created_at_idx ON changesets USING btree (user_id, created_at); -- --- Name: changesets_user_id_id_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: +-- Name: changesets_user_id_id_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: -- CREATE INDEX changesets_user_id_id_idx ON changesets USING btree (user_id, id); -- --- Name: countries_code_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: +-- Name: countries_code_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: -- CREATE UNIQUE INDEX countries_code_idx ON countries USING btree (code); -- --- Name: current_nodes_tile_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: +-- Name: current_nodes_tile_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: -- CREATE INDEX current_nodes_tile_idx ON current_nodes USING btree (tile); -- --- Name: current_nodes_timestamp_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: +-- Name: current_nodes_timestamp_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: -- CREATE INDEX current_nodes_timestamp_idx ON current_nodes USING btree ("timestamp"); -- --- Name: current_relation_members_member_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: +-- Name: current_relation_members_member_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: -- CREATE INDEX current_relation_members_member_idx ON current_relation_members USING btree (member_type, member_id); -- --- Name: current_relation_tags_id_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: +-- Name: current_relation_tags_id_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: -- CREATE INDEX current_relation_tags_id_idx ON current_relation_tags USING btree (relation_id); -- --- Name: current_relation_tags_v_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: +-- Name: current_relation_tags_v_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: -- CREATE INDEX current_relation_tags_v_idx ON current_relation_tags USING btree (v); -- --- Name: current_relations_timestamp_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: +-- Name: current_relations_timestamp_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: -- CREATE INDEX current_relations_timestamp_idx ON current_relations USING btree ("timestamp"); -- --- Name: current_way_nodes_node_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: +-- Name: current_way_nodes_node_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: -- CREATE INDEX current_way_nodes_node_idx ON current_way_nodes USING btree (node_id); -- --- Name: current_way_tags_id_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: +-- Name: current_way_tags_id_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: -- CREATE INDEX current_way_tags_id_idx ON current_way_tags USING btree (way_id); -- --- Name: current_way_tags_v_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: +-- Name: current_way_tags_v_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: -- CREATE INDEX current_way_tags_v_idx ON current_way_tags USING btree (v); -- --- Name: current_ways_timestamp_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: +-- Name: current_ways_timestamp_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: -- CREATE INDEX current_ways_timestamp_idx ON current_ways USING btree ("timestamp"); -- --- Name: diary_comment_user_id_created_at_index; Type: INDEX; Schema: public; Owner: -; Tablespace: +-- Name: diary_comment_user_id_created_at_index; Type: INDEX; Schema: public; Owner: -; Tablespace: -- CREATE INDEX diary_comment_user_id_created_at_index ON diary_comments USING btree (user_id, created_at); -- --- Name: diary_comments_entry_id_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: +-- Name: diary_comments_entry_id_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: -- CREATE UNIQUE INDEX diary_comments_entry_id_idx ON diary_comments USING btree (diary_entry_id, id); -- --- Name: diary_entry_created_at_index; Type: INDEX; Schema: public; Owner: -; Tablespace: +-- Name: diary_entry_created_at_index; Type: INDEX; Schema: public; Owner: -; Tablespace: -- CREATE INDEX diary_entry_created_at_index ON diary_entries USING btree (created_at); -- --- Name: diary_entry_language_code_created_at_index; Type: INDEX; Schema: public; Owner: -; Tablespace: +-- Name: diary_entry_language_code_created_at_index; Type: INDEX; Schema: public; Owner: -; Tablespace: -- CREATE INDEX diary_entry_language_code_created_at_index ON diary_entries USING btree (language_code, created_at); -- --- Name: diary_entry_user_id_created_at_index; Type: INDEX; Schema: public; Owner: -; Tablespace: +-- Name: diary_entry_user_id_created_at_index; Type: INDEX; Schema: public; Owner: -; Tablespace: -- CREATE INDEX diary_entry_user_id_created_at_index ON diary_entries USING btree (user_id, created_at); -- --- Name: friends_user_id_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: +-- Name: friends_user_id_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: -- CREATE INDEX friends_user_id_idx ON friends USING btree (user_id); -- --- Name: gpx_file_tags_gpxid_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: +-- Name: gpx_file_tags_gpxid_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: -- CREATE INDEX gpx_file_tags_gpxid_idx ON gpx_file_tags USING btree (gpx_id); -- --- Name: gpx_file_tags_tag_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: +-- Name: gpx_file_tags_tag_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: -- CREATE INDEX gpx_file_tags_tag_idx ON gpx_file_tags USING btree (tag); -- --- Name: gpx_files_timestamp_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: +-- Name: gpx_files_timestamp_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: -- CREATE INDEX gpx_files_timestamp_idx ON gpx_files USING btree ("timestamp"); -- --- Name: gpx_files_user_id_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: +-- Name: gpx_files_user_id_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: -- CREATE INDEX gpx_files_user_id_idx ON gpx_files USING btree (user_id); -- --- Name: gpx_files_visible_visibility_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: +-- Name: gpx_files_visible_visibility_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: -- CREATE INDEX gpx_files_visible_visibility_idx ON gpx_files USING btree (visible, visibility); -- --- Name: index_client_applications_on_key; Type: INDEX; Schema: public; Owner: -; Tablespace: +-- Name: index_client_applications_on_key; Type: INDEX; Schema: public; Owner: -; Tablespace: -- CREATE UNIQUE INDEX index_client_applications_on_key ON client_applications USING btree (key); -- --- Name: index_oauth_nonces_on_nonce_and_timestamp; Type: INDEX; Schema: public; Owner: -; Tablespace: +-- Name: index_oauth_nonces_on_nonce_and_timestamp; Type: INDEX; Schema: public; Owner: -; Tablespace: -- CREATE UNIQUE INDEX index_oauth_nonces_on_nonce_and_timestamp ON oauth_nonces USING btree (nonce, "timestamp"); -- --- Name: index_oauth_tokens_on_token; Type: INDEX; Schema: public; Owner: -; Tablespace: +-- Name: index_oauth_tokens_on_token; Type: INDEX; Schema: public; Owner: -; Tablespace: -- CREATE UNIQUE INDEX index_oauth_tokens_on_token ON oauth_tokens USING btree (token); -- --- Name: index_user_blocks_on_user_id; Type: INDEX; Schema: public; Owner: -; Tablespace: +-- Name: index_user_blocks_on_user_id; Type: INDEX; Schema: public; Owner: -; Tablespace: -- CREATE INDEX index_user_blocks_on_user_id ON user_blocks USING btree (user_id); -- --- Name: messages_from_user_id_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: +-- Name: messages_from_user_id_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: -- CREATE INDEX messages_from_user_id_idx ON messages USING btree (from_user_id); -- --- Name: messages_to_user_id_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: +-- Name: messages_to_user_id_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: -- CREATE INDEX messages_to_user_id_idx ON messages USING btree (to_user_id); -- --- Name: nodes_changeset_id_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: +-- Name: nodes_changeset_id_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: -- CREATE INDEX nodes_changeset_id_idx ON nodes USING btree (changeset_id); -- --- Name: nodes_tile_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: +-- Name: nodes_tile_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: -- CREATE INDEX nodes_tile_idx ON nodes USING btree (tile); -- --- Name: nodes_timestamp_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: +-- Name: nodes_timestamp_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: -- CREATE INDEX nodes_timestamp_idx ON nodes USING btree ("timestamp"); -- --- Name: nodes_uid_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: +-- Name: nodes_uid_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: -- CREATE INDEX nodes_uid_idx ON nodes USING btree (node_id); -- --- Name: points_gpxid_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: +-- Name: points_gpxid_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: -- CREATE INDEX points_gpxid_idx ON gps_points USING btree (gpx_id); -- --- Name: points_tile_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: +-- Name: points_tile_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: -- CREATE INDEX points_tile_idx ON gps_points USING btree (tile); -- --- Name: relation_members_member_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: +-- Name: relation_members_member_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: -- CREATE INDEX relation_members_member_idx ON relation_members USING btree (member_type, member_id); -- --- Name: relation_tags_id_version_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: +-- Name: relation_tags_id_version_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: -- CREATE INDEX relation_tags_id_version_idx ON relation_tags USING btree (relation_id, version); -- --- Name: relations_changeset_id_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: +-- Name: relations_changeset_id_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: -- CREATE INDEX relations_changeset_id_idx ON relations USING btree (changeset_id); -- --- Name: relations_timestamp_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: +-- Name: relations_timestamp_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: -- CREATE INDEX relations_timestamp_idx ON relations USING btree ("timestamp"); -- --- Name: sessions_session_id_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: +-- Name: sessions_session_id_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: -- CREATE UNIQUE INDEX sessions_session_id_idx ON sessions USING btree (session_id); -- --- Name: unique_schema_migrations; Type: INDEX; Schema: public; Owner: -; Tablespace: +-- Name: unique_schema_migrations; Type: INDEX; Schema: public; Owner: -; Tablespace: -- CREATE UNIQUE INDEX unique_schema_migrations ON schema_migrations USING btree (version); -- --- Name: user_id_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: +-- Name: user_id_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: -- CREATE INDEX user_id_idx ON friends USING btree (friend_user_id); -- --- Name: user_openid_url_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: +-- Name: user_openid_url_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: -- CREATE UNIQUE INDEX user_openid_url_idx ON users USING btree (openid_url); -- --- Name: user_roles_id_role_unique; Type: INDEX; Schema: public; Owner: -; Tablespace: +-- Name: user_roles_id_role_unique; Type: INDEX; Schema: public; Owner: -; Tablespace: -- CREATE UNIQUE INDEX user_roles_id_role_unique ON user_roles USING btree (user_id, role); -- --- Name: user_tokens_token_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: +-- Name: user_tokens_token_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: -- CREATE UNIQUE INDEX user_tokens_token_idx ON user_tokens USING btree (token); -- --- Name: user_tokens_user_id_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: +-- Name: user_tokens_user_id_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: -- CREATE INDEX user_tokens_user_id_idx ON user_tokens USING btree (user_id); -- --- Name: users_display_name_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: +-- Name: users_display_name_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: -- CREATE UNIQUE INDEX users_display_name_idx ON users USING btree (display_name); -- --- Name: users_email_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: +-- Name: users_email_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: -- CREATE UNIQUE INDEX users_email_idx ON users USING btree (email); -- --- Name: way_nodes_node_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: +-- Name: way_nodes_node_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: -- CREATE INDEX way_nodes_node_idx ON way_nodes USING btree (node_id); -- --- Name: way_tags_id_version_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: +-- Name: way_tags_id_version_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: -- CREATE INDEX way_tags_id_version_idx ON way_tags USING btree (way_id, version); -- --- Name: ways_changeset_id_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: +-- Name: ways_changeset_id_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: -- CREATE INDEX ways_changeset_id_idx ON ways USING btree (changeset_id); -- --- Name: ways_timestamp_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: +-- Name: ways_timestamp_idx; Type: INDEX; Schema: public; Owner: -; Tablespace: -- CREATE INDEX ways_timestamp_idx ON ways USING btree ("timestamp"); diff --git a/server/tools/osmosis/script/contrib/apidb_0.6_osmosis_xid_indexing.sql b/server/tools/osmosis/script/contrib/apidb_0.6_osmosis_xid_indexing.sql index b493df7b17..2d37fdbeef 100644 --- a/server/tools/osmosis/script/contrib/apidb_0.6_osmosis_xid_indexing.sql +++ b/server/tools/osmosis/script/contrib/apidb_0.6_osmosis_xid_indexing.sql @@ -11,9 +11,9 @@ $BODY$ IF tl >= 2147483648 THEN tl := tl - 4294967296; END IF; - + ti := tl; - + RETURN ti; END; $BODY$ diff --git a/server/tools/osmosis/script/contrib/replicate_osm_file.sh b/server/tools/osmosis/script/contrib/replicate_osm_file.sh index bfa9546758..b1f5370000 100755 --- a/server/tools/osmosis/script/contrib/replicate_osm_file.sh +++ b/server/tools/osmosis/script/contrib/replicate_osm_file.sh @@ -27,11 +27,10 @@ STATUS=$? # Verify that osmosis ran successfully. if [ "$STATUS" -ne "0" ]; then - + echo "Osmosis failed, aborting." exit $STATUS - + fi mv $TEMP_OSM_FILE $OSM_FILE - diff --git a/server/tools/osmosis/script/fix_line_endings.sh b/server/tools/osmosis/script/fix_line_endings.sh index 60a9e1a66c..af57ee1163 100755 --- a/server/tools/osmosis/script/fix_line_endings.sh +++ b/server/tools/osmosis/script/fix_line_endings.sh @@ -11,4 +11,3 @@ find . -iname "*.xml" -exec svn propset svn:eol-style native '{}' \; find . -iname "*.txt" -exec svn propset svn:eol-style native '{}' \; find . -iname "*.osm" -exec svn propset svn:eol-style native '{}' \; find . -iname "*.osc" -exec svn propset svn:eol-style native '{}' \; - diff --git a/server/tools/osmosis/script/munin/osm_replication_lag b/server/tools/osmosis/script/munin/osm_replication_lag index fc004cfc00..d04c4bb57e 100755 --- a/server/tools/osmosis/script/munin/osm_replication_lag +++ b/server/tools/osmosis/script/munin/osm_replication_lag @@ -21,10 +21,10 @@ if [ "$1" = "config" ]; then echo 'graph_args --base 1000' echo 'graph_vlabel seconds behind main database' echo 'graph_category osm' - + echo 'lag.label replication lag' echo 'lag.draw LINE' - + exit 0 fi diff --git a/server/tools/osmosis/script/pgsnapshot_load_0.6.sql b/server/tools/osmosis/script/pgsnapshot_load_0.6.sql index afc9cb7920..dbd5b1f4a8 100644 --- a/server/tools/osmosis/script/pgsnapshot_load_0.6.sql +++ b/server/tools/osmosis/script/pgsnapshot_load_0.6.sql @@ -70,7 +70,7 @@ CREATE INDEX idx_ways_linestring ON ways USING gist (linestring); ALTER TABLE ONLY ways CLUSTER ON idx_ways_bbox; ALTER TABLE ONLY ways CLUSTER ON idx_ways_linestring; --- Optional: CLUSTER imported tables. CLUSTER takes a significant amount of time to run and a +-- Optional: CLUSTER imported tables. CLUSTER takes a significant amount of time to run and a -- significant amount of free disk space but speeds up some queries. --CLUSTER nodes; diff --git a/server/tools/osmosis/script/pgsnapshot_schema_0.6_upgrade_5-6.sql b/server/tools/osmosis/script/pgsnapshot_schema_0.6_upgrade_5-6.sql index e9e54fbc0d..e7354e0032 100644 --- a/server/tools/osmosis/script/pgsnapshot_schema_0.6_upgrade_5-6.sql +++ b/server/tools/osmosis/script/pgsnapshot_schema_0.6_upgrade_5-6.sql @@ -11,10 +11,10 @@ BEGIN SET enable_seqscan = false; SET enable_mergejoin = false; SET enable_hashjoin = false; - + FOR tagRow IN SELECT * FROM node_tags ORDER BY node_id LOOP currentId := tagRow.node_id; - + IF currentId <> previousId THEN IF previousId IS NOT NULL THEN IF result IS NOT NULL THEN @@ -26,16 +26,15 @@ BEGIN END IF; END IF; END IF; - IF result IS NULL THEN result := tagRow.k => tagRow.v; ELSE result := result || (tagRow.k => tagRow.v); END IF; - + previousId := currentId; END LOOP; - + IF previousId IS NOT NULL THEN IF result IS NOT NULL THEN UPDATE nodes SET tags = result WHERE id = previousId; @@ -55,10 +54,10 @@ BEGIN SET enable_seqscan = false; SET enable_mergejoin = false; SET enable_hashjoin = false; - + FOR tagRow IN SELECT * FROM way_tags ORDER BY way_id LOOP currentId := tagRow.way_id; - + IF currentId <> previousId THEN IF previousId IS NOT NULL THEN IF result IS NOT NULL THEN @@ -70,16 +69,15 @@ BEGIN END IF; END IF; END IF; - IF result IS NULL THEN result := tagRow.k => tagRow.v; ELSE result := result || (tagRow.k => tagRow.v); END IF; - + previousId := currentId; END LOOP; - + IF previousId IS NOT NULL THEN IF result IS NOT NULL THEN UPDATE ways SET tags = result WHERE id = previousId; @@ -99,10 +97,10 @@ BEGIN SET enable_seqscan = false; SET enable_mergejoin = false; SET enable_hashjoin = false; - + FOR tagRow IN SELECT * FROM relation_tags ORDER BY relation_id LOOP currentId := tagRow.relation_id; - + IF currentId <> previousId THEN IF previousId IS NOT NULL THEN IF result IS NOT NULL THEN @@ -114,16 +112,15 @@ BEGIN END IF; END IF; END IF; - IF result IS NULL THEN result := tagRow.k => tagRow.v; ELSE result := result || (tagRow.k => tagRow.v); END IF; - + previousId := currentId; END LOOP; - + IF previousId IS NOT NULL THEN IF result IS NOT NULL THEN UPDATE relations SET tags = result WHERE id = previousId; @@ -143,10 +140,10 @@ BEGIN SET enable_seqscan = false; SET enable_mergejoin = false; SET enable_hashjoin = false; - + FOR wayNodeRow IN SELECT * FROM way_nodes ORDER BY way_id, sequence_id LOOP currentId := wayNodeRow.way_id; - + IF currentId <> previousId THEN IF previousId IS NOT NULL THEN IF result IS NOT NULL THEN @@ -158,16 +155,15 @@ BEGIN END IF; END IF; END IF; - IF result IS NULL THEN result = ARRAY[wayNodeRow.node_id]; ELSE result = array_append(result, wayNodeRow.node_id); END IF; - + previousId := currentId; END LOOP; - + IF previousId IS NOT NULL THEN IF result IS NOT NULL THEN UPDATE ways SET nodes = result WHERE id = previousId; @@ -210,7 +206,7 @@ SELECT build_way_nodes(); -- Remove the way nodes function. DROP FUNCTION build_way_nodes(); --- Organise data according to geographical location. +-- Organise data according to geographical location. CLUSTER nodes USING idx_nodes_geom; CLUSTER ways USING idx_ways_linestring; diff --git a/tests/server/helpers/test_files/non_square_split_results.json b/tests/server/helpers/test_files/non_square_split_results.json index 3ab639aa2e..1308007fdc 100644 --- a/tests/server/helpers/test_files/non_square_split_results.json +++ b/tests/server/helpers/test_files/non_square_split_results.json @@ -1 +1 @@ -[{"geometry": {"coordinates": [[[[152.578124972669, -31.9521622328954], [151.874999972795, -31.9521622328954], [151.874999972795, -31.3536369364459], [152.578124972669, -31.3536369364459], [152.578124972669, -31.9521622328954]]]], "type": "MultiPolygon"}, "properties": {"isSquare": false, "x": 472, "y": 208, "zoom": 9}, "type": "Feature"}, {"geometry": {"coordinates": [[[[151.874999972795, -31.3536369364459], [151.874999972795, -30.7512777712788], [152.578124972669, -30.7512777712788], [152.578124972669, -31.3536369364459], [151.874999972795, -31.3536369364459]]]], "type": "MultiPolygon"}, "properties": {"isSquare": false, "x": 472, "y": 209, "zoom": 9}, "type": "Feature"}, {"geometry": {"coordinates": [[[[152.94452533106718, -31.3536369364459], [152.94092667, -31.360552708], [152.949562214, -31.445384196], [152.905274711, -31.47718046], [152.857738766, -31.573415644], [152.804260827, -31.669551593], [152.732956909, -31.816093707], [152.673536977, -31.881709671], [152.595147099906, -31.9521622328954], [152.578124972669, -31.9521622328954], [152.578124972669, -31.3536369364459], [152.94452533106718, -31.3536369364459]]]], "type": "MultiPolygon"}, "properties": {"isSquare": false, "x": 473, "y": 208, "zoom": 9}, "type": "Feature"}, {"geometry": {"coordinates": [[[[153.002629854192, -30.7512777712788], [153.012230588, -30.800769981], [153.041940554, -30.8568964], [153.0894765, -30.912989989], [153.047882548, -30.994521987], [153.041940554, -31.065805365], [153.000346602, -31.126862915], [152.964694643, -31.223457045], [152.964694643, -31.314876311], [152.94452533106718, -31.3536369364459], [152.578124972669, -31.3536369364459], [152.578124972669, -30.7512777712788], [153.002629854192, -30.7512777712788]]]], "type": "MultiPolygon"}, "properties": {"isSquare": false, "x": 473, "y": 209, "zoom": 9}, "type": "Feature"}] \ No newline at end of file +[{"geometry": {"coordinates": [[[[152.401607946321, -31.9521622328954], [151.874999972795, -31.9521622328954], [151.874999972795, -31.3112796266923], [152.401607946321, -31.3112796266923], [152.401607946321, -31.9521622328954]]]], "type": "MultiPolygon"}, "properties": {"isSquare": false, "x": null, "y": null, "zoom": null}, "type": "Feature"}, {"geometry": {"coordinates": [[[[151.874999972795, -31.3112796266923], [151.874999972795, -30.7512777712788], [152.401607946321, -30.7512777712788], [152.401607946321, -31.3112796266923], [151.874999972795, -31.3112796266923]]]], "type": "MultiPolygon"}, "properties": {"isSquare": false, "x": null, "y": null, "zoom": null}, "type": "Feature"}, {"geometry": {"coordinates": [[[[152.964694643, -31.3112796266923], [152.964694643, -31.314876311], [152.94092667, -31.360552708], [152.949562214, -31.445384196], [152.905274711, -31.47718046], [152.857738766, -31.573415644], [152.804260827, -31.669551593], [152.732956909, -31.816093707], [152.673536977, -31.881709671], [152.595147099906, -31.9521622328954], [152.401607946321, -31.9521622328954], [152.401607946321, -31.3112796266923], [152.964694643, -31.3112796266923]]]], "type": "MultiPolygon"}, "properties": {"isSquare": false, "x": null, "y": null, "zoom": null}, "type": "Feature"}, {"geometry": {"coordinates": [[[[153.002629854192, -30.7512777712788], [153.012230588, -30.800769981], [153.041940554, -30.8568964], [153.0894765, -30.912989989], [153.047882548, -30.994521987], [153.041940554, -31.065805365], [153.000346602, -31.126862915], [152.964694643, -31.223457045], [152.964694643, -31.3112796266923], [152.401607946321, -31.3112796266923], [152.401607946321, -30.7512777712788], [153.002629854192, -30.7512777712788]]]], "type": "MultiPolygon"}, "properties": {"isSquare": false, "x": null, "y": null, "zoom": null}, "type": "Feature"}] diff --git a/tests/server/integration/services/grid/test_split_service.py b/tests/server/integration/services/grid/test_split_service.py index 43e01e1f76..3286613e00 100644 --- a/tests/server/integration/services/grid/test_split_service.py +++ b/tests/server/integration/services/grid/test_split_service.py @@ -1,5 +1,7 @@ import json import os +from shapely.geometry import Polygon, MultiPolygon +from geoalchemy2 import shape import unittest from unittest.mock import patch @@ -18,6 +20,7 @@ class TestSplitService(unittest.TestCase): skip_tests = False test_project = None test_user = None + maxDiff = None @classmethod def setUpClass(cls): @@ -54,10 +57,13 @@ def test_split_geom_returns_split_geometries(self): x = 1010 y = 1399 zoom = 11 + task_stub = Task() + task_stub.is_square = True + expected = geojson.loads(json.dumps(get_canned_json('split_task.json'))) # act - result = SplitService._create_split_tasks(x, y, zoom, None) + result = SplitService._create_split_tasks(x, y, zoom, task_stub) # assert self.assertEqual(str(expected), str(result)) @@ -66,7 +72,9 @@ def test_split_geom_raise_grid_service_error_when_task_not_usable(self): if self.skip_tests: return with self.assertRaises(SplitServiceError): - SplitService._create_split_tasks("foo", "bar", "dum", None) + task_stub = Task() + task_stub.is_square = True + SplitService._create_split_tasks("foo", "bar", "dum", task_stub) @patch.object(Task, 'get_per_task_instructions') @patch.object(Project, 'tasks') @@ -90,9 +98,10 @@ def test_split_task_helper(self, mock_task_get, mock_task_get_max_task_id_for_pr task_stub.locked_by = 1234 task_stub.lock_holder = 1234 task_stub.is_square = True - task_stub.x = 1010 - task_stub.y = 1399 - task_stub.zoom = 11 + task_stub.x = 16856 + task_stub.y = 17050 + task_stub.zoom = 15 + task_stub.geometry = shape.from_shape(Polygon([(5.1855468740711421, 7.2970875628719796), (5.1855468740711421, 7.3079847788619219), (5.1965332021941588, 7.3079847788619219), (5.1965332021941588, 7.2970875628719796), (5.1855468740711421, 7.2970875628719796)])) mock_task_get.return_value = task_stub mock_task_get_max_task_id_for_project.return_value = 1 mock_project_get.return_value = Project() @@ -127,6 +136,3 @@ def test_split_non_square_task(self, mock_task): result = SplitService._create_split_tasks(task.x, task.y, task.zoom, task) self.assertEqual(str(expected), str(result)) - - -