├── .travis.yml ├── forked └── naughty-words │ ├── fr-CA-u-sd-caqc.json │ ├── tlh.json │ ├── no.json │ ├── da.json │ ├── th.json │ ├── ar.json │ ├── eo.json │ ├── fa.json │ ├── sv.json │ ├── cs.json │ ├── ko.json │ ├── pl.json │ ├── es.json │ ├── de.json │ ├── index.js │ ├── pt.json │ ├── USERS.md │ ├── fr.json │ ├── hu.json │ ├── hi.json │ ├── README.md │ ├── fi.json │ ├── ja.json │ ├── tr.json │ └── ru.json ├── .gitignore ├── comparators ├── .DS_Store ├── example.js ├── new_user_large_building.js ├── place_feature_deleted.js ├── new_user.js ├── new_user_pokemon_nest.js ├── deprecated_construction_proposal_tag.js ├── wikidata_wikipedia_tag_deleted.js ├── disputed_border_deleted.js ├── modified_monument.js ├── very_long_name.js ├── missing_primary_tag.js ├── place_type_change.js ├── deleted_address.js ├── null_island.js ├── name_ref_changes.js ├── added_website.js ├── added_place.js ├── new_user_park.js ├── rare_critical_feature_created.js ├── disputed_border_tag_changed.js ├── motorway_trunk_geometry_changed.js ├── irrelevant_tags_on_highway.js ├── large_building.js ├── new_user_motorway.js ├── new_user_footway.js ├── impossible_angles.js ├── wikidata_distance_with_osm.js ├── new_user_motorway_deleted.js ├── pokemon_edits.js ├── invalid_name.js ├── common_tag_values.js ├── major_name_modification.js ├── destination_ref_changed.js ├── invalid_tag_combination.js ├── new_user_water.js ├── water_feature_by_new_user.js ├── invalid_tag_modification.js ├── dragged_highway_waterway.js ├── path_road_changed.js ├── major_lake_modified.js ├── osm_landmarks.js ├── invalid_highway_tags.js ├── place_name_changed.js ├── profanity.js ├── major_road_changed.js ├── name_modified.js ├── straight_segment.js ├── modified_survey_point.js ├── modified_place_wikidata.js ├── name_unmatches_with_wikidata.js ├── place_edited.js ├── tag-combinations.csv └── wrong_turn_restriction.js ├── tests ├── basic_fixture.pbf ├── result_feature_overlap.pbf ├── fixtures │ ├── example.json │ ├── straight_segment.json │ ├── common_tag_values.json │ ├── deprecated_construction_proposal_tag.json │ ├── features │ │ ├── way-67715300-10.json │ │ ├── node-268436671-13.json │ │ ├── node-158230039-14.json │ │ └── way-110041164-3.json │ ├── rare_critical_feature_created.json │ ├── feature_overlap.json │ ├── null_island.json │ ├── invalid_tag_combination.json │ ├── new_user_footway.json │ ├── wikidata_wikipedia_tag_deleted.json │ ├── invalid_name.json │ ├── new_user_park.json │ ├── place_name_changed.json │ ├── place_feature_deleted.json │ ├── new_user_motorway.json │ ├── new_user_pokemon_nest.json │ ├── new_user_water.json │ ├── new_user_motorway_deleted.json │ ├── destination_ref_changed.json │ ├── new_user_large_building.json │ ├── common.json │ ├── motorway_trunk_geometry_changed.json │ ├── name_ref_changes.json │ ├── name_unmatches_with_wikidata.json │ ├── modified_place_wikidata.json │ ├── profanity.json │ ├── added_place.json │ ├── very_long_name.json │ ├── missing_primary_tag.json │ └── place_type_change.json ├── deprecated_construction_proposal_tag.json ├── common.js └── test_compare_function.js ├── config └── priority_languages.json ├── requirements.txt ├── scripts ├── download_landmarks.sh ├── download_common_tag_values.sh ├── download_common_tags.py ├── save-water │ ├── extract.js │ ├── index.js │ └── get-lakes.js ├── convert_user_blocks_to_csv.py ├── primary-map-features-analysis.js ├── run_compare_function.js ├── primary-map-features.js └── download_user_blocks.py ├── .eslintrc ├── data ├── low-zoom-features.js └── priority_areas.geojson ├── lib ├── version.js ├── primary_tags.js ├── park.js ├── wrapsync.js ├── names.js ├── self_intersecting.js ├── important_place.js ├── pokemon_nest.js ├── changed_tag.js ├── large_building.js ├── get_vector_tile_features.js ├── important.js ├── impossible_angle.js └── compare_all.js ├── LICENSE.txt ├── fun └── word_cloud_user_blocks.py ├── example └── example.js ├── README.md └── package.json /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 12 4 | - 14 5 | -------------------------------------------------------------------------------- /forked/naughty-words/fr-CA-u-sd-caqc.json: -------------------------------------------------------------------------------- 1 | [ 2 | "noune" 3 | ] 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.log 3 | *.spatialite 4 | *.DS_Store 5 | .nyc_output/* 6 | -------------------------------------------------------------------------------- /forked/naughty-words/tlh.json: -------------------------------------------------------------------------------- 1 | [ 2 | "ghuy'cha'", 3 | "QI'yaH", 4 | "Qu'vatlh" 5 | ] 6 | -------------------------------------------------------------------------------- /comparators/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/osm-compare/HEAD/comparators/.DS_Store -------------------------------------------------------------------------------- /tests/basic_fixture.pbf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/osm-compare/HEAD/tests/basic_fixture.pbf -------------------------------------------------------------------------------- /tests/result_feature_overlap.pbf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mapbox/osm-compare/HEAD/tests/result_feature_overlap.pbf -------------------------------------------------------------------------------- /config/priority_languages.json: -------------------------------------------------------------------------------- 1 | [ 2 | "en", 3 | "es", 4 | "de", 5 | "fr", 6 | "ru", 7 | "zh" 8 | ] 9 | -------------------------------------------------------------------------------- /comparators/example.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = example; 4 | 5 | function example(newVersion, oldVersion, callback) { 6 | callback(null, { 7 | 'result:example': {} 8 | }); 9 | } 10 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | beautifulsoup4==4.4.1 2 | cycler==0.10.0 3 | matplotlib==1.5.1 4 | numpy==1.11.0 5 | Pillow==3.2.0 6 | pyparsing==2.1.4 7 | python-dateutil==2.5.3 8 | pytz==2016.4 9 | requests==2.10.0 10 | six==1.10.0 11 | wordcloud==1.2.1 12 | -------------------------------------------------------------------------------- /scripts/download_landmarks.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | if [ ! -f landmarks.spatialite ]; then 3 | echo "Downloading landmarks database from S3 ..."; 4 | curl -s https://s3.amazonaws.com/vandalism-dynamosm-support/landmarks.spatialite -o landmarks.spatialite 5 | fi -------------------------------------------------------------------------------- /forked/naughty-words/no.json: -------------------------------------------------------------------------------- 1 | [ 2 | "drittsekk", 3 | "faen i helvete", 4 | "fitte", 5 | "jævla", 6 | "kuk", 7 | "kukene", 8 | "kuker", 9 | "nigger", 10 | "pikk", 11 | "sotrør", 12 | "ståpikk", 13 | "ståpikkene", 14 | "ståpikker" 15 | ] 16 | -------------------------------------------------------------------------------- /scripts/download_common_tag_values.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | readonly HIGHWAY_FILE="common_tag_values/highway.json" 3 | 4 | if [ ! -f $HIGHWAY_FILE ]; then 5 | echo "Downloading common tags from taginfo" 6 | curl -s "https://taginfo.openstreetmap.org/api/4/key/values?key=highway" -o "$HIGHWAY_FILE" 7 | fi 8 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "mourner", 3 | "rules": { 4 | "space-before-function-paren": 0, 5 | "indent": [2, 2], 6 | "global-require": 1, 7 | "consistent-return": 0, 8 | "no-unused-vars": 0, 9 | "camelcase": 0, 10 | "valid-jsdoc": 2, 11 | "comma-dangle": ["error", "never"], 12 | "operator-linebreak": [0], 13 | "object-curly-spacing": "warn" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /forked/naughty-words/da.json: -------------------------------------------------------------------------------- 1 | [ 2 | "anus", 3 | "bøsserøv", 4 | "cock", 5 | "fisse", 6 | "fissehår", 7 | "fuck", 8 | "hestepik", 9 | "kussekryller", 10 | "lort", 11 | "luder", 12 | "pik", 13 | "pikhår", 14 | "pikslugeri", 15 | "piksutteri", 16 | "pis", 17 | "røv", 18 | "røvhul", 19 | "røvskæg", 20 | "røvspræke", 21 | "shit" 22 | ] 23 | -------------------------------------------------------------------------------- /scripts/download_common_tags.py: -------------------------------------------------------------------------------- 1 | import json 2 | import requests 3 | 4 | key = 'waterway' 5 | url = 'https://taginfo.openstreetmap.org/api/4/key/values?key={0}&sortname=count&sortorder=desc' 6 | 7 | response = requests.get(url.format(key)) 8 | 9 | common_tags = json.loads(response.text) 10 | results = list() 11 | for common_tag in common_tags['data'][:20]: 12 | results.append(common_tag['value']) 13 | print '\', \''.join(results) 14 | -------------------------------------------------------------------------------- /data/low-zoom-features.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var lowZoomFeatures = { 4 | disputed: ['yes'], 5 | admin_level: [3, 2], 6 | natural: ['water', 'wood'], 7 | waterway: ['riverbank'], 8 | boundary: ['disputed', 'national_park'], 9 | highway: ['motorway', 'trunk'], 10 | place: ['continent', 'ocean', 'sea', 'country', 'state', 'city'], 11 | aeroway: ['aerodrome'], 12 | landuse: ['reservoir'] 13 | }; 14 | 15 | module.exports = lowZoomFeatures; 16 | -------------------------------------------------------------------------------- /comparators/new_user_large_building.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const newUser = require('./new_user'); 3 | const largeBuilding = require('../lib/large_building'); 4 | 5 | const newUserLargeBuilding = (newVersion, oldVersion) => { 6 | if ( 7 | newUser(newVersion, oldVersion) && largeBuilding(newVersion, oldVersion) 8 | ) { 9 | return {'result:new_user_large_building': true}; 10 | } 11 | return false; 12 | }; 13 | 14 | module.exports = newUserLargeBuilding; 15 | -------------------------------------------------------------------------------- /comparators/place_feature_deleted.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const importantPlace = require('../lib/important_place').importantPlace; 3 | 4 | module.exports = placeFeatureDeleted; 5 | 6 | function placeFeatureDeleted(newVersion, oldVersion) { 7 | const isDeleted = !!newVersion.deleted; 8 | const isImportant = importantPlace(newVersion, oldVersion); 9 | 10 | if (isDeleted && isImportant) { 11 | return {'result:place_feature_deleted': true}; 12 | } 13 | return false; 14 | } 15 | -------------------------------------------------------------------------------- /comparators/new_user.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = newUser; 3 | 4 | function newUser(newVersion, oldVersion, opts) { 5 | if (!newVersion || !newVersion.properties) { 6 | return false; 7 | } 8 | opts = Object.assign({maxChangesets: 50}, opts); 9 | 10 | const changesetCount = newVersion.properties['osm:user:changesetcount']; 11 | 12 | if (changesetCount > 0 && changesetCount <= opts.maxChangesets) { 13 | return {'result:new_user': true}; 14 | } 15 | return false; 16 | } 17 | -------------------------------------------------------------------------------- /forked/naughty-words/th.json: -------------------------------------------------------------------------------- 1 | [ 2 | "กระดอ", 3 | "กระเด้า", 4 | "กระหรี่", 5 | "กะปิ", 6 | "กู", 7 | "ขี้", 8 | "ควย", 9 | "จิ๋ม", 10 | "จู๋", 11 | "เจ๊ก", 12 | "เจี๊ยว", 13 | "ดอกทอง", 14 | "ตอแหล", 15 | "ตูด", 16 | "น้ําแตก", 17 | "มึง", 18 | "แม่ง", 19 | "เย็ด", 20 | "รูตูด", 21 | "ล้างตู้เย็น", 22 | "ส้นตีน", 23 | "สัด", 24 | "เสือก", 25 | "หญิงชาติชั่ว", 26 | "หลั่ง", 27 | "ห่า", 28 | "หํา", 29 | "หี", 30 | "เหี้ย", 31 | "อมนกเขา", 32 | "ไอ้ควาย" 33 | ] 34 | -------------------------------------------------------------------------------- /comparators/new_user_pokemon_nest.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const newUser = require('./new_user'); 4 | const pokemonNest = require('../lib/pokemon_nest'); 5 | 6 | const newUserPokemonNest = (newVersion, oldVersion) => { 7 | if (!newVersion || !newVersion.properties) { 8 | return false; 9 | } 10 | 11 | if (newUser(newVersion, oldVersion) && pokemonNest(newVersion, oldVersion)) { 12 | return {'result:new_user_pokemon_nest': true}; 13 | } 14 | return false; 15 | }; 16 | 17 | module.exports = newUserPokemonNest; 18 | -------------------------------------------------------------------------------- /lib/version.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = version; 4 | 5 | function version(newVersion, oldVersion, opts) { 6 | opts = Object.assign( 7 | { 8 | minVersion: 0, 9 | maxVersion: 1 10 | }, 11 | opts 12 | ); 13 | if (opts.maxVersion === 'Infinity') opts.maxVersion = Infinity; 14 | 15 | const currentVersion = newVersion.properties['osm:version']; 16 | if (currentVersion >= opts.minVersion && currentVersion <= opts.maxVersion) { 17 | return true; 18 | } 19 | return false; 20 | } 21 | -------------------------------------------------------------------------------- /comparators/deprecated_construction_proposal_tag.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = deprecated_construction_proposal_tag; 3 | 4 | function deprecated_construction_proposal_tag(newVersion, oldVersion) { 5 | if (newVersion) { 6 | if ( 7 | newVersion.properties && 8 | newVersion.properties.highway && 9 | newVersion.properties.construction && 10 | newVersion.properties.construction === 'yes' 11 | ) { 12 | return { 13 | 'result:deprecated_construction_proposal_tag': true 14 | }; 15 | } 16 | } 17 | return false; 18 | } 19 | -------------------------------------------------------------------------------- /lib/primary_tags.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const primaryTags = [ 4 | 'aerialway', 5 | 'aeroway', 6 | 'amenity', 7 | 'barrier', 8 | 'boundary', 9 | 'building', 10 | 'craft', 11 | 'emergency', 12 | 'geological', 13 | 'highway', 14 | 'historic', 15 | 'landuse', 16 | 'leisure', 17 | 'man_made', 18 | 'military', 19 | 'natural', 20 | 'office', 21 | 'place', 22 | 'power', 23 | 'public_transport', 24 | 'railway', 25 | 'route', 26 | 'shop', 27 | 'sport', 28 | 'tourism', 29 | 'waterway' 30 | ]; 31 | 32 | exports.primaryTags = primaryTags; 33 | -------------------------------------------------------------------------------- /scripts/save-water/extract.js: -------------------------------------------------------------------------------- 1 | module.exports = function (tileLayers, tile, writeData, done) { 2 | var osm = tileLayers.osm.osm; 3 | 4 | var lakes = []; 5 | for (var i = 0; i < osm.features.length; i++) { 6 | var feature = osm.features[i]; 7 | if ((feature.properties['@version'] == 1) && feature.properties.hasOwnProperty('water') && (feature.properties['water'] === 'lake')) { 8 | lakes.push(JSON.stringify(feature)); 9 | } 10 | } 11 | 12 | if (lakes.length) writeData('\n' + lakes.join('\n')); 13 | done(null, null); 14 | }; 15 | -------------------------------------------------------------------------------- /tests/fixtures/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "compareFunction": "example", 3 | "fixtures": [ 4 | { 5 | "description": "Description of this fixture", 6 | "newVersion": {}, 7 | "oldVersion": {}, 8 | "expectedResult": { 9 | "result:example": {} 10 | } 11 | }, 12 | { 13 | "description": "Description of another fixture", 14 | "newVersion": {}, 15 | "oldVersion": {}, 16 | "expectedResult": { 17 | "result:example": {} 18 | } 19 | } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /lib/park.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = park; 4 | 5 | const parkValues = new Set([ 6 | 'recreation_ground', 7 | 'village_green', 8 | 'park', 9 | 'nature_reserve', 10 | 'protected_area', 11 | 'national_park', 12 | 'garden' 13 | ]); 14 | 15 | function isPark(tags) { 16 | return parkValues.has(tags.landuse) || parkValues.has(tags.boundary) || parkValues.has(tags.leisure); 17 | } 18 | 19 | 20 | function park(newVersion, oldVersion) { 21 | if (newVersion && isPark(newVersion.properties)) return true; 22 | if (oldVersion && isPark(oldVersion.properties)) return true; 23 | return false; 24 | } 25 | -------------------------------------------------------------------------------- /forked/naughty-words/ar.json: -------------------------------------------------------------------------------- 1 | [ 2 | "سكس", 3 | "طيز", 4 | "شرج", 5 | "لعق", 6 | "لحس", 7 | "مص", 8 | "تمص", 9 | "بيضان", 10 | "ثدي", 11 | "بز", 12 | "بزاز", 13 | "حلمة", 14 | "مفلقسة", 15 | "بظر", 16 | "كس", 17 | "فرج", 18 | "شهوة", 19 | "شاذ", 20 | "مبادل", 21 | "عاهرة", 22 | "جماع", 23 | "قضيب", 24 | "زب", 25 | "لوطي", 26 | "لواط", 27 | "سحاق", 28 | "سحاقية", 29 | "اغتصاب", 30 | "خنثي", 31 | "احتلام", 32 | "نيك", 33 | "متناك", 34 | "متناكة", 35 | "شرموطة", 36 | "عرص", 37 | "خول", 38 | "قحبة", 39 | "لبوة" 40 | ] 41 | -------------------------------------------------------------------------------- /forked/naughty-words/eo.json: -------------------------------------------------------------------------------- 1 | [ 2 | "bugren", 3 | "bugri", 4 | "bugru", 5 | "ĉiesulino", 6 | "ĉiesulo", 7 | "diofek", 8 | "diofeka", 9 | "fek", 10 | "feken", 11 | "fekfikanto", 12 | "feklekulo", 13 | "fekulo", 14 | "fik", 15 | "fikado", 16 | "fikema", 17 | "fikfek", 18 | "fiki", 19 | "fikiĝi", 20 | "fikiĝu", 21 | "fikilo", 22 | "fikklaŭno", 23 | "fikota", 24 | "fiku", 25 | "forfiki", 26 | "forfikiĝu", 27 | "forfiku", 28 | "forfurzu", 29 | "forpisi", 30 | "forpisu", 31 | "furzulo", 32 | "kacen", 33 | "kaco", 34 | "kacsuĉulo", 35 | "kojono", 36 | "piĉen", 37 | "piĉo", 38 | "zamenfek" 39 | ] 40 | -------------------------------------------------------------------------------- /lib/wrapsync.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Converts a synchronous comparator function into an async one to have consistent interface 5 | * @param {Function} comparator - synchronous function which returns value or throws error 6 | * @returns {Function} A Node style asynchronous function that accepts a callback 7 | */ 8 | var wrapSync = function(comparator) { 9 | return function(newVersion, oldVersion, callback) { 10 | try { 11 | var result = comparator(newVersion, oldVersion); 12 | return callback(null, result); 13 | } catch (err) { 14 | return callback(err); 15 | } 16 | }; 17 | }; 18 | 19 | module.exports = wrapSync; 20 | -------------------------------------------------------------------------------- /comparators/wikidata_wikipedia_tag_deleted.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = wikidata_wikipedia_tag_deleted; 4 | 5 | function wikidata_wikipedia_tag_deleted(newVersion, oldVersion) { 6 | if (newVersion.deleted || !newVersion.properties) return false; 7 | if (!oldVersion || !oldVersion.properties) return false; 8 | 9 | if ( 10 | ('wikidata' in oldVersion.properties && 11 | !('wikidata' in newVersion.properties)) || 12 | ('wikipedia' in oldVersion.properties && 13 | !('wikipedia' in newVersion.properties)) 14 | ) { 15 | return { 16 | 'result:wikidata_wikipedia_tag_deleted': true 17 | }; 18 | } 19 | 20 | return false; 21 | } 22 | -------------------------------------------------------------------------------- /forked/naughty-words/fa.json: -------------------------------------------------------------------------------- 1 | [ 2 | "آب کیر", 3 | "ارگاسم", 4 | "برهنه", 5 | "پورن", 6 | "پورنو", 7 | "تجاوز", 8 | "تخمی", 9 | "جق", 10 | "جقی", 11 | "جلق", 12 | "جنده", 13 | "چوچول", 14 | "حشر", 15 | "حشری", 16 | "داف", 17 | "دودول", 18 | "ساک زدن", 19 | "سکس", 20 | "سکس کردن", 21 | "سکسی", 22 | "سوپر", 23 | "شق کردن", 24 | "شهوت", 25 | "شهوتی", 26 | "شونبول", 27 | "فیلم سوپر", 28 | "کس", 29 | "کس دادن", 30 | "کس کردن", 31 | "کسکش", 32 | "کوس", 33 | "کون", 34 | "کون دادن", 35 | "کون کردن", 36 | "کونکش", 37 | "کونی", 38 | "کیر", 39 | "کیری", 40 | "لاپا", 41 | "لاپایی", 42 | "لاشی", 43 | "لخت", 44 | "لش", 45 | "منی", 46 | "هرزه" 47 | ] 48 | -------------------------------------------------------------------------------- /lib/names.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const langs = require('../config/priority_languages.json'); 3 | const namePattern = /^(int_|short_|)?(name|wikidata:labels)(:.*)?$/; 4 | const isNameKey = key => namePattern.test(key); 5 | const priorityLanguages = new Set(langs); 6 | 7 | function isPrioNameKey(key) { 8 | if (key === 'name') return true; 9 | if (!isNameKey(key)) return false; 10 | const parts = key.split(':'); 11 | if ( 12 | (parts[0] === 'name' || 13 | [parts[0], parts[1]].join(':') === 'wikidata:labels') && 14 | !priorityLanguages.has(parts[parts.length - 1]) 15 | ) 16 | return false; 17 | return true; 18 | } 19 | 20 | exports.isPrioNameKey = isPrioNameKey; 21 | exports.isNameKey = isNameKey; 22 | -------------------------------------------------------------------------------- /comparators/disputed_border_deleted.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = disputedBorderDeleted; 3 | 4 | function disputedBorderDeleted(newVersion, oldVersion) { 5 | if (newVersion.deleted && !oldVersion) { 6 | // None of old version or new Version present 7 | return false; 8 | } 9 | if (newVersion.deleted && oldVersion) { 10 | // Only old Version is present, which indicates feature has been deleted 11 | /* 12 | Comparing the tags 13 | Creating a result object 14 | */ 15 | if (oldVersion.properties && oldVersion.properties['disputed']) { 16 | return { 17 | message: 'Disputed border deleted', 18 | 'result:disputed_border_deleted': true 19 | }; 20 | } 21 | } 22 | 23 | return false; 24 | } 25 | -------------------------------------------------------------------------------- /forked/naughty-words/sv.json: -------------------------------------------------------------------------------- 1 | [ 2 | "arsle", 3 | "brutta", 4 | "discofitta", 5 | "dra åt helvete", 6 | "fan", 7 | "fitta", 8 | "fittig", 9 | "för helvete", 10 | "helvete", 11 | "hård", 12 | "jävlar", 13 | "knulla", 14 | "kuk", 15 | "kuksås", 16 | "kötthuvud", 17 | "köttnacke", 18 | "moona", 19 | "moonade", 20 | "moonar", 21 | "moonat", 22 | "mutta", 23 | "nigger", 24 | "neger", 25 | "olla", 26 | "pippa", 27 | "pitt", 28 | "prutt", 29 | "pök", 30 | "runka", 31 | "röv", 32 | "rövhål", 33 | "rövknulla", 34 | "satan", 35 | "skita", 36 | "skit ner dig", 37 | "skäggbiff", 38 | "snedfitta", 39 | "snefitta", 40 | "stake", 41 | "subba", 42 | "sås", 43 | "sätta på", 44 | "tusan" 45 | ] 46 | -------------------------------------------------------------------------------- /tests/fixtures/straight_segment.json: -------------------------------------------------------------------------------- 1 | { 2 | "compareFunction": "straight_segment", 3 | "fixtures": [ 4 | { 5 | "description": "If the feature that contains out of a single segment exceeding a reasonable length", 6 | "newVersion": { 7 | "type": "Feature", 8 | "properties": { 9 | "highway": "footway", 10 | "osm:version": 4, 11 | "osm:uid": 5017521 12 | }, 13 | "geometry": { 14 | "type": "LineString", 15 | "coordinates": [[-78, 39], [-76, 39]] 16 | } 17 | }, 18 | "oldVersion": null, 19 | "expectedResult": { 20 | "result:straight_segment": true 21 | } 22 | } 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /comparators/modified_monument.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = modifiedMonument; 4 | 5 | function modifiedMonument(newVersion, oldVersion) { 6 | if ( 7 | !newVersion.deleted && 8 | newVersion.properties.hasOwnProperty('historic') && 9 | newVersion.properties.historic === 'monument' && 10 | newVersion.properties['osm:version'] > 10 11 | ) { 12 | return {'result:modified_monument': true}; 13 | } 14 | 15 | if ( 16 | oldVersion && 17 | newVersion.deleted && 18 | oldVersion.properties.hasOwnProperty('historic') && 19 | oldVersion.properties.historic === 'monument' && 20 | oldVersion.properties['osm:version'] > 10 21 | ) { 22 | return {'result:modified_monument': true}; 23 | } 24 | return false; 25 | } 26 | -------------------------------------------------------------------------------- /lib/self_intersecting.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const lineintersect = require('@turf/line-intersect'); 3 | 4 | function selfIntersectingWays(newVersion) { 5 | if ( 6 | newVersion && 7 | newVersion.geometry && 8 | newVersion.geometry.type === 'LineString' 9 | ) { 10 | const coords = newVersion.geometry.coordinates; 11 | const intersecting = lineintersect(newVersion, newVersion); 12 | if ( 13 | intersecting.features.length > coords.length - 2 && 14 | (coords[0][0] !== coords[coords.length - 1][0] && 15 | coords[0][1] !== coords[coords.length - 1][1]) 16 | ) { 17 | return true; 18 | } else { 19 | return false; 20 | } 21 | } 22 | return false; 23 | } 24 | 25 | module.exports = selfIntersectingWays; 26 | -------------------------------------------------------------------------------- /forked/naughty-words/cs.json: -------------------------------------------------------------------------------- 1 | [ 2 | "bordel", 3 | "buzna", 4 | "čumět", 5 | "čurák", 6 | "debil", 7 | "do piče", 8 | "do prdele", 9 | "dršťka", 10 | "držka", 11 | "flundra", 12 | "hajzl", 13 | "hovno", 14 | "chcanky", 15 | "chuj", 16 | "jebat", 17 | "kokot", 18 | "kokotina", 19 | "koňomrd", 20 | "kunda", 21 | "kurva", 22 | "mamrd", 23 | "mrdat", 24 | "mrdka", 25 | "mrdník", 26 | "oslošoust", 27 | "piča", 28 | "píčus", 29 | "píchat", 30 | "pizda", 31 | "prcat", 32 | "prdel", 33 | "prdelka", 34 | "sračka", 35 | "srát", 36 | "šoustat", 37 | "šulin", 38 | "vypíčenec", 39 | "zkurvit", 40 | "zkurvysyn", 41 | "zmrd", 42 | "žrát" 43 | ] 44 | -------------------------------------------------------------------------------- /comparators/very_long_name.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = veryLongName; 4 | 5 | function veryLongName(newVersion, oldVersion) { 6 | if ( 7 | !newVersion.deleted && 8 | newVersion.properties && 9 | newVersion.properties.hasOwnProperty('name') && 10 | newVersion.properties['name'].length > 80 11 | ) { 12 | if ( 13 | newVersion.properties.hasOwnProperty('type') && 14 | newVersion.properties.hasOwnProperty('osm:type') && 15 | newVersion.properties['type'] === 'route' && 16 | newVersion.properties['osm:type'] === 'relation' && 17 | newVersion.properties['name'].length < 121 18 | ) { 19 | return false; 20 | } else { 21 | return {'result:very_long_name': true}; 22 | } 23 | } else { 24 | return false; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | ISC License 3 | 4 | Copyright (c) 2017, Mapbox 5 | 6 | Permission to use, copy, modify, and/or distribute this software for any 7 | purpose with or without fee is hereby granted, provided that the above 8 | copyright notice and this permission notice appear in all copies. 9 | 10 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | -------------------------------------------------------------------------------- /scripts/convert_user_blocks_to_csv.py: -------------------------------------------------------------------------------- 1 | import json 2 | import csv 3 | 4 | json_filename = 'data/user_blocks.json' 5 | csv_filename = 'data/user_blocks.csv' 6 | 7 | with open(json_filename) as f: 8 | user_blocks = json.load(f) 9 | 10 | with open(csv_filename, 'w') as f: 11 | writer = csv.writer(f, delimiter='|') 12 | field_names = ['id', 'blocked_user', 'blocked_by', 'reason', 'created_at', 'ends_at'] 13 | writer.writerow(field_names) 14 | for user_block in user_blocks: 15 | writer.writerow([ 16 | user_block['id'], 17 | user_block['blocked_user'].encode('utf-8'), 18 | user_block['blocked_by'].encode('utf-8'), 19 | user_block['reason'].encode('utf-8'), 20 | user_block['created_at'], 21 | user_block['ends_at'], 22 | ]) 23 | -------------------------------------------------------------------------------- /comparators/missing_primary_tag.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var fs = require('fs'); 4 | var join = require('path').join; 5 | var primaryTags = require('../lib/primary_tags').primaryTags; 6 | 7 | module.exports = missingPrimaryTag; 8 | 9 | function missingPrimaryTag(newVersion, oldVersion) { 10 | if (newVersion.deleted || !newVersion.properties) return false; 11 | 12 | primaryTags = primaryTags.concat([ 13 | 'addr:street', 14 | 'addr:interpolation', 15 | 'addr:full', 16 | 'addr:postcode' 17 | ]); 18 | 19 | const feature_tags = Object.keys(newVersion.properties); 20 | const feature_primary_tags = primaryTags.filter( 21 | i => feature_tags.filter(t => t === i).length > 0 22 | ); 23 | 24 | if (feature_primary_tags.length > 0) return false; 25 | return {'result:missing_primary_tag': true}; 26 | } 27 | -------------------------------------------------------------------------------- /lib/important_place.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const important = require('./important'); 3 | 4 | function importantPlace(newVersion, oldVersion) { 5 | const newType = checkPlaceType(newVersion); 6 | const oldType = checkPlaceType(oldVersion); 7 | return oldType || newType; 8 | } 9 | 10 | function checkPlaceType(feature) { 11 | if (!feature) return false; 12 | if (feature && feature.geometry && feature.geometry.type !== 'Point') { 13 | return false; 14 | } 15 | const tags = feature.properties; 16 | if (tags && tags.place) { 17 | if (tags.place === 'city') return 'city'; 18 | if (tags.place === 'town' && important.hasWikiTags(tags)) { 19 | return 'town'; 20 | } 21 | } 22 | return false; 23 | } 24 | 25 | module.exports = { 26 | importantPlace: importantPlace, 27 | checkPlaceType: checkPlaceType 28 | }; 29 | -------------------------------------------------------------------------------- /comparators/place_type_change.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const checkPlaceType = require('../lib/important_place').checkPlaceType; 3 | 4 | function placeTypeChange(newVersion, oldVersion) { 5 | if ( 6 | !oldVersion || !oldVersion.properties || !oldVersion || !oldVersion.geometry 7 | ) { 8 | return false; 9 | } 10 | const isNew = newVersion.properties['osm:version'] === 1 || !oldVersion; 11 | const isDeleted = !!newVersion.deleted; 12 | if (isNew || isDeleted) return false; 13 | if (oldVersion.properties.place || newVersion.properties.place) { 14 | const newType = checkPlaceType(newVersion); 15 | const oldType = checkPlaceType(oldVersion); 16 | if (newType !== oldType) { 17 | return { 18 | 'result:place_type_change': true 19 | }; 20 | } 21 | } 22 | return false; 23 | } 24 | module.exports = placeTypeChange; 25 | -------------------------------------------------------------------------------- /comparators/deleted_address.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = deletedAddress; 4 | 5 | function deletedAddress(newVersion, oldVersion) { 6 | if ( 7 | oldVersion && 8 | oldVersion.properties && 9 | (oldVersion.properties['addr:housenumber'] || 10 | oldVersion.properties['addr:housename']) 11 | ) { 12 | if (newVersion.deleted || !newVersion.properties) { 13 | return {'result:deleted_address': true}; 14 | } 15 | if ( 16 | !('addr:housenumber' in newVersion.properties) && 17 | !('addr:housename' in newVersion.properties) 18 | ) { 19 | return {'result:deleted_address': true}; 20 | } 21 | if ( 22 | oldVersion.properties['addr:street'] && 23 | !('addr:street' in newVersion.properties) 24 | ) { 25 | return {'result:deleted_address': true}; 26 | } 27 | } 28 | return false; 29 | } 30 | -------------------------------------------------------------------------------- /comparators/null_island.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var turfInside = require('turf-inside'); 4 | var bboxPolygon = require('turf-bbox-polygon'); 5 | var centroid = require('turf-centroid'); 6 | 7 | module.exports = nullIsland; 8 | 9 | function nullIsland(newVersion, oldVersion) { 10 | if ( 11 | newVersion.deleted || 12 | !newVersion.hasOwnProperty('geometry') || 13 | newVersion['geometry'] === null 14 | ) { 15 | return false; 16 | } 17 | var polygon = bboxPolygon([ 18 | -10.496769839987422, 19 | -4.291703357034322, 20 | 5.252754932388029, 21 | 4.291703357043673 22 | ]); 23 | var center = centroid(newVersion); 24 | var inside = turfInside(center, polygon); 25 | if (inside) { 26 | return { 27 | message: 'Is inside null island boundaries', 28 | 'result:null_island': inside 29 | }; 30 | } 31 | return false; 32 | } 33 | -------------------------------------------------------------------------------- /comparators/name_ref_changes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var MAJOR_ROAD_TYPES = ['motorway', 'trunk', 'motorway_link', 'trunk_link']; 4 | 5 | function getHighwayType(feature) { 6 | return feature.properties.highway; 7 | } 8 | 9 | function isMajorRoad(feature) { 10 | var highwayType = getHighwayType(feature); 11 | return MAJOR_ROAD_TYPES.indexOf(highwayType) !== -1; 12 | } 13 | 14 | function nameRefChanged(newVersion, oldVersion) { 15 | if (!oldVersion || newVersion.deleted) { 16 | return false; 17 | } 18 | 19 | if (isMajorRoad(oldVersion)) { 20 | if ( 21 | oldVersion.properties.name !== newVersion.properties.name || 22 | oldVersion.properties.ref !== newVersion.properties.ref 23 | ) { 24 | return { 25 | 'result:name_ref_changes': true 26 | }; 27 | } 28 | } 29 | return false; 30 | } 31 | 32 | module.exports = nameRefChanged; 33 | -------------------------------------------------------------------------------- /scripts/save-water/index.js: -------------------------------------------------------------------------------- 1 | var argv = require('minimist')(process.argv.slice(2)); 2 | var tileReduce = require('tile-reduce'); 3 | var path = require('path'); 4 | var fs = require('fs'); 5 | 6 | if (!argv.osm || !argv.lakes) { 7 | console.log(''); 8 | console.log('node index.js OPTIONS'); 9 | console.log(''); 10 | console.log(' OPTIONS'); 11 | console.log(' --osm osm-qa-tiles.mbtiles'); 12 | console.log(' --lakes lakes.json'); 13 | console.log(''); 14 | return; 15 | } 16 | 17 | tileReduce({ 18 | // BBOX of US. 19 | bbox: [-125.33203125, 28.304380682962783, -53.26171875, 51.28940590271679], 20 | zoom: 12, 21 | map: path.join(__dirname, 'extract.js'), 22 | sources: [ 23 | { 24 | name: 'osm', 25 | mbtiles: argv.osm 26 | } 27 | ], 28 | output: fs.createWriteStream(argv.lakes) 29 | }); 30 | -------------------------------------------------------------------------------- /comparators/added_website.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = addedWebsite; 4 | 5 | function addedWebsite(newVersion, oldVersion) { 6 | if ( 7 | oldVersion && 8 | oldVersion.properties && 9 | !newVersion.deleted && 10 | newVersion.properties && 11 | oldVersion.properties.website === newVersion.properties.website 12 | ) { 13 | return false; 14 | } else if ( 15 | !newVersion.deleted && 16 | newVersion.properties && 17 | 'website' in newVersion.properties 18 | ) { 19 | return {'result:added_website': true}; 20 | } else if ( 21 | oldVersion && 22 | oldVersion.properties && 23 | 'website' in oldVersion.properties && 24 | !newVersion.deleted && 25 | newVersion.properties && 26 | !('website' in newVersion.properties) 27 | ) { 28 | return {'result:added_website': true}; 29 | } else { 30 | return false; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /tests/fixtures/common_tag_values.json: -------------------------------------------------------------------------------- 1 | { 2 | "compareFunction": "common_tag_values", 3 | "fixtures": [ 4 | { 5 | "description": "Feature with no new version and old version", 6 | "newVersion": {"deleted": true}, 7 | "oldVersion": {}, 8 | "expectedResult": false 9 | }, 10 | { 11 | "description": "Feature with valid highway tag", 12 | "newVersion": {"properties": {"highway" : "primary"}}, 13 | "oldVersion": {}, 14 | "expectedResult": false 15 | }, 16 | { 17 | "description": "Feature with invalid highway tag", 18 | "newVersion": {"properties": {"highway" : "random"}}, 19 | "oldVersion": {}, 20 | "expectedResult": { 21 | "result:common_tag_values": true 22 | } 23 | } 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /comparators/added_place.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = addedPlace; 4 | 5 | function addedPlace(newVersion, oldVersion) { 6 | if (newVersion.deleted) { 7 | return false; 8 | } 9 | if (oldVersion) { 10 | if ( 11 | 'place' in newVersion.properties && !('place' in oldVersion.properties) 12 | ) { 13 | if ( 14 | newVersion.properties['place'] === 'city' || 15 | newVersion.properties['place'] === 'town' || 16 | newVersion.properties['place'] === 'country' 17 | ) { 18 | return {'result:added_place': true}; 19 | } 20 | } 21 | } else if ( 22 | 'place' in newVersion.properties && 23 | (newVersion.properties['place'] === 'city' || 24 | newVersion.properties['place'] === 'town' || 25 | newVersion.properties['place'] === 'country') 26 | ) { 27 | return {'result:added_place': true}; 28 | } 29 | return false; 30 | } 31 | -------------------------------------------------------------------------------- /comparators/new_user_park.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const newUser = require('./new_user'); 3 | const version = require('../lib/version'); 4 | const park = require('../lib/park'); 5 | 6 | module.exports = newUserWater; 7 | 8 | const isPark = (newVersion, oldVersion) => park(newVersion, oldVersion); 9 | const isNewVersion = (newVersion, oldVersion) => 10 | version(newVersion, oldVersion, {minVersion: 0, maxVersion: 1}); 11 | const isNewUser = (newVersion, oldVersion) => 12 | newUser(newVersion, oldVersion, {maxChangesets: 30}); 13 | 14 | function newUserWater(newVersion, oldVersion) { 15 | if (!newVersion || !newVersion.properties) { 16 | return false; 17 | } 18 | 19 | if ( 20 | isPark(newVersion, oldVersion) && 21 | isNewVersion(newVersion, oldVersion) && 22 | isNewUser(newVersion, oldVersion) 23 | ) { 24 | return {'result:new_user_park': true}; 25 | } 26 | 27 | return false; 28 | } 29 | -------------------------------------------------------------------------------- /comparators/rare_critical_feature_created.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var featureFilter = require('feature-filter'); 4 | 5 | module.exports = rareCriticalFeatureCreated; 6 | 7 | var featuresJSON = [ 8 | 'any', 9 | ['==', 'place', 'country'], 10 | ['==', 'place', 'continent'], 11 | ['==', 'place', 'ocean'], 12 | ['==', 'place', 'sea'], 13 | ['==', 'natural', 'mountain_range'], 14 | ['==', 'aerodrome:type', 'public'], 15 | ['in', 'admin_level', 1, 2, 3, 4] 16 | ]; 17 | 18 | var filterFeatures = featureFilter(featuresJSON); 19 | 20 | function rareCriticalFeatureCreated(newVersion, oldVersion, callback) { 21 | if ( 22 | !newVersion.deleted && 23 | newVersion['properties']['osm:version'] === 1 && 24 | filterFeatures(newVersion) 25 | ) { 26 | return { 27 | message: 'Rare critical feature created', 28 | 'result:rare_critical_feature_created': true 29 | }; 30 | } 31 | return false; 32 | } 33 | -------------------------------------------------------------------------------- /comparators/disputed_border_tag_changed.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = disputedBorderTagChanged; 3 | 4 | function disputedBorderTagChanged(newVersion, oldVersion) { 5 | if (newVersion.deleted && !oldVersion) { 6 | // None of old version or new Version present 7 | return false; 8 | } 9 | if (!newVersion.deleted && oldVersion) { 10 | // Both new Version and old Version are present, which indicates feature has been modified 11 | /* 12 | Comparing the tags 13 | Creating a result object like following: 14 | result['result:comparator_name'][parameter] = value; 15 | */ 16 | if (oldVersion.properties && oldVersion.properties['disputed']) { 17 | if (oldVersion.properties['disputed'] !== null) { 18 | return { 19 | message: 'Disputed border tag changed', 20 | 'result:disputed_border_tag_changed': true 21 | }; 22 | } 23 | } 24 | } 25 | 26 | return false; 27 | } 28 | -------------------------------------------------------------------------------- /comparators/motorway_trunk_geometry_changed.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var deepEquals = require('deep-equals'); 4 | var MAJOR_ROAD_TYPES = ['motorway', 'trunk', 'motorway_link', 'trunk_link']; 5 | 6 | function getHighwayType(feature) { 7 | return feature.properties.highway; 8 | } 9 | 10 | function isMajorRoad(feature) { 11 | var highwayType = getHighwayType(feature); 12 | return MAJOR_ROAD_TYPES.indexOf(highwayType) !== -1; 13 | } 14 | 15 | function getGeometry(feature) { 16 | return feature.geometry; 17 | } 18 | 19 | function geometryChanged(newVersion, oldVersion) { 20 | if (!oldVersion || newVersion.deleted) { 21 | return false; 22 | } 23 | 24 | if (isMajorRoad(oldVersion)) { 25 | if (!deepEquals(getGeometry(oldVersion), getGeometry(newVersion))) { 26 | return { 27 | 'result:motorway_trunk_geometry_changed': true 28 | }; 29 | } 30 | } 31 | return false; 32 | } 33 | 34 | module.exports = geometryChanged; 35 | -------------------------------------------------------------------------------- /comparators/irrelevant_tags_on_highway.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = irrelevant_tags_on_highway; 3 | 4 | function contains_addr(properties) { 5 | var keys = Object.keys(properties); 6 | for (var i = 0; i < keys.length; i++) { 7 | if (keys[i].indexOf('addr') !== -1) { 8 | return true; 9 | } 10 | } 11 | return false; 12 | } 13 | 14 | function irrelevant_tags_on_highway(newVersion, oldVersion) { 15 | if (newVersion.deleted || !newVersion) { 16 | // None of old version or new Version present 17 | return false; 18 | } 19 | if ( 20 | newVersion.properties.highway && 21 | newVersion.properties.highway !== 'bus_stop' && 22 | (newVersion.properties.phone || 23 | newVersion.properties.website || 24 | contains_addr(newVersion.properties)) 25 | ) { 26 | return { 27 | message: 'Invalid tags on highway', 28 | 'result:irrelevant_tags_on_highway': true 29 | }; 30 | } 31 | return false; 32 | } 33 | -------------------------------------------------------------------------------- /tests/deprecated_construction_proposal_tag.json: -------------------------------------------------------------------------------- 1 | { 2 | "compareFunction": "deprecated_construction_proposal_tag", 3 | "fixtures": [{ 4 | "description": "Deprecate highway", 5 | "newVersion": { 6 | "type": "Feature", 7 | "id": "way!430124248!2", 8 | "properties": { 9 | "highway": "tertiary", 10 | "construction": "yes" 11 | }, 12 | }, 13 | "oldVersion": null, 14 | "expectedResult": { 15 | "result:deprecated_construction_proposal_tag": true 16 | } 17 | }, { 18 | "description": "No deprecated highway", 19 | "newVersion": { 20 | 21 | "type": "Feature", 22 | "properties": { 23 | "highway": "construction", 24 | "construction": "residential" 25 | }, 26 | }, 27 | "oldVersion": {}, 28 | "expectedResult": false 29 | 30 | }] 31 | } -------------------------------------------------------------------------------- /forked/naughty-words/ko.json: -------------------------------------------------------------------------------- 1 | [ 2 | "강간", 3 | "개새끼", 4 | "개자식", 5 | "개좆", 6 | "개차반", 7 | "거유", 8 | "계집년", 9 | "고자", 10 | "근친", 11 | "노모", 12 | "니기미", 13 | "뒤질래", 14 | "딸딸이", 15 | "때씹", 16 | "또라이", 17 | "뙤놈", 18 | "로리타", 19 | "망가", 20 | "몰카", 21 | "미친", 22 | "미친새끼", 23 | "바바리맨", 24 | "변태", 25 | "병신", 26 | "보지", 27 | "불알", 28 | "빠구리", 29 | "사까시", 30 | "섹스", 31 | "스와핑", 32 | "쌍놈", 33 | "씨발", 34 | "씨발놈", 35 | "씨팔", 36 | "씹", 37 | "씹물", 38 | "씹빨", 39 | "씹새끼", 40 | "씹알", 41 | "씹창", 42 | "씹팔", 43 | "암캐", 44 | "애자", 45 | "야동", 46 | "야사", 47 | "야애니", 48 | "엄창", 49 | "에로", 50 | "염병", 51 | "옘병", 52 | "유모", 53 | "육갑", 54 | "은꼴", 55 | "자위", 56 | "자지", 57 | "잡년", 58 | "종간나", 59 | "좆", 60 | "좆만", 61 | "죽일년", 62 | "쥐좆", 63 | "직촬", 64 | "짱깨", 65 | "쪽바리", 66 | "창녀", 67 | "포르노", 68 | "하드코어", 69 | "호로", 70 | "화냥년", 71 | "후레아들", 72 | "후장", 73 | "희쭈그리" 74 | ] 75 | -------------------------------------------------------------------------------- /forked/naughty-words/pl.json: -------------------------------------------------------------------------------- 1 | [ 2 | "burdel", 3 | "burdelmama", 4 | "chuj", 5 | "chujnia", 6 | "ciota", 7 | "cipa", 8 | "cyc", 9 | "debil", 10 | "dmuchać", 11 | "do kurwy nędzy", 12 | "dupa", 13 | "dupek", 14 | "duperele", 15 | "dziwka", 16 | "fiut", 17 | "gówno", 18 | "gówno prawda", 19 | "huj", 20 | "jajco", 21 | "jajeczko", 22 | "jajko", 23 | "jajo", 24 | "ja pierdolę", 25 | "jebać", 26 | "jebany", 27 | "kurwa", 28 | "kurwy", 29 | "kutafon", 30 | "kutas", 31 | "lizać pałę", 32 | "obciągać chuja", 33 | "obciągać fiuta", 34 | "obciągać loda", 35 | "pieprzyć", 36 | "pierdolec", 37 | "pierdolić", 38 | "pierdolnięty", 39 | "pierdoła", 40 | "pierdzieć", 41 | "pizda", 42 | "pojeb", 43 | "popierdolony", 44 | "robic loda", 45 | "robić loda", 46 | "ruchać", 47 | "rzygać", 48 | "skurwysyn", 49 | "sraczka", 50 | "srać", 51 | "suka", 52 | "syf", 53 | "wkurwiać", 54 | "zajebisty" 55 | ] 56 | -------------------------------------------------------------------------------- /tests/fixtures/deprecated_construction_proposal_tag.json: -------------------------------------------------------------------------------- 1 | { 2 | "compareFunction": "deprecated_construction_proposal_tag", 3 | "fixtures": [{ 4 | "description": "Deprecate highway", 5 | "newVersion": { 6 | "type": "Feature", 7 | "id": "way!430124248!2", 8 | "properties": { 9 | "highway": "tertiary", 10 | "construction": "yes" 11 | } 12 | }, 13 | "oldVersion": null, 14 | "expectedResult": { 15 | "result:deprecated_construction_proposal_tag": true 16 | } 17 | }, { 18 | "description": "No deprecated highway", 19 | "newVersion": { 20 | 21 | "type": "Feature", 22 | "properties": { 23 | "highway": "construction", 24 | "construction": "residential" 25 | } 26 | }, 27 | "oldVersion": {}, 28 | "expectedResult": false 29 | 30 | }] 31 | } -------------------------------------------------------------------------------- /comparators/large_building.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const turfArea = require('turf-area'); 3 | const impossibleAngle = require('../lib/impossible_angle'); 4 | 5 | module.exports = largeBuilding; 6 | 7 | /* 8 | Buildings with very large areas or large areas and weird angles. 9 | @param {geojson} newVersion 10 | @param {null} oldVersion 11 | @returns {object|false) returns the detected incident or false 12 | */ 13 | function largeBuilding(newVersion, oldVersion) { 14 | if (newVersion.deleted || !newVersion.geometry) return false; 15 | if (!newVersion.properties.hasOwnProperty('building')) return false; 16 | const area = Math.round(turfArea(newVersion)); 17 | const hasImpossibleAngle = impossibleAngle(newVersion, oldVersion, { 18 | maxAngle: 50 19 | }); 20 | if (area > 500000 && hasImpossibleAngle) { 21 | return {'result:large_building': true}; 22 | } 23 | 24 | if (area > 2000000) { 25 | return {'result:large_building': true}; 26 | } 27 | return false; 28 | } 29 | -------------------------------------------------------------------------------- /lib/pokemon_nest.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const findChangedTag = require('./changed_tag'); 3 | 4 | module.exports = pokemonNest; 5 | 6 | const pokemonValues = new Set([ 7 | 'recreation_ground', 8 | 'park', 9 | 'pitch', 10 | 'playground', 11 | 'golf_course', 12 | 'meadow', 13 | 'grass', 14 | 'cemetery', 15 | 'grass', 16 | 'forest', 17 | 'farmland', 18 | 'cliff', 19 | 'bare_rock', 20 | 'residential' 21 | ]); 22 | 23 | const pokemonTags = [ 24 | 'leisure', 25 | 'landuse', 26 | 'natural' 27 | ]; 28 | 29 | function hasPokemonTags(newVersion, oldVersion) { 30 | const changedInfo = findChangedTag(newVersion, oldVersion, pokemonTags); 31 | if (changedInfo.changed && pokemonValues.has(changedInfo.final)) { 32 | return changedInfo; 33 | } 34 | return false; 35 | } 36 | 37 | function pokemonNest(newVersion, oldVersion) { 38 | const suspiciousTag = hasPokemonTags(newVersion, oldVersion); 39 | if (suspiciousTag) { 40 | return true; 41 | } 42 | return false; 43 | } 44 | -------------------------------------------------------------------------------- /lib/changed_tag.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const findChangedTag = (newVersion, oldVersion, tags) => { 4 | const regex = new RegExp(`^${tags.join('|')}$`); 5 | if (!newVersion || !oldVersion) return false; 6 | 7 | let key, initial, final; 8 | const changed = (() => { 9 | for (const tag in oldVersion.properties) { 10 | if (!regex.test(tag)) continue; 11 | if (newVersion.properties[tag] === oldVersion.properties[tag]) continue; 12 | key = tag; 13 | initial = oldVersion.properties[tag]; 14 | final = newVersion.properties[tag]; 15 | return true; 16 | } 17 | 18 | for (const tag in newVersion.properties) { 19 | if (!regex.test(tag)) continue; 20 | if (newVersion.properties[tag] === oldVersion.properties[tag]) continue; 21 | key = tag; 22 | initial = oldVersion.properties[tag]; 23 | final = newVersion.properties[tag]; 24 | return true; 25 | } 26 | 27 | return false; 28 | })(); 29 | 30 | return {changed, key, initial, final}; 31 | }; 32 | 33 | module.exports = findChangedTag; 34 | -------------------------------------------------------------------------------- /fun/word_cloud_user_blocks.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | 3 | import json 4 | from os import path 5 | from wordcloud import WordCloud 6 | from bs4 import BeautifulSoup 7 | 8 | filename = 'data/user_blocks.json' 9 | with open(filename) as f: 10 | user_blocks = json.load(f) 11 | 12 | reasons = list() 13 | for user_block in user_blocks: 14 | soup = BeautifulSoup(user_block['reason'], 'html.parser') 15 | reasons.append(soup.text.encode('utf-8')) 16 | # print reasons 17 | text = ' '.join(reasons) 18 | 19 | # Generate a word cloud image 20 | wordcloud = WordCloud().generate(text) 21 | 22 | # Display the generated image: 23 | # the matplotlib way: 24 | # import matplotlib.pyplot as plt 25 | # plt.imshow(wordcloud) 26 | # plt.axis("off") 27 | # 28 | # # take relative word frequencies into account, lower max_font_size 29 | # wordcloud = WordCloud(max_font_size=40, relative_scaling=.5).generate(text) 30 | # plt.figure() 31 | # plt.imshow(wordcloud) 32 | # plt.axis("off") 33 | # plt.show() 34 | 35 | # The pil way (if you don't have matplotlib) 36 | image = wordcloud.to_image() 37 | image.show() 38 | -------------------------------------------------------------------------------- /comparators/new_user_motorway.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const newUser = require('./new_user'); 3 | 4 | function isMotorway(newVersion) { 5 | if (newVersion.properties && newVersion.properties.highway) { 6 | if ( 7 | newVersion.properties.highway === 'motorway' || 8 | newVersion.properties.highway === 'trunk' || 9 | newVersion.properties.highway === 'motorway_link' || 10 | newVersion.properties.highway === 'trunk_link' 11 | ) { 12 | return true; 13 | } 14 | } 15 | return false; 16 | } 17 | function isAdded(newVersion) { 18 | if (newVersion.properties['osm:version'] === 1) { 19 | return true; 20 | } 21 | return false; 22 | } 23 | const isANewUser = (newVersion, oldVersion) => 24 | newUser(newVersion, oldVersion, {maxChangesets: 100}); 25 | const motorwayNewUser = (newVersion, oldVersion) => { 26 | if ( 27 | isANewUser(newVersion, oldVersion) && 28 | isMotorway(newVersion) && 29 | isAdded(newVersion) 30 | ) { 31 | return {'result:new_user_motorway': true}; 32 | } 33 | return false; 34 | }; 35 | module.exports = motorwayNewUser; 36 | -------------------------------------------------------------------------------- /comparators/new_user_footway.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const newUser = require('./new_user'); 4 | const impossibleAngles = require('../lib/impossible_angle'); 5 | const selfIntersecting = require('../lib/self_intersecting'); 6 | 7 | // check if the user is new user 8 | // check if the feature is actually a footway and it is version 1 9 | // check if the footway has imposible angle 10 | // check if the footway is selfintersecting 11 | 12 | function isFootway(newVersion) { 13 | if ( 14 | newVersion.properties && 15 | newVersion.properties.highway === 'footway' && 16 | newVersion.properties['osm:version'] === 1 17 | ) { 18 | return true; 19 | } 20 | return false; 21 | } 22 | 23 | function isBadFootway(newVersion) { 24 | return isFootway(newVersion) && 25 | (impossibleAngles(newVersion) || selfIntersecting(newVersion)); 26 | } 27 | 28 | const newUserFootway = (newVersion, oldVersion) => { 29 | if (newUser(newVersion, oldVersion) && isBadFootway(newVersion)) { 30 | return {'result:new_user_footway': true}; 31 | } 32 | return false; 33 | }; 34 | 35 | module.exports = newUserFootway; 36 | -------------------------------------------------------------------------------- /comparators/impossible_angles.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const impossibleAngle = require('../lib/impossible_angle'); 3 | 4 | module.exports = impossibleAngleMotorableHighways; 5 | /* 6 | check impossible angles in highways 7 | */ 8 | function impossibleAngleMotorableHighways(newVersion, oldVersion) { 9 | return impossibleAngle(newVersion) && 10 | motorableHighway(newVersion, oldVersion); 11 | } 12 | 13 | const highwayValues = new Set([ 14 | 'motorway', 15 | 'motorway_link', 16 | 'trunk', 17 | 'trunk_link', 18 | 'primary', 19 | 'primary_link', 20 | 'secondary', 21 | 'secondary_link', 22 | 'tertiary', 23 | 'tertiary_link', 24 | 'residentail', 25 | 'unclassified', 26 | 'service', 27 | 'living_street' 28 | ]); 29 | 30 | function isMotorableHighway(tags) { 31 | return highwayValues.has(tags.highway); 32 | } 33 | function motorableHighway(newVersion, oldVersion) { 34 | if ( 35 | (newVersion && isMotorableHighway(newVersion.properties)) || 36 | (oldVersion && isMotorableHighway(oldVersion.properties)) 37 | ) 38 | return {'result:impossible_angles': true}; 39 | return false; 40 | } 41 | -------------------------------------------------------------------------------- /comparators/wikidata_distance_with_osm.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var turfDistance = require('turf-distance'); 4 | var turfCentroid = require('turf-centroid'); 5 | module.exports = wikidataDistanceWithOsm; 6 | 7 | function wikidataDistanceWithOsm(newVersion) { 8 | if ( 9 | newVersion.properties && 10 | newVersion.properties.place && 11 | newVersion.properties['wikidata:claims:P625:longitude'] && 12 | newVersion.properties['wikidata:claims:P625:latitude'] && 13 | newVersion.geometry 14 | ) { 15 | var wikiFeature = { 16 | type: 'Feature', 17 | properties: {}, 18 | geometry: { 19 | type: 'Point', 20 | coordinates: [ 21 | newVersion.properties['wikidata:claims:P625:longitude'], 22 | newVersion.properties['wikidata:claims:P625:latitude'] 23 | ] 24 | } 25 | }; 26 | var distance = turfDistance( 27 | turfCentroid(newVersion), 28 | wikiFeature, 29 | 'kilometres' 30 | ); 31 | if (distance > 1) { 32 | return {'result:wikidata_distance_with_osm': distance}; 33 | } 34 | } 35 | return false; 36 | } 37 | -------------------------------------------------------------------------------- /example/example.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = significant_features; 3 | 4 | function significant_features(newVersion, oldVersion) { 5 | if (newVersion.deleted && !oldVersion) { 6 | // None of old version or new Version present 7 | return false; 8 | } 9 | if (newVersion && oldVersion) { 10 | // Both new Version and old Version are present, which indicates feature has been modified 11 | /* 12 | Comparing the tags 13 | return false; 14 | */ 15 | } else if (newVersion && !oldVersion) { 16 | // Only new Version is present, which indicates feature has been added 17 | /* 18 | Comparing the tags 19 | return false; 20 | */ 21 | } else if (newVersion.deleted && oldVersion) { 22 | // Only old Version is present, which indicates feature has been deleted 23 | /* 24 | Comparing the tags 25 | returning result 26 | */ 27 | if (oldVersion.properties && oldVersion.properties['osm:version']) { 28 | if (oldVersion.properties['osm:version'] > 25) { 29 | return {'result:significant_feature': true}; 30 | } 31 | } 32 | } 33 | 34 | return false; 35 | } 36 | -------------------------------------------------------------------------------- /scripts/primary-map-features-analysis.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var fs = require('fs'); 4 | var csv = require('csv'); 5 | var argv = require('minimist')(process.argv.slice(2)); 6 | 7 | (function() { 8 | if (!argv.counts) { 9 | console.log(''); 10 | console.log('USAGE: node primary-map-features-analysis.js OPTIONS'); 11 | console.log(''); 12 | console.log(' OPTIONS'); 13 | console.log(' --counts counts.csv'); 14 | console.log(''); 15 | return; 16 | } 17 | 18 | // Statistics for number of counts that are zero, less than 1 and greater than 1. 19 | var stats = [0, 0, 0]; 20 | 21 | csv.parse(fs.readFileSync(argv.counts), function(error, rows) { 22 | var header = []; 23 | for (var i = 0; i < rows.length; i++) { 24 | if (header.length === 0) { 25 | header = rows[i]; 26 | continue; 27 | } 28 | 29 | for (var j = 0; j < rows[i].length; j++) { 30 | var count = parseFloat(rows[i][j]); 31 | if (count === 0) stats[0] += 1; 32 | else if (count < 1) stats[1] += 1; 33 | else stats[2] += 1; 34 | } 35 | } 36 | console.log(stats); 37 | }); 38 | })(); 39 | -------------------------------------------------------------------------------- /comparators/new_user_motorway_deleted.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const newUser = require('./new_user'); 3 | 4 | function isMotorway(newVersion, oldVersion) { 5 | if (!oldVersion) { 6 | return false; 7 | } 8 | if (newVersion.properties && newVersion.properties.highway) { 9 | if ( 10 | newVersion.properties.highway === 'motorway' || 11 | newVersion.properties.highway === 'trunk' || 12 | newVersion.properties.highway === 'motorway_link' || 13 | newVersion.properties.highway === 'trunk_link' 14 | ) { 15 | return true; 16 | } 17 | } 18 | return false; 19 | } 20 | function isDeleted(newVersion) { 21 | if (newVersion.deleted) { 22 | return true; 23 | } 24 | return false; 25 | } 26 | const isANewUser = (newVersion, oldVersion) => 27 | newUser(newVersion, oldVersion, {maxChangesets: 200}); 28 | const newUserMotorwayDeleted = (newVersion, oldVersion) => { 29 | if ( 30 | isANewUser(newVersion, oldVersion) && 31 | isMotorway(newVersion, oldVersion) && 32 | isDeleted(newVersion) 33 | ) { 34 | return {'result:new_user_motorway_deleted': true}; 35 | } 36 | return false; 37 | }; 38 | 39 | module.exports = newUserMotorwayDeleted; 40 | -------------------------------------------------------------------------------- /comparators/pokemon_edits.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var filtered_tags = [ 4 | 'natural', 5 | 'water', 6 | 'highway', 7 | 'building', 8 | 'leisure', 9 | 'tourism' 10 | ]; 11 | module.exports = pokemonEdits; 12 | 13 | function hasPokename(name) { 14 | return name.match(/(P|p)ok(é|e)((m|M)on|(S|s)top|(G|g)ym|(G|g)o)/g) || 15 | name.match(/(P|p)ok(é|e)/g); 16 | } 17 | 18 | function pokemonEdits(newVersion, oldVersion) { 19 | if (newVersion.deleted && !oldVersion) { 20 | // None of old version or new Version present 21 | return false; 22 | } 23 | if (!newVersion.deleted) { 24 | var pass = filtered_tags.reduce( 25 | function(accum, tag) { 26 | return tag in newVersion.properties || accum; 27 | }, 28 | false 29 | ); 30 | 31 | if (pass) { 32 | for (var prop in newVersion.properties) { 33 | if (prop.indexOf('name') === 0) { 34 | if (hasPokename(newVersion.properties[prop])) { 35 | return { 36 | message: 'Contains a pokemon name', 37 | 'result:pokemon_edits': true 38 | }; 39 | } 40 | } 41 | } 42 | } 43 | } 44 | 45 | return false; 46 | } 47 | -------------------------------------------------------------------------------- /tests/fixtures/features/way-67715300-10.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "FeatureCollection", 3 | "properties": { 4 | "result:landmark_score": { 5 | "cfVersion": 2, 6 | "score": 2.9 7 | } 8 | }, 9 | "features": [ 10 | { 11 | "type": "Feature", 12 | "id": "way!67715300!10", 13 | "properties": { 14 | "building": "yes", 15 | "heritage": "2", 16 | "heritage:operator": "mhs", 17 | "name": "Hôtel-Dieu", 18 | "operator": "InterContinental Hotels Group", 19 | "ref:mhs": "PA00081349", 20 | "source": "cadastre-dgi-fr source : Direction Générale des Impôts - Cadastre. Mise à jour : 2013", 21 | "tourism": "hotel", 22 | "wikipedia": "fr:Hôtel-Dieu de Marseille", 23 | "osm:type": "way", 24 | "osm:id": 67715300, 25 | "osm:version": 10, 26 | "osm:changeset": 37947672, 27 | "osm:timestamp": 1458429896000, 28 | "osm:uid": 2775946, 29 | "osm:user": "M GM" 30 | }, 31 | "geometry": { 32 | "type": "Point", 33 | "coordinates": [ 34 | 5.370142964999999, 35 | 43.2985024625 36 | ] 37 | } 38 | }, 39 | null 40 | ] 41 | } 42 | -------------------------------------------------------------------------------- /tests/fixtures/rare_critical_feature_created.json: -------------------------------------------------------------------------------- 1 | { 2 | "compareFunction": "rare_critical_feature_created", 3 | "fixtures": [ 4 | { 5 | "description": "New country created", 6 | "newVersion": { 7 | "type": "Feature", 8 | "properties": { 9 | "osm:version": 1, 10 | "place": "country" 11 | }, 12 | "geometry": { 13 | "type": "Polygon", 14 | "coordinates": [ 15 | [ 16 | [ 17 | 58.35937499999999, 18 | 64.01449619484472 19 | ], 20 | [ 21 | 56.953125, 22 | 57.51582286553883 23 | ], 24 | [ 25 | 76.2890625, 26 | 57.70414723434193 27 | ], 28 | [ 29 | 76.9921875, 30 | 63.54855223203644 31 | ], 32 | [ 33 | 58.35937499999999, 34 | 64.01449619484472 35 | ] 36 | ] 37 | ] 38 | } 39 | }, 40 | "oldVersion": {}, 41 | "expectedResult": { 42 | "result:rare_critical_feature_created": true 43 | } 44 | } 45 | ] 46 | } -------------------------------------------------------------------------------- /tests/fixtures/feature_overlap.json: -------------------------------------------------------------------------------- 1 | { 2 | "compareFunction": "feature_overlap", 3 | "fixtures": [ 4 | { 5 | "description": "feature_overlap return result", 6 | "newVersion": { 7 | "type": "Feature", 8 | "properties": { 9 | "osm:version" : 1, 10 | "natural": "water", 11 | "osm:type": "way" 12 | }, 13 | "geometry": { 14 | "type": "Polygon", 15 | "coordinates": [ 16 | [ 17 | [ 18 | 77.55291938781738, 19 | 12.984161807639202 20 | ], 21 | [ 22 | 77.55291938781738, 23 | 12.9838324899327 24 | ], 25 | [ 26 | 77.55411028862, 27 | 12.983774989970945 28 | ], 29 | [ 30 | 77.55415320396423, 31 | 12.984083398701047 32 | ], 33 | [ 34 | 77.55291938781738, 35 | 12.984161807639202 36 | ] 37 | ] 38 | ] 39 | } 40 | }, 41 | "oldVersion": {}, 42 | "expectedResult": { 43 | "result:feature_overlap": 24 44 | } 45 | } 46 | ] 47 | } -------------------------------------------------------------------------------- /forked/naughty-words/es.json: -------------------------------------------------------------------------------- 1 | [ 2 | "Asesinato", 3 | "asno", 4 | "bastardo", 5 | "Bollera", 6 | "Cabron", 7 | "Cabrón", 8 | "Caca", 9 | "Chupada", 10 | "Chupapollas", 11 | "Chupetón", 12 | "concha", 13 | "Concha de tu madre", 14 | "Coño", 15 | "Coprofagía", 16 | "Culo", 17 | "Drogas", 18 | "Esperma", 19 | "Fiesta de salchichas", 20 | "Follador", 21 | "Follar", 22 | "Gilipichis", 23 | "Gilipollas", 24 | "Hacer una paja", 25 | "Haciendo el amor", 26 | "Heroína", 27 | "Hija de puta", 28 | "Hijaputa", 29 | "Hijo de puta", 30 | "Hijoputa", 31 | "Idiota", 32 | "Imbécil", 33 | "infierno", 34 | "Jilipollas", 35 | "Kapullo", 36 | "Lameculos", 37 | "Maciza", 38 | "Macizorra", 39 | "maldito", 40 | "Mamada", 41 | "Marica", 42 | "Maricón", 43 | "Mariconazo", 44 | "martillo", 45 | "Mierda", 46 | "Nazi", 47 | "Orina", 48 | "Pedo", 49 | "Pervertido", 50 | "Pezón", 51 | "Pinche", 52 | "Pis", 53 | "Prostituta", 54 | "Puta", 55 | "Racista", 56 | "Ramera", 57 | "Sádico", 58 | "Semen", 59 | "Sexo", 60 | "Sexo oral", 61 | "Soplagaitas", 62 | "Soplapollas", 63 | "Tetas grandes", 64 | "Tía buena", 65 | "Travesti", 66 | "Trio", 67 | "Verga", 68 | "vete a la mierda", 69 | "Vulva" 70 | ] 71 | -------------------------------------------------------------------------------- /comparators/invalid_name.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = invalidName; 4 | 5 | function invalidName(newVersion, oldVersion, callback) { 6 | // Not interested if there isn't a newVersion. 7 | if (newVersion.deleted) return callback(null, false); 8 | 9 | var properties = newVersion.properties; 10 | for (var key in properties) { 11 | var naughtyWords; 12 | 13 | // TODO: Having English as the default locale for now. 14 | if (key === 'name') { 15 | naughtyWords = require('naughty-words/en.json'); 16 | if (naughtyWords.indexOf(properties[key]) !== -1) 17 | return callback(null, {'result:invalid_name': true}); 18 | } else if (key.indexOf('name') === 0) { 19 | // Splitting 'name:ko' into ['name', 'ko'] 20 | var locale = key.split(':')[1]; 21 | 22 | // Name of the locale's naughty word file. 23 | var filename = 'naughty-words/' + locale + '.json'; 24 | 25 | try { 26 | naughtyWords = require(filename); 27 | if (naughtyWords.indexOf(properties[key]) !== -1) 28 | return callback(null, {'result:invalid_name': true}); 29 | } catch (error) { 30 | // TODO: naughty-words for the locale does not exist. Skip for now. 31 | } 32 | } 33 | } 34 | return callback(null, false); 35 | } 36 | -------------------------------------------------------------------------------- /forked/naughty-words/de.json: -------------------------------------------------------------------------------- 1 | [ 2 | "analritter", 3 | "arsch", 4 | "arschficker", 5 | "arschlecker", 6 | "arschloch", 7 | "bimbo", 8 | "bratze", 9 | "bumsen", 10 | "bonze", 11 | "dödel", 12 | "fick", 13 | "ficken", 14 | "flittchen", 15 | "fotze", 16 | "fratze", 17 | "hackfresse", 18 | "hure", 19 | "hurensohn", 20 | "ische", 21 | "kackbratze", 22 | "kacke", 23 | "kacken", 24 | "kackwurst", 25 | "kampflesbe", 26 | "kanake", 27 | "kimme", 28 | "lümmel", 29 | "MILF", 30 | "möpse", 31 | "morgenlatte", 32 | "möse", 33 | "mufti", 34 | "muschi", 35 | "nackt", 36 | "neger", 37 | "nigger", 38 | "nippel", 39 | "nutte", 40 | "onanieren", 41 | "orgasmus", 42 | "pimmel", 43 | "pimpern", 44 | "pinkeln", 45 | "pissen", 46 | "pisser", 47 | "popel", 48 | "poppen", 49 | "porno", 50 | "reudig", 51 | "rosette", 52 | "schabracke", 53 | "schlampe", 54 | "scheiße", 55 | "scheisser", 56 | "schiesser", 57 | "schnackeln", 58 | "schwanzlutscher", 59 | "schwuchtel", 60 | "tittchen", 61 | "titten", 62 | "vögeln", 63 | "vollpfosten", 64 | "wichse", 65 | "wichsen", 66 | "wichser" 67 | ] 68 | -------------------------------------------------------------------------------- /comparators/common_tag_values.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var fs = require('fs'); 4 | var join = require('path').join; 5 | const primaryTags = require('../lib/primary_tags').primaryTags; 6 | 7 | module.exports = commonTagValues; 8 | 9 | function commonTagValues(newVersion, oldVersion) { 10 | var result = false; 11 | 12 | if (newVersion.deleted || !newVersion.properties) return false; 13 | 14 | var primary_tag_present = false; 15 | result = {'result:common_tag_values': true}; 16 | 17 | for (var i = primaryTags.length - 1; i >= 0; i--) { 18 | var tag = primaryTags[i]; 19 | 20 | if (tag in newVersion.properties) { 21 | primary_tag_present = true; 22 | var data = fs.readFileSync( 23 | join(__dirname, '..', 'common_tag_values/' + tag + '.json') 24 | ); 25 | var commonValues = JSON.parse(data.toString()).data; 26 | var value = newVersion.properties[tag]; 27 | for (var j = commonValues.length - 1; j >= 0; j--) { 28 | if ( 29 | commonValues[j]['value'] === value && 30 | !(commonValues[j]['fraction'] <= 0.0 && 31 | commonValues[j]['in_wiki'] === false) 32 | ) { 33 | return false; 34 | } 35 | } 36 | } 37 | } 38 | if (!primary_tag_present) return false; 39 | return result; 40 | } 41 | -------------------------------------------------------------------------------- /tests/fixtures/null_island.json: -------------------------------------------------------------------------------- 1 | { 2 | "compareFunction": "null_island", 3 | "fixtures": [ 4 | { 5 | "description": "A way in null island", 6 | "newVersion": { 7 | "type": "Feature", 8 | "properties": {}, 9 | "geometry": { 10 | "type": "LineString", 11 | "coordinates": [ 12 | [ 13 | -2.197265625, 14 | -2.3284603685731593 15 | ], 16 | [ 17 | 2.8564453125, 18 | -2.218683588558448 19 | ] 20 | ] 21 | } 22 | }, 23 | "oldVersion": null, 24 | "expectedResult": { 25 | "result:null_island": true 26 | } 27 | }, 28 | { 29 | "description": "A way that's not in null island", 30 | "newVersion": { 31 | "type": "Feature", 32 | "properties": {}, 33 | "geometry": { 34 | "type": "LineString", 35 | "coordinates": [ 36 | [ 37 | 37.880859375, 38 | 23.160563309048314 39 | ], 40 | [ 41 | 48.251953125, 42 | 31.87755764334002 43 | ] 44 | ] 45 | } 46 | }, 47 | "oldVersion": null, 48 | "expectedResult": false 49 | } 50 | ] 51 | } 52 | -------------------------------------------------------------------------------- /comparators/major_name_modification.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Levenshtein = require('levenshtein'); 4 | 5 | module.exports = majorNameModification; 6 | 7 | function majorNameModification(newVersion, oldVersion) { 8 | if ( 9 | newVersion.deleted || 10 | !newVersion.properties || 11 | !newVersion.properties.name || 12 | !(newVersion.properties.wikidata || newVersion.properties.wikipedia) 13 | ) 14 | return false; 15 | if (!oldVersion || !oldVersion.properties || !oldVersion.properties.name) 16 | return false; 17 | 18 | // If the name tag was not modified 19 | if (oldVersion.properties.name === newVersion.properties.name) return false; 20 | 21 | var distance = new Levenshtein( 22 | newVersion.properties.name, 23 | oldVersion.properties.name 24 | ).distance; 25 | var length = oldVersion.properties.name.length; 26 | var modification = 100.0 * distance / length; 27 | 28 | // If modification is greater than 50%, it is a major name modification. 29 | process.stderr.write('# version ' + newVersion.properties['osm:version']); 30 | if (modification >= 50 && newVersion.properties['osm:version'] > 10) { 31 | return { 32 | 'result:major_name_modification': true, 33 | 'result:levenshtein_distance': distance 34 | }; 35 | } 36 | 37 | return false; 38 | } 39 | -------------------------------------------------------------------------------- /comparators/destination_ref_changed.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var MAJOR_ROAD_TYPES = [ 4 | 'motorway', 5 | 'trunk', 6 | 'primary', 7 | 'secondary', 8 | 'tertiary', 9 | 'motorway_link', 10 | 'trunk_link', 11 | 'primary_link', 12 | 'secondary_link', 13 | 'tertiary_link' 14 | ]; 15 | 16 | function getHighwayType(feature) { 17 | return feature.properties.highway; 18 | } 19 | 20 | function isMajorRoad(feature) { 21 | var highwayType = getHighwayType(feature); 22 | return MAJOR_ROAD_TYPES.indexOf(highwayType) !== -1; 23 | } 24 | 25 | function hasDestination(feature) { 26 | return feature.properties.destination; 27 | } 28 | 29 | function hasDestinationRef(feature) { 30 | return feature.properties['destination:ref']; 31 | } 32 | 33 | function destinationRefChanged(newVersion, oldVersion) { 34 | if (!oldVersion || newVersion.deleted) { 35 | return false; 36 | } 37 | 38 | if (isMajorRoad(oldVersion)) { 39 | if ( 40 | oldVersion.properties.destination !== newVersion.properties.destination || 41 | oldVersion.properties['destination:ref'] !== 42 | newVersion.properties['destination:ref'] 43 | ) { 44 | return { 45 | 'result:destination_ref_changed': true 46 | }; 47 | } 48 | } 49 | return false; 50 | } 51 | module.exports = destinationRefChanged; 52 | -------------------------------------------------------------------------------- /tests/fixtures/invalid_tag_combination.json: -------------------------------------------------------------------------------- 1 | { 2 | "compareFunction": "invalid_tag_combination", 3 | "fixtures": [ 4 | { 5 | "description": "Test feature with invalid tag combination with count less than 1", 6 | "expectedResult": { 7 | "result:invalid_tag_combination": true 8 | }, 9 | "newVersion": { 10 | "type": "Feature", 11 | "properties": { 12 | "tourism": "yes", 13 | "amenity": "yes" 14 | }, 15 | "geometry": null 16 | }, 17 | "oldVersion": null 18 | }, 19 | { 20 | "description": "Test feature with invalid tag combination with count greater than 1", 21 | "expectedResult": false, 22 | "newVersion": { 23 | "type": "Feature", 24 | "properties": { 25 | "sport": "yes", 26 | "leisure": "yes" 27 | }, 28 | "geometry": null 29 | }, 30 | "oldVersion": null 31 | }, 32 | { 33 | "description": "Test feature with no primary tags", 34 | "expectedResult": false, 35 | "newVersion": { 36 | "type": "Feature", 37 | "properties": { 38 | "name": "Hello, world!", 39 | "version": "1" 40 | }, 41 | "geometry": null 42 | }, 43 | "oldVersion": null 44 | } 45 | ] 46 | } 47 | -------------------------------------------------------------------------------- /comparators/invalid_tag_combination.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var csv = require('csv'); 4 | var fs = require('fs'); 5 | var path = require('path'); 6 | 7 | module.exports = invalidTagCombination; 8 | function invalidTagCombination(newVersion, oldVersion, callback) { 9 | // What should be the minimum value of count to be a valid tag combination. 10 | var MIN_COUNT = 1; 11 | 12 | if (newVersion.deleted) return callback(null, false); 13 | 14 | csv.parse( 15 | fs.readFileSync(path.join(__dirname, 'tag-combinations.csv')), 16 | function(error, rows) { 17 | var tags = Object.keys(newVersion.properties); 18 | 19 | for (var i = 0; i < tags.length; i++) { 20 | for (var j = 0; j < tags.length; j++) { 21 | if (i === j) continue; 22 | 23 | var tag = tags[i]; 24 | var anotherTag = tags[j]; 25 | 26 | var rowIndex = rows[0].indexOf(tag); 27 | var columnIndex = rows[0].indexOf(anotherTag); 28 | if (rowIndex === -1 || columnIndex === -1) continue; 29 | 30 | var count = parseFloat(rows[rowIndex][columnIndex]); 31 | 32 | if (isNaN(count) || count < MIN_COUNT) 33 | return callback(null, {'result:invalid_tag_combination': true}); 34 | } 35 | } 36 | return callback(null, false); 37 | } 38 | ); 39 | } 40 | -------------------------------------------------------------------------------- /forked/naughty-words/index.js: -------------------------------------------------------------------------------- 1 | module.exports = {}; 2 | module.exports['ar'] = require('./ar.json'); 3 | module.exports['cs'] = require('./cs.json'); 4 | module.exports['da'] = require('./da.json'); 5 | module.exports['de'] = require('./de.json'); 6 | module.exports['en'] = require('./en.json'); 7 | module.exports['eo'] = require('./eo.json'); 8 | module.exports['es'] = require('./es.json'); 9 | module.exports['fa'] = require('./fa.json'); 10 | module.exports['fi'] = require('./fi.json'); 11 | module.exports['fr'] = require('./fr.json'); 12 | module.exports['fr-CA-u-sd-caqc'] = require('./fr-CA-u-sd-caqc.json'); 13 | module.exports['hi'] = require('./hi.json'); 14 | module.exports['hu'] = require('./hu.json'); 15 | module.exports['it'] = require('./it.json'); 16 | module.exports['ja'] = require('./ja.json'); 17 | module.exports['ko'] = require('./ko.json'); 18 | module.exports['nl'] = require('./nl.json'); 19 | module.exports['no'] = require('./no.json'); 20 | module.exports['pl'] = require('./pl.json'); 21 | module.exports['pt'] = require('./pt.json'); 22 | module.exports['ru'] = require('./ru.json'); 23 | module.exports['sv'] = require('./sv.json'); 24 | module.exports['th'] = require('./th.json'); 25 | module.exports['tlh'] = require('./tlh.json'); 26 | module.exports['tr'] = require('./tr.json'); 27 | module.exports['zh'] = require('./zh.json'); 28 | -------------------------------------------------------------------------------- /forked/naughty-words/pt.json: -------------------------------------------------------------------------------- 1 | [ 2 | "aborto", 3 | "amador", 4 | "ânus", 5 | "aranha", 6 | "ariano", 7 | "balalao", 8 | "bastardo", 9 | "bicha", 10 | "biscate", 11 | "bissexual", 12 | "boceta", 13 | "boob", 14 | "bosta", 15 | "braulio de borracha", 16 | "bumbum", 17 | "burro", 18 | "cabrao", 19 | "cacete", 20 | "cagar", 21 | "camisinha", 22 | "caralho", 23 | "cerveja", 24 | "chochota", 25 | "chupar", 26 | "clitoris", 27 | "cocaína", 28 | "colhoes", 29 | "comer", 30 | "cona", 31 | "consolo", 32 | "corno", 33 | "cu", 34 | "dar o rabo", 35 | "dum raio", 36 | "esporra", 37 | "fecal", 38 | "filho da puta", 39 | "foda", 40 | "foda-se", 41 | "foder", 42 | "frango assado", 43 | "gozar", 44 | "grelho", 45 | "heroína", 46 | "heterosexual", 47 | "homem gay", 48 | "homoerótico", 49 | "homosexual", 50 | "inferno", 51 | "lésbica", 52 | "lolita", 53 | "mama", 54 | "merda", 55 | "paneleiro", 56 | "passar um cheque", 57 | "pau", 58 | "peidar", 59 | "pênis", 60 | "pinto", 61 | "porra", 62 | "puta", 63 | "puta que pariu", 64 | "puta que te pariu", 65 | "queca", 66 | "sacanagem", 67 | "saco", 68 | "torneira", 69 | "transar", 70 | "vai-te foder", 71 | "vai tomar no cu", 72 | "veado", 73 | "vibrador", 74 | "xana", 75 | "xochota" 76 | ] 77 | -------------------------------------------------------------------------------- /comparators/new_user_water.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const newUser = require('./new_user'); 3 | const version = require('../lib/version'); 4 | 5 | module.exports = newUserWater; 6 | 7 | function isWaterFeature(feature) { 8 | const landuse_values = ['pond', 'reservoir', 'salt_pond', 'basin']; 9 | const props = feature.properties; 10 | 11 | if ('natural' in props && props['natural'] === 'water') return true; 12 | if ('water' in props) return true; 13 | if ('landuse' in props && landuse_values.indexOf(props['landuse']) !== -1) 14 | return true; 15 | if ('reservoir_type' in props && props['reservoir_type'] === 'water_storage') 16 | return true; 17 | if ('man_made' in props && props['man_made'] === 'reservoir_covered') 18 | return true; 19 | if ('waterway' in props) return true; 20 | 21 | return false; 22 | } 23 | 24 | function newUserWater(newVersion, oldVersion) { 25 | if (!newVersion || !newVersion.properties) { 26 | return false; 27 | } 28 | const hasWater = isWaterFeature(newVersion); 29 | const isNewVersion = version(newVersion, oldVersion, { 30 | minVersion: 0, 31 | maxVersion: 1 32 | }); 33 | const isNewUser = newUser(newVersion, oldVersion, {maxChangesets: 10}); 34 | 35 | if (hasWater && isNewVersion && isNewUser) { 36 | return {'result:new_user_water': true}; 37 | } 38 | return false; 39 | } 40 | -------------------------------------------------------------------------------- /comparators/water_feature_by_new_user.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const newUser = require('./new_user'); 3 | 4 | module.exports = waterFeatureByNewUser; 5 | 6 | function isWaterFeature(feature) { 7 | var landuse_values = ['pond', 'reservoir', 'salt_pond', 'basin']; 8 | 9 | var properties = feature.properties; 10 | 11 | if (properties.hasOwnProperty('natural') && properties['natural'] === 'water') 12 | return true; 13 | if (properties.hasOwnProperty('water')) return true; 14 | if ( 15 | properties.hasOwnProperty('landuse') && 16 | landuse_values.indexOf(properties['landuse']) !== -1 17 | ) 18 | return true; 19 | if ( 20 | properties.hasOwnProperty('reservoir_type') && 21 | properties['reservoir_type'] === 'water_storage' 22 | ) 23 | return true; 24 | if ( 25 | properties.hasOwnProperty('man_made') && 26 | properties['man_made'] === 'reservoir_covered' 27 | ) 28 | return true; 29 | if (properties.hasOwnProperty('waterway')) return true; 30 | 31 | return false; 32 | } 33 | 34 | function waterFeatureByNewUser(newVersion, oldVersion, callback) { 35 | if (newVersion.deleted || newVersion.properties['osm:version'] !== 1) { 36 | return false; 37 | } 38 | 39 | if (isWaterFeature(newVersion) && newUser(newVersion)) { 40 | return {'result:water_feature_by_new_user': true}; 41 | } 42 | return false; 43 | } 44 | -------------------------------------------------------------------------------- /comparators/invalid_tag_modification.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = invalidTagModification; 4 | 5 | function getPrimaryTags(properties) { 6 | var result = []; 7 | var primaryTags = [ 8 | 'aerialway', 9 | 'aeroway', 10 | 'amenity', 11 | 'barrier', 12 | 'boundary', 13 | 'building', 14 | 'craft', 15 | 'emergency', 16 | 'geological', 17 | 'highway', 18 | 'historic', 19 | 'landuse', 20 | 'leisure', 21 | 'man_made', 22 | 'military', 23 | 'natural', 24 | 'office', 25 | 'place', 26 | 'power', 27 | 'public_transport', 28 | 'railway', 29 | 'route', 30 | 'shop', 31 | 'sport', 32 | 'tourism', 33 | 'waterway' 34 | ]; 35 | for (var key in properties) { 36 | if (primaryTags.indexOf(key) !== -1) result.push(key); 37 | } 38 | return result; 39 | } 40 | 41 | function invalidTagModification(newVersion, oldVersion, callback) { 42 | if (newVersion.deleted || !oldVersion) return callback(null, false); 43 | 44 | var primaryTags = getPrimaryTags(oldVersion.properties); 45 | // Check if all primary tags are retained in newVersion. 46 | for (var i = 0; i < primaryTags.length; i++) { 47 | if (!(primaryTags[i] in newVersion.properties)) 48 | return callback(null, {'result:invalid_tag_modification': true}); 49 | } 50 | 51 | return callback(null, false); 52 | } 53 | -------------------------------------------------------------------------------- /forked/naughty-words/USERS.md: -------------------------------------------------------------------------------- 1 | # Users of these lists 2 | 3 | The following projects, documents, and organizations use these lists of dirty, 4 | naughty, obscene, and otherwise bad words. To contribute additional uses, please 5 | either [create an issue](https://github.com/shutterstock/List-of-Dirty-Naughty-Obscene-and-Otherwise-Bad-Words/issues/new) 6 | or send a pull request. 7 | 8 | ## Projects 9 | 10 | * [jQuery.ProfanityFilter](https://github.com/ChaseFlorell/jQuery.ProfanityFilter): 11 | jQuery plugin to filter out profane words on the client. 12 | * [grunt-naughty-words](https://www.npmjs.com/package/grunt-naughty-words): 13 | Node.js npm library that provides a grunt task for creating an array of 14 | profane words, and a regex to test against, as well as the capability to 15 | compile your own obscene word list. 16 | * [Arena Metrics](http://www.arena.co.ke): 17 | Arena Metrics uses these lists to prevent users from popping offensive words 18 | into polls and surveys. 19 | 20 | ## Documents 21 | 22 | * [“Bad words” filter](http://stackoverflow.com/questions/24515/bad-words-filter) 23 | on Stack Overflow 24 | * [How do you implement a good profanity filter?](http://stackoverflow.com/questions/273516/how-do-you-implement-a-good-profanity-filter) 25 | on Stack Overflow 26 | 27 | ## Organizations 28 | 29 | * [Shutterstock, Inc.](http://code.shutterstock.com/) 30 | -------------------------------------------------------------------------------- /comparators/dragged_highway_waterway.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = draggedHighwayWaterway; 4 | var turfPoint = require('turf-point'); 5 | var turfDistance = require('turf-distance'); 6 | var threshold = 10; 7 | 8 | function draggedHighwayWaterway(newVersion, oldVersion) { 9 | if ( 10 | !newVersion.deleted && 11 | oldVersion && 12 | newVersion.geometry && 13 | oldVersion.geometry 14 | ) { 15 | if ( 16 | JSON.stringify(newVersion.geometry) === 17 | JSON.stringify(oldVersion.geometry) 18 | ) 19 | return false; 20 | } 21 | 22 | if ( 23 | !newVersion.deleted && 24 | newVersion.properties && 25 | (newVersion.properties.hasOwnProperty('highway') || 26 | newVersion.properties.hasOwnProperty('waterway')) && 27 | newVersion.properties['osm:type'] === 'way' && 28 | newVersion.geometry && 29 | newVersion.geometry.hasOwnProperty('coordinates') && 30 | newVersion.geometry.coordinates.length > 1 31 | ) { 32 | for (var i = 0; i < newVersion.geometry.coordinates.length - 1; i++) { 33 | var point1 = turfPoint(newVersion.geometry.coordinates[i]); 34 | var point2 = turfPoint(newVersion.geometry.coordinates[i + 1]); 35 | 36 | var distance = turfDistance(point1, point2, 'kilometers'); 37 | if (distance > threshold) 38 | return {'result:dragged_highway_waterway': true}; 39 | } 40 | } 41 | return false; 42 | } 43 | -------------------------------------------------------------------------------- /comparators/path_road_changed.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var PATH_ROAD_TYPES = ['pedestrian', 'footway', 'cycleway', 'path']; 4 | 5 | function getHighwayType(feature) { 6 | return feature.properties.highway; 7 | } 8 | 9 | function isPathRoad(feature) { 10 | var highwayType = getHighwayType(feature); 11 | return PATH_ROAD_TYPES.indexOf(highwayType) !== -1; 12 | } 13 | 14 | function pathRoadChanged(newVersion, oldVersion) { 15 | if (!oldVersion && newVersion.deleted) { 16 | return false; 17 | } 18 | 19 | if (oldVersion && newVersion.deleted) { 20 | // Don't care about path road deletions. 21 | return false; 22 | } 23 | 24 | if (!oldVersion && !newVersion.deleted) { 25 | if (isPathRoad(newVersion)) { 26 | return { 27 | 'result:path_road_changed': { 28 | added: true 29 | } 30 | }; 31 | } 32 | } 33 | 34 | if (oldVersion && !newVersion.deleted) { 35 | var newHighwayType = getHighwayType(newVersion); 36 | var oldHighwayType = getHighwayType(oldVersion); 37 | 38 | if (isPathRoad(oldVersion) || isPathRoad(newVersion)) { 39 | if (oldHighwayType !== newHighwayType) { 40 | return { 41 | 'result:path_road_changed': { 42 | modified: true, 43 | from: oldHighwayType, 44 | to: newHighwayType 45 | } 46 | }; 47 | } 48 | } 49 | } 50 | 51 | return false; 52 | } 53 | 54 | module.exports = pathRoadChanged; 55 | -------------------------------------------------------------------------------- /comparators/major_lake_modified.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var lakeIds = [ 4 | 3987743, 5 | 4039486, 6 | 2606941, 7 | 1205151, 8 | 1205149, 9 | 1374224, 10 | 555716, 11 | 2791372, 12 | 2631184, 13 | 1834172, 14 | 4039900, 15 | 1159098, 16 | 1206310, 17 | 3367363, 18 | 2195612, 19 | 1754729, 20 | 5872303, 21 | 4618842, 22 | 2709093, 23 | 1308279, 24 | 3000330, 25 | 2194833, 26 | 2791738, 27 | 188230, 28 | 1969016, 29 | 2617981, 30 | 1082659, 31 | 36970, 32 | 1239458, 33 | 3120035, 34 | 1110965, 35 | 108807, 36 | 5869931, 37 | 1269323, 38 | 2795794, 39 | 3119933, 40 | 1206317, 41 | 404236, 42 | 404644, 43 | 1414848, 44 | 1125603 45 | ]; 46 | 47 | module.exports = majorLakeModified; 48 | 49 | function majorLakeModified(newVersion, oldVersion) { 50 | var obj = {}; 51 | if (!newVersion.deleted) { 52 | obj = newVersion; 53 | } else if (oldVersion) { 54 | obj = oldVersion; 55 | } else { 56 | return false; 57 | } 58 | if ( 59 | !obj.properties || !obj.properties['osm:type'] || !obj.properties['osm:id'] 60 | ) { 61 | return false; 62 | } 63 | var props = obj.properties; 64 | var osmType = props['osm:type']; 65 | var osmId = props['osm:id']; 66 | if (osmType === 'relation' && lakeIds.indexOf(osmId) !== -1) { 67 | return { 68 | message: 'Major lake modified', 69 | 'result:major_lake_modified': true 70 | }; 71 | } else { 72 | return false; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /tests/fixtures/new_user_footway.json: -------------------------------------------------------------------------------- 1 | { 2 | "compareFunction": "new_user_footway", 3 | "fixtures": [ 4 | { 5 | "description": "checks for a footway added by new user", 6 | "newVersion": { 7 | "type": "Feature", 8 | "properties": { 9 | "highway": "footway", 10 | "osm:type": "way", 11 | "osm:version": 1, 12 | "osm:user:changesetcount": 2 13 | }, 14 | "geometry": { 15 | "type": "LineString", 16 | "coordinates": [ 17 | [-82.27118186653, 18 | 27.39206051794 19 | ], 20 | [-82.27623514831, 21 | 27.39210814748 22 | ], 23 | [-82.27632634342, 24 | 27.39216053995 25 | ], 26 | [-82.27631561458, 27 | 27.39305120812 28 | ], 29 | [-82.27620296180, 30 | 27.39310360014 31 | ], 32 | [-82.27227084339, 33 | 27.39306073394 34 | ], 35 | [-82.27221585810, 36 | 27.39301548627 37 | ], 38 | [-82.27223731577, 39 | 27.39006005878 40 | ], 41 | [-82.27319620550, 42 | 27.39006958487 43 | ], 44 | [-82.27325253189, 45 | 27.39013388590 46 | ], 47 | [-82.27325789630, 48 | 27.39229152101 49 | ] 50 | ] 51 | } 52 | }, 53 | "oldVersion": null, 54 | "expectedResult": {"result:new_user_footway":true} 55 | } 56 | ] 57 | } 58 | -------------------------------------------------------------------------------- /tests/fixtures/wikidata_wikipedia_tag_deleted.json: -------------------------------------------------------------------------------- 1 | { 2 | "compareFunction": "wikidata_wikipedia_tag_deleted", 3 | "fixtures": [ 4 | { 5 | "description": "wikidata tag deleted", 6 | "newVersion": { 7 | "type": "Feature", 8 | "properties": {}, 9 | "geometry": null 10 | }, 11 | "oldVersion": { 12 | "type": "Feature", 13 | "properties": { 14 | "wikidata": "Q40435" 15 | }, 16 | "geometry": null 17 | }, 18 | "expectedResult": { 19 | "result:wikidata_wikipedia_tag_deleted": true 20 | } 21 | }, 22 | { 23 | "description": "wikidata tag added", 24 | "newVersion": { 25 | "type": "Feature", 26 | "properties": { 27 | "wikidata": "Q40435" 28 | }, 29 | "geometry": null 30 | }, 31 | "oldVersion": { 32 | "type": "Feature", 33 | "properties": {}, 34 | "geometry": null 35 | }, 36 | "expectedResult": false 37 | }, 38 | { 39 | "description": "wikipedia tag deleted", 40 | "newVersion": { 41 | "type": "Feature", 42 | "properties": {}, 43 | "geometry": null 44 | }, 45 | "oldVersion": { 46 | "type": "Feature", 47 | "properties": { 48 | "wikipedia": "en:abc" 49 | }, 50 | "geometry": null 51 | }, 52 | "expectedResult": { 53 | "result:wikidata_wikipedia_tag_deleted": true 54 | } 55 | } 56 | ] 57 | } 58 | -------------------------------------------------------------------------------- /tests/fixtures/invalid_name.json: -------------------------------------------------------------------------------- 1 | { 2 | "compareFunction": "invalid_name", 3 | "fixtures": [ 4 | { 5 | "description": "Test feature with name tag but without profanity", 6 | "expectedResult": false, 7 | "newVersion": { 8 | "type": "Feature", 9 | "properties": { 10 | "name": "Hello, world!" 11 | }, 12 | "geometry": null 13 | }, 14 | "oldVersion": null 15 | }, 16 | { 17 | "description": "Test feature with profanity in name tag", 18 | "expectedResult": {"result:invalid_name": true}, 19 | "newVersion": { 20 | "type": "Feature", 21 | "properties": { 22 | "name": "xxx" 23 | }, 24 | "geometry": null 25 | }, 26 | "oldVersion": null 27 | }, 28 | { 29 | "description": "Test feature with profanity in name:* tag", 30 | "expectedResult": {"result:invalid_name": true}, 31 | "newVersion": { 32 | "type": "Feature", 33 | "properties": { 34 | "name:ko": "강간" 35 | }, 36 | "geometry": null 37 | }, 38 | "oldVersion": null 39 | }, 40 | { 41 | "description": "Test feature with profanity in name:* tag when naughty-words does not exist for the locale", 42 | "expectedResult": false, 43 | "newVersion": { 44 | "type": "Feature", 45 | "properties": { 46 | "name:kn": "xxx" 47 | }, 48 | "geometry": null 49 | }, 50 | "oldVersion": null 51 | } 52 | ] 53 | } 54 | -------------------------------------------------------------------------------- /lib/large_building.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const turfArea = require('@turf/area'); 4 | const thresholdAngle = 50; 5 | function findAngle(A, B, C) { 6 | const pi = 3.14159265; 7 | const AB = Math.sqrt(Math.pow(B[0] - A[0], 2) + Math.pow(B[1] - A[1], 2)); 8 | const BC = Math.sqrt(Math.pow(B[0] - C[0], 2) + Math.pow(B[1] - C[1], 2)); 9 | const AC = Math.sqrt(Math.pow(C[0] - A[0], 2) + Math.pow(C[1] - A[1], 2)); 10 | return Math.acos((BC * BC + AB * AB - AC * AC) / (2 * BC * AB)) * (180 / pi); 11 | } 12 | function largeBuilding(newVersion) { 13 | if ( 14 | newVersion.deleted || 15 | !newVersion.hasOwnProperty('geometry') || 16 | newVersion['geometry'] === null 17 | ) { 18 | return false; 19 | } 20 | if (newVersion.properties.hasOwnProperty('building')) { 21 | const area = turfArea(newVersion); 22 | if ( 23 | newVersion.geometry.type !== 'MultiPolygon' && 24 | area > 500000 && 25 | checkAngle(newVersion) 26 | ) { 27 | return true; 28 | } 29 | if (area > 2000000) { 30 | return true; 31 | } 32 | return false; 33 | } 34 | } 35 | function checkAngle(newVersion) { 36 | let angle = 0; 37 | const coords = newVersion.geometry['coordinates'][0]; 38 | if (coords.length > 2) { 39 | for (let j = 0; j < coords.length - 2; j++) { 40 | angle = findAngle(coords[j], coords[j + 1], coords[j + 2]); 41 | if (angle < thresholdAngle) { 42 | return true; 43 | } 44 | } 45 | } 46 | return false; 47 | } 48 | module.exports = largeBuilding; 49 | -------------------------------------------------------------------------------- /lib/get_vector_tile_features.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var d3 = require('d3-queue'); 4 | var vt2geojson = require('@mapbox/vt2geojson'); 5 | var attempt = require('attempt'); 6 | 7 | var retrieveTileData = function(layers, x, y, z, callback) { 8 | var url = 'http://a.tiles.mapbox.com/v4/mapbox.mapbox-streets-v7/' + 9 | z + 10 | '/' + 11 | x + 12 | '/' + 13 | y + 14 | '.mvt?access_token=pk.eyJ1IjoiYW1pc2hhIiwiYSI6ImNqaHVibzk5ZjBpaGQzcW9uanRrZG1kdTMifQ.YfJF2hRjuxM3FnRghgV7yw'; 15 | vt2geojson( 16 | { 17 | uri: url, 18 | layer: layers 19 | }, 20 | function(err, result) { 21 | if (err) return callback(err, null); 22 | return callback(null, result); 23 | } 24 | ); 25 | }; 26 | 27 | var attempt_retrieveTileData = function(layers, x, y, z, callback) { 28 | attempt( 29 | {retries: 5}, 30 | function(attempts) { 31 | if (attempts > 0) console.log('#', attempts); 32 | retrieveTileData(layers, x, y, z, this); 33 | }, 34 | function(err, result) { 35 | if (err) return callback(err, null); 36 | else return callback(null, result); 37 | } 38 | ); 39 | }; 40 | 41 | var getVectorTileFeatures = function(tiles, layers, callback) { 42 | var queue = d3.queue(5); 43 | tiles.forEach(function(tile) { 44 | queue.defer(attempt_retrieveTileData, layers, tile[0], tile[1], tile[2]); 45 | }); 46 | queue.awaitAll(function(err, results) { 47 | if (err) return callback(err, null); 48 | return callback(null, results); 49 | }); 50 | }; 51 | 52 | module.exports = getVectorTileFeatures; 53 | -------------------------------------------------------------------------------- /tests/fixtures/new_user_park.json: -------------------------------------------------------------------------------- 1 | { 2 | "compareFunction": "new_user_park", 3 | "fixtures": [ 4 | { 5 | "description": "checks for a park added by new user", 6 | "newVersion": { 7 | "type": "Feature", 8 | "properties": { 9 | "landuse": "park", 10 | "osm:type": "way", 11 | "osm:version": 1, 12 | "osm:user:changesetcount": 2 13 | }, 14 | "geometry": { 15 | "type": "LineString", 16 | "coordinates": [ 17 | [-82.27118186653, 18 | 27.39206051794 19 | ], 20 | [-82.27623514831, 21 | 27.39210814748 22 | ], 23 | [-82.27632634342, 24 | 27.39216053995 25 | ], 26 | [-82.27631561458, 27 | 27.39305120812 28 | ], 29 | [-82.27620296180, 30 | 27.39310360014 31 | ], 32 | [-82.27227084339, 33 | 27.39306073394 34 | ], 35 | [-82.27221585810, 36 | 27.39301548627 37 | ], 38 | [-82.27223731577, 39 | 27.39006005878 40 | ], 41 | [-82.27319620550, 42 | 27.39006958487 43 | ], 44 | [-82.27325253189, 45 | 27.39013388590 46 | ], 47 | [-82.27325789630, 48 | 27.39229152101 49 | ] 50 | ] 51 | } 52 | }, 53 | "oldVersion": null, 54 | "expectedResult": {"result:new_user_park":true} 55 | } 56 | ] 57 | } 58 | -------------------------------------------------------------------------------- /lib/important.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const namePattern = /^(int_|loc_|nat_|official_|reg_|short_|sorting_)?name(:.*)?$/; 4 | module.exports = important; 5 | module.exports.hasTranslations = hasTranslations; 6 | module.exports.hasWikiTags = hasWikiTags; 7 | module.exports.hasPlaceMetadata = hasPlaceMetadata; 8 | 9 | function hasWikiTags(tags) { 10 | return tags && (tags.wikipedia || tags.wikidata); 11 | } 12 | 13 | function hasTranslations(tags) { 14 | const translations = Object.keys(tags).map((key) => namePattern.test(key)).filter((k) => !!k).length; 15 | return translations > 1; 16 | } 17 | 18 | function hasAddr(tags) { 19 | const addrs = Object.keys(tags).map((key) => /^addr/.test(key)).filter((k) => !!k).length; 20 | return addrs > 0; 21 | } 22 | 23 | function hasRefs(tags) { 24 | const refs = Object.keys(tags).map((key) => /^ref/.test(key)).filter((k) => !!k).length; 25 | return refs > 0; 26 | } 27 | 28 | function hasContact(tags) { 29 | return tags.website || tags.phone || tags['contact:phone'] || tags['contact:website']; 30 | } 31 | 32 | function hasPlaceMetadata(tags) { 33 | return tags.population || tags.ele || tags.postal_code || tags.is_in; 34 | } 35 | 36 | function hasImportantTags(tags) { 37 | return hasAddr(tags) || hasContact(tags) || hasRefs(tags) || hasTranslations(tags) || hasWikiTags(tags) || hasPlaceMetadata(tags); 38 | } 39 | 40 | function important(newVersion, oldVersion) { 41 | if (hasImportantTags(newVersion.properties)) { 42 | return true; 43 | } else if (oldVersion && hasImportantTags(oldVersion.properties)) { 44 | return true; 45 | } 46 | return false; 47 | } 48 | -------------------------------------------------------------------------------- /tests/fixtures/place_name_changed.json: -------------------------------------------------------------------------------- 1 | { 2 | "compareFunction": "place_name_changed", 3 | "fixtures": [ 4 | { 5 | "description": "Flags place edited - name changed", 6 | "newVersion": { 7 | "type": "Feature", 8 | "properties": { 9 | "osm:id": 1234, 10 | "osm:type": "node", 11 | "osm:uid": 123, 12 | "osm:changeset": 123, 13 | "place": "city", 14 | "name": "paris" 15 | }, 16 | "geometry": { 17 | "type": "Point", 18 | "coordinates": [ 19 | 10, 20 | 10 21 | ] 22 | } 23 | }, 24 | "oldVersion": { 25 | "type": "Feature", 26 | "properties": { 27 | "osm:id": 1234, 28 | "osm:type": "node", 29 | "osm:uid": 124, 30 | "osm:changeset": 124, 31 | "place": "city", 32 | "name": "paris", 33 | "name:en": "paris" 34 | }, 35 | "geometry": { 36 | "type": "Point", 37 | "coordinates": [ 38 | 11, 39 | 11 40 | ] 41 | } 42 | }, 43 | "expectedResult": { 44 | "result:place_name_changed": true 45 | } 46 | } 47 | ] 48 | } 49 | -------------------------------------------------------------------------------- /forked/naughty-words/fr.json: -------------------------------------------------------------------------------- 1 | [ 2 | "baiser", 3 | "bander", 4 | "bigornette", 5 | "bite", 6 | "bitte", 7 | "bloblos", 8 | "bordel", 9 | "bosser", 10 | "bourré", 11 | "bourrée", 12 | "brackmard", 13 | "branlage", 14 | "branler", 15 | "branlette", 16 | "branleur", 17 | "branleuse", 18 | "brouter le cresson", 19 | "caca", 20 | "cailler", 21 | "chatte", 22 | "chiasse", 23 | "chier", 24 | "chiottes", 25 | "clito", 26 | "clitoris", 27 | "con", 28 | "connard", 29 | "connasse", 30 | "conne", 31 | "couilles", 32 | "cramouille", 33 | "cul", 34 | "déconne", 35 | "déconner", 36 | "drague", 37 | "emmerdant", 38 | "emmerder", 39 | "emmerdeur", 40 | "emmerdeuse", 41 | "enculé", 42 | "enculée", 43 | "enculeur", 44 | "enculeurs", 45 | "enfoiré", 46 | "enfoirée", 47 | "étron", 48 | "fille de pute", 49 | "fils de pute", 50 | "folle", 51 | "foutre", 52 | "gerbe", 53 | "gerber", 54 | "gouine", 55 | "grande folle", 56 | "grogniasse", 57 | "gueule", 58 | "jouir", 59 | "la putain de ta mère", 60 | "MALPT", 61 | "ménage à trois", 62 | "merde", 63 | "merdeuse", 64 | "merdeux", 65 | "meuf", 66 | "nègre", 67 | "nique ta mère", 68 | "nique ta race", 69 | "palucher", 70 | "pédale", 71 | "pédé", 72 | "péter", 73 | "pipi", 74 | "pisser", 75 | "pouffiasse", 76 | "pousse-crotte", 77 | "putain", 78 | "pute", 79 | "ramoner", 80 | "sac à merde", 81 | "salaud", 82 | "salope", 83 | "suce", 84 | "tapette", 85 | "teuf", 86 | "tringler", 87 | "trique", 88 | "trou du cul", 89 | "turlute", 90 | "veuve", 91 | "zigounette", 92 | "zizi" 93 | ] 94 | -------------------------------------------------------------------------------- /forked/naughty-words/hu.json: -------------------------------------------------------------------------------- 1 | [ 2 | "balfasz", 3 | "balfaszok", 4 | "balfaszokat", 5 | "balfaszt", 6 | "barmok", 7 | "barmokat", 8 | "barmot", 9 | "barom", 10 | "baszik", 11 | "bazmeg", 12 | "buksza", 13 | "bukszák", 14 | "bukszákat", 15 | "bukszát", 16 | "búr", 17 | "búrok", 18 | "csöcs", 19 | "csöcsök", 20 | "csöcsöket", 21 | "csöcsöt", 22 | "fasz", 23 | "faszfej", 24 | "faszfejek", 25 | "faszfejeket", 26 | "faszfejet", 27 | "faszok", 28 | "faszokat", 29 | "faszt", 30 | "fing", 31 | "fingok", 32 | "fingokat", 33 | "fingot", 34 | "franc", 35 | "francok", 36 | "francokat", 37 | "francot", 38 | "geci", 39 | "gecibb", 40 | "gecik", 41 | "geciket", 42 | "gecit", 43 | "kibaszott", 44 | "kibaszottabb", 45 | "kúr", 46 | "kurafi", 47 | "kurafik", 48 | "kurafikat", 49 | "kurafit", 50 | "kurva", 51 | "kurvák", 52 | "kurvákat", 53 | "kurvát", 54 | "leggecibb", 55 | "legkibaszottabb", 56 | "legszarabb", 57 | "marha", 58 | "marhák", 59 | "marhákat", 60 | "marhát", 61 | "megdöglik", 62 | "pele", 63 | "pelék", 64 | "picsa", 65 | "picsákat", 66 | "picsát", 67 | "pina", 68 | "pinák", 69 | "pinákat", 70 | "pinát", 71 | "pofa", 72 | "pofákat", 73 | "pofát", 74 | "pöcs", 75 | "pöcsök", 76 | "pöcsöket", 77 | "pöcsöt", 78 | "punci", 79 | "puncik", 80 | "segg", 81 | "seggek", 82 | "seggeket", 83 | "segget", 84 | "seggfej", 85 | "seggfejek", 86 | "seggfejeket", 87 | "seggfejet", 88 | "szajha", 89 | "szajhák", 90 | "szajhákat", 91 | "szajhát", 92 | "szar", 93 | "szarabb", 94 | "szarik", 95 | "szarok", 96 | "szarokat", 97 | "szart" 98 | ] 99 | -------------------------------------------------------------------------------- /tests/fixtures/place_feature_deleted.json: -------------------------------------------------------------------------------- 1 | { 2 | "compareFunction": "place_feature_deleted", 3 | "fixtures": [ 4 | { 5 | "description": "Flags place deleted", 6 | "newVersion": { 7 | "type": "Feature", 8 | "properties": { 9 | "osm:version": 2, 10 | "osm:id": 1234, 11 | "osm:type": "node", 12 | "osm:uid": 123, 13 | "osm:changeset": 123, 14 | "name": "London", 15 | "place": "city" 16 | }, 17 | "deleted": true, 18 | "geometry": { 19 | "type": "Point", 20 | "coordinates": [ 21 | 10, 22 | 10 23 | ] 24 | } 25 | }, 26 | "oldVersion": { 27 | "type": "Feature", 28 | "properties": { 29 | "osm:id": 1234, 30 | "osm:version": 1, 31 | "osm:type": "node", 32 | "osm:uid": 124, 33 | "osm:changeset": 124, 34 | "name": "London", 35 | "place": "city" 36 | }, 37 | "geometry": { 38 | "type": "Point", 39 | "coordinates": [ 40 | 11, 41 | 11 42 | ] 43 | } 44 | }, 45 | "expectedResult": { 46 | "result:place_feature_deleted": true 47 | } 48 | } 49 | ] 50 | } 51 | -------------------------------------------------------------------------------- /lib/impossible_angle.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = impossibleAngle; 4 | 5 | function findAngle(A, B, C) { 6 | const pi = 3.14159265; 7 | const AB = Math.sqrt(Math.pow(B[0] - A[0], 2) + Math.pow(B[1] - A[1], 2)); 8 | const BC = Math.sqrt(Math.pow(B[0] - C[0], 2) + Math.pow(B[1] - C[1], 2)); 9 | const AC = Math.sqrt(Math.pow(C[0] - A[0], 2) + Math.pow(C[1] - A[1], 2)); 10 | return Math.acos((BC * BC + AB * AB - AC * AC) / (2 * BC * AB)) * (180 / pi); 11 | } 12 | 13 | /* 14 | Features having an impossible angle as specified by the options. 15 | @param {geojson} newVersion 16 | @param {null} oldVersion 17 | @param {object} options 18 | @param {number} [options.maxAngle=30] 19 | @returns {bool) returns true for detected angles or false 20 | */ 21 | function impossibleAngle(newVersion, oldVersion, options) { 22 | options = Object.assign( 23 | { 24 | maxAngle: 30 25 | }, 26 | options 27 | ); 28 | 29 | if (newVersion.deleted || !newVersion.geometry) return false; 30 | let coords = []; 31 | if (newVersion.geometry.type === 'MultiPolygon') { 32 | coords = newVersion.geometry.coordinates[0][0]; 33 | } else if (newVersion.geometry.type === 'Polygon') { 34 | coords = newVersion.geometry.coordinates[0]; 35 | } else if (newVersion.geometry.type === 'LineString') { 36 | coords = newVersion.geometry.coordinates; 37 | } 38 | 39 | for (let j = 0; j < coords.length - 2; j++) { 40 | const angle = findAngle(coords[j], coords[j + 1], coords[j + 2]); 41 | if (angle < options.maxAngle) { 42 | return { 43 | message: `Found impossible angle ${angle}`, 44 | angle: angle 45 | }; 46 | } 47 | } 48 | return false; 49 | } 50 | -------------------------------------------------------------------------------- /comparators/osm_landmarks.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var queue = require('d3-queue').queue; 4 | var getLakes = require('osm-landmarks').getLakes; 5 | var getAirports = require('osm-landmarks').getAirports; 6 | var getRestaurants = require('osm-landmarks').getRestaurants; 7 | 8 | module.exports = osmLandmarks; 9 | 10 | function osmLandmarks(newVersion, oldVersion, callback) { 11 | var featureID, featureType; 12 | 13 | if ( 14 | !newVersion.deleted && 15 | newVersion.properties && 16 | 'osm:id' in newVersion.properties && 17 | 'osm:type' in newVersion.properties 18 | ) { 19 | featureID = String(newVersion.properties['osm:id']); 20 | featureType = newVersion.properties['osm:type']; 21 | } else if ( 22 | oldVersion && 23 | oldVersion.properties && 24 | 'osm:id' in oldVersion.properties && 25 | 'osm:type' in oldVersion.properties 26 | ) { 27 | featureID = String(oldVersion.properties['osm:id']); 28 | featureType = oldVersion.properties['osm:type']; 29 | } else { 30 | return callback(null, false); 31 | } 32 | 33 | var q = queue(1); 34 | 35 | q.defer(getLakes); 36 | q.defer(getAirports); 37 | q.defer(getRestaurants); 38 | 39 | q.awaitAll(function(err, results) { 40 | if (err) { 41 | console.log(err); 42 | return callback(err, false); 43 | } 44 | 45 | for (var i = 0; i < results.length; i++) { 46 | for (var j = 0; j < results[i].length; j++) { 47 | if ( 48 | results[i][j][0] === featureID && results[i][j][1] === featureType 49 | ) { 50 | return callback(null, { 51 | 'result:osm_landmarks': true 52 | }); 53 | } 54 | } 55 | } 56 | 57 | return callback(null, false); 58 | }); 59 | } 60 | -------------------------------------------------------------------------------- /comparators/invalid_highway_tags.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = invalidHighwayTags; 3 | 4 | function invalidHighwayTags(newVersion, oldVersion) { 5 | var validHighwayTags = [ 6 | 'motorway', 7 | 'trunk', 8 | 'primary', 9 | 'secondary', 10 | 'tertiary', 11 | 'unclassified', 12 | 'residential', 13 | 'service', 14 | 'motorway_link', 15 | 'trunk_link', 16 | 'primary_link', 17 | 'secondary_link', 18 | 'tertiary_link', 19 | 'living_street', 20 | 'pedestrian', 21 | 'corridor', 22 | 'track', 23 | 'bus_guideway', 24 | 'escape', 25 | 'raceway', 26 | 'road', 27 | 'footway', 28 | 'bridleway', 29 | 'steps', 30 | 'path', 31 | 'cycleway', 32 | 'proposed', 33 | 'construction', 34 | 'rest_area', 35 | 'services', 36 | 'bus_stop', 37 | 'crossing', 38 | 'elevator', 39 | 'emergency_access_point', 40 | 'give_way', 41 | 'mini_roundabout', 42 | 'motorway_junction', 43 | 'passing_place', 44 | 'speed_camera', 45 | 'street_lamp', 46 | 'stop', 47 | 'traffic_signals', 48 | 'milestone', 49 | 'traffic_mirror', 50 | 'turning_circle', 51 | 'turning_loop' 52 | ]; 53 | if ( 54 | !newVersion.deleted && 55 | newVersion.properties && 56 | newVersion.properties['highway'] && 57 | validHighwayTags.indexOf(newVersion.properties.highway) === -1 58 | ) { 59 | if ( 60 | oldVersion && 61 | oldVersion.properties && 62 | oldVersion.properties['highway'] && 63 | oldVersion.properties.highway === newVersion.properties.highway 64 | ) { 65 | return false; 66 | } else { 67 | return {'result:invalid_highway_tags': true}; 68 | } 69 | } 70 | return false; 71 | } 72 | -------------------------------------------------------------------------------- /forked/naughty-words/hi.json: -------------------------------------------------------------------------------- 1 | [ 2 | "aand", 3 | "aandu", 4 | "balatkar", 5 | "beti chod", 6 | "bhadva", 7 | "bhadve", 8 | "bhandve", 9 | "bhootni ke", 10 | "bhosad", 11 | "bhosadi ke", 12 | "boobe", 13 | "chakke", 14 | "chinaal", 15 | "chinki", 16 | "chod", 17 | "chodu", 18 | "chodu bhagat", 19 | "chooche", 20 | "choochi", 21 | "choot", 22 | "choot ke baal", 23 | "chootia", 24 | "chootiya", 25 | "chuche", 26 | "chuchi", 27 | "chudai khanaa", 28 | "chudan chudai", 29 | "chut", 30 | "chut ke baal", 31 | "chut ke dhakkan", 32 | "chut maarli", 33 | "chutad", 34 | "chutadd", 35 | "chutan", 36 | "chutia", 37 | "chutiya", 38 | "gaand", 39 | "gaandfat", 40 | "gaandmasti", 41 | "gaandufad", 42 | "gandu", 43 | "gashti", 44 | "gasti", 45 | "ghassa", 46 | "ghasti", 47 | "harami", 48 | "haramzade", 49 | "hawas", 50 | "hawas ke pujari", 51 | "hijda", 52 | "hijra", 53 | "jhant", 54 | "jhant chaatu", 55 | "jhant ke baal", 56 | "jhantu", 57 | "kamine", 58 | "kaminey", 59 | "kanjar", 60 | "kutta", 61 | "kutta kamina", 62 | "kutte ki aulad", 63 | "kutte ki jat", 64 | "kuttiya", 65 | "loda", 66 | "lodu", 67 | "lund", 68 | "lund choos", 69 | "lund khajoor", 70 | "lundtopi", 71 | "lundure", 72 | "maa ki chut", 73 | "maal", 74 | "madar chod", 75 | "mooh mein le", 76 | "mutth", 77 | "najayaz", 78 | "najayaz aulaad", 79 | "najayaz paidaish", 80 | "paki", 81 | "pataka", 82 | "patakha", 83 | "raand", 84 | "randi", 85 | "saala", 86 | "saala kutta", 87 | "saali kutti", 88 | "saali randi", 89 | "suar", 90 | "suar ki aulad", 91 | "tatte", 92 | "tatti", 93 | "teri maa ka bhosada", 94 | "teri maa ka boba chusu", 95 | "teri maa ki chut", 96 | "tharak", 97 | "tharki" 98 | ] 99 | -------------------------------------------------------------------------------- /comparators/place_name_changed.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const isPrioNameKey = require('../lib/names.js').isPrioNameKey; 3 | const diffWords = require('diff').diffWords; 4 | const importantPlace = require('../lib/important_place').importantPlace; 5 | 6 | module.exports = placeNameChanged; 7 | 8 | function nameChanged(newTags, oldTags) { 9 | for (const key in oldTags) { 10 | if (!isPrioNameKey(key)) continue; 11 | if (newTags[key] === oldTags[key]) continue; 12 | 13 | // allow name tag differences if the only difference is text (in parentheses) 14 | if ( 15 | diffWords(newTags[key] || '', oldTags[key] || '').filter(change => { 16 | if (!change.added && !change.removed) return false; 17 | if (/^\([^)]+\)$/.test(change.value.trim())) return false; 18 | return true; 19 | }).length === 0 20 | ) 21 | continue; 22 | return {'result:place_name_changed': true}; 23 | } 24 | 25 | const newNameTags = new Set(Object.keys(newTags).filter(isPrioNameKey)); 26 | const oldNameTags = new Set(Object.keys(oldTags).filter(isPrioNameKey)); 27 | 28 | const addedNameTags = Array.from(newNameTags.values()).filter( 29 | n => !oldNameTags.has(n) 30 | ); 31 | const removedNameTags = Array.from(oldNameTags.values()).filter( 32 | n => !newNameTags.has(n) 33 | ); 34 | 35 | if (addedNameTags.length > 0) { 36 | return {'result:place_name_changed': true}; 37 | } 38 | if (removedNameTags.length > 0) { 39 | return {'result:place_name_changed': true}; 40 | } 41 | return false; 42 | } 43 | 44 | function placeNameChanged(newVersion, oldVersion) { 45 | if (newVersion && oldVersion && importantPlace(newVersion, oldVersion)) { 46 | const change = nameChanged(newVersion.properties, oldVersion.properties); 47 | if (change) return change; 48 | } 49 | return false; 50 | } 51 | -------------------------------------------------------------------------------- /tests/common.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var test = require('tap').test; 4 | var queue = require('d3-queue').queue; 5 | var path = require('path'); 6 | var fs = require('fs'); 7 | var comparators = require('../index'); 8 | 9 | test('Test compare functions with common fixtures', function(assert) { 10 | var dirname = path.join(__dirname, '/fixtures/'); 11 | var filename = 'common.json'; 12 | var jsonData = JSON.parse( 13 | fs.readFileSync(path.join(dirname, filename), 'utf-8') 14 | ); 15 | 16 | var fixtureQueue = queue(10); 17 | jsonData.fixtures.forEach(function(fixture) { 18 | fixtureQueue.defer(testFixture, assert, fixture); 19 | }); 20 | fixtureQueue.awaitAll(function() { 21 | assert.end(); 22 | }); 23 | }); 24 | 25 | /* 26 | * Takes a fixture and tests it on all compare functions. 27 | */ 28 | function testFixture(assert, fixture, callback) { 29 | var dirname = path.join(__dirname, '..', 'comparators'); 30 | var files = fs.readdirSync(dirname); 31 | files = files.filter(function(filename) { 32 | return /.js$/.test(filename); 33 | }); 34 | 35 | var compareQueue = queue(10); 36 | Object.keys(comparators).forEach(function(comparator) { 37 | compareQueue.defer( 38 | testFixtureOnCompareFunction, 39 | assert, 40 | fixture, 41 | comparators[comparator], 42 | callback 43 | ); 44 | }); 45 | 46 | compareQueue.awaitAll(function() { 47 | callback(); 48 | }); 49 | } 50 | 51 | /* 52 | * Takes a fixture and a compare function and tests it. 53 | */ 54 | function testFixtureOnCompareFunction( 55 | assert, 56 | fixture, 57 | compareFunction, 58 | callback 59 | ) { 60 | compareFunction( 61 | fixture.newVersion, 62 | fixture.oldVersion, 63 | function(error, result) { 64 | assert.error(error); 65 | callback(); 66 | } 67 | ); 68 | } 69 | -------------------------------------------------------------------------------- /comparators/profanity.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const naughtyWords = require('../forked/naughty-words'); 3 | const priorityLanguages = require('../config/priority_languages.json'); 4 | const isNameKey = require('../lib/names').isNameKey; 5 | 6 | function profanity(newVersion, oldVersion) { 7 | if (newVersion.deleted) return false; 8 | const oldTags = (oldVersion && oldVersion.properties) || {}; 9 | const tags = newVersion.properties; 10 | 11 | if (!tags) return false; 12 | for (const tag in tags) { 13 | if (!isNameKey(tag)) continue; 14 | 15 | // Only consider name tags that have changed from their previous value 16 | if (tags[tag] === oldTags[tag]) continue; 17 | 18 | const val = tags[tag]; 19 | // Replace punctuation with whitespace 20 | // http://stackoverflow.com/a/25575009 21 | const normalized = val.replace( 22 | /[\u2000-\u206F\u2E00-\u2E7F\\'!"#$%&()*+,\-./:;<=>?@[\]^_`{|}~]/ig, 23 | ' ' 24 | ); 25 | const splitTag = tag.split(':'); 26 | const lang = (splitTag[splitTag.length - 1] || '').split('_')[0]; 27 | // If there is a language code, check against the matching language, 28 | // Otherwise check priority languages 29 | const languagesToCheck = naughtyWords[lang] ? [lang] : priorityLanguages; 30 | const incidents = languagesToCheck 31 | .map(lang => { 32 | for (let i = 0; i < naughtyWords[lang].length; i++) { 33 | const word = naughtyWords[lang][i]; 34 | const regex = new RegExp('(\\s|^)' + word + '(\\s|$)', 'gi'); 35 | if (regex.test(normalized)) { 36 | return true; 37 | } 38 | } 39 | return false; 40 | }) 41 | .filter(incident => !!incident); 42 | if (incidents.length) return {'result:profanity': true}; 43 | return false; 44 | } 45 | } 46 | module.exports = profanity; 47 | -------------------------------------------------------------------------------- /data/priority_areas.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "type": "FeatureCollection", 3 | "features": [ 4 | { 5 | "type": "Feature", 6 | "properties": { 7 | "stroke": "#555555", 8 | "stroke-width": 2, 9 | "stroke-opacity": 1, 10 | "fill": "#555555", 11 | "fill-opacity": 0.5, 12 | "name": "Washington DC Mall" 13 | }, 14 | "geometry": { 15 | "type": "Polygon", 16 | "coordinates": [ 17 | [ 18 | [ 19 | -77.03988790512084, 20 | 38.900501886178 21 | ], 22 | [ 23 | -77.03306436538696, 24 | 38.900501886178 25 | ], 26 | [ 27 | -77.03327894210815, 28 | 38.892318822259845 29 | ], 30 | [ 31 | -77.00304508209227, 32 | 38.892318822259845 33 | ], 34 | [ 35 | -77.003173828125, 36 | 38.88732502003549 37 | ], 38 | [ 39 | -77.05033779144287, 40 | 38.887007676258236 41 | ], 42 | [ 43 | -77.05181837081909, 44 | 38.88829374490249 45 | ], 46 | [ 47 | -77.05190420150757, 48 | 38.88932926377281 49 | ], 50 | [ 51 | -77.05078840255737, 52 | 38.89188459252509 53 | ], 54 | [ 55 | -77.05074548721312, 56 | 38.89270294635079 57 | ], 58 | [ 59 | -77.03965187072754, 60 | 38.89243572981179 61 | ], 62 | [ 63 | -77.03988790512084, 64 | 38.900501886178 65 | ] 66 | ] 67 | ] 68 | } 69 | } 70 | ] 71 | } 72 | -------------------------------------------------------------------------------- /tests/fixtures/new_user_motorway.json: -------------------------------------------------------------------------------- 1 | { 2 | "compareFunction": "new_user_motorway", 3 | "fixtures": [ 4 | { 5 | "description": "checks for a motorway added by new user", 6 | "newVersion": { 7 | "type": "Feature", 8 | "properties": { 9 | "highway": "motorway", 10 | "osm:type": "way", 11 | "osm:version": 1, 12 | "osm:user:changesetcount": 1 13 | }, 14 | "geometry": { 15 | "type": "LineString", 16 | "coordinates": [ 17 | [ 18 | 77.53283500671385, 19 | 13.032864653055398 20 | ], 21 | [ 22 | 77.53068923950195, 23 | 13.03428618497674 24 | ], 25 | [ 26 | 77.5285005569458, 27 | 13.035080566906114 28 | ], 29 | [ 30 | 77.52635478973389, 31 | 13.035665899327737 32 | ] 33 | ] 34 | } 35 | }, 36 | "oldVersion": { 37 | "type": "Feature", 38 | "properties": {}, 39 | "geometry": { 40 | "type": "LineString", 41 | "coordinates": [ 42 | [ 43 | 77.53283500671385, 44 | 13.032864653055398 45 | ], 46 | [ 47 | 77.53068923950195, 48 | 13.03428618497674 49 | ], 50 | [ 51 | 77.5285005569458, 52 | 13.035080566906114 53 | ], 54 | [ 55 | 77.52635478973389, 56 | 13.035665899327737 57 | ], 58 | [ 59 | 77.52506732940674, 60 | 13.036251230364918 61 | ] 62 | ] 63 | } 64 | }, 65 | "expectedResult": {"result:new_user_motorway":true} 66 | } 67 | ] 68 | } 69 | -------------------------------------------------------------------------------- /tests/fixtures/new_user_pokemon_nest.json: -------------------------------------------------------------------------------- 1 | { 2 | "compareFunction": "new_user_pokemon_nest", 3 | "fixtures": [ 4 | { 5 | "description": "checks for a motorway added by new user", 6 | "newVersion": { 7 | "type": "Feature", 8 | "properties": { 9 | "leisure": "park", 10 | "osm:type": "way", 11 | "osm:version": 1, 12 | "osm:user:changesetcount": 1 13 | }, 14 | "geometry": { 15 | "type": "LineString", 16 | "coordinates": [ 17 | [ 18 | 77.53283500671385, 19 | 13.032864653055398 20 | ], 21 | [ 22 | 77.53068923950195, 23 | 13.03428618497674 24 | ], 25 | [ 26 | 77.5285005569458, 27 | 13.035080566906114 28 | ], 29 | [ 30 | 77.52635478973389, 31 | 13.035665899327737 32 | ] 33 | ] 34 | } 35 | }, 36 | "oldVersion": { 37 | "type": "Feature", 38 | "properties": {}, 39 | "geometry": { 40 | "type": "LineString", 41 | "coordinates": [ 42 | [ 43 | 77.53283500671385, 44 | 13.032864653055398 45 | ], 46 | [ 47 | 77.53068923950195, 48 | 13.03428618497674 49 | ], 50 | [ 51 | 77.5285005569458, 52 | 13.035080566906114 53 | ], 54 | [ 55 | 77.52635478973389, 56 | 13.035665899327737 57 | ], 58 | [ 59 | 77.52506732940674, 60 | 13.036251230364918 61 | ] 62 | ] 63 | } 64 | }, 65 | "expectedResult": {"result:new_user_pokemon_nest":true} 66 | } 67 | ] 68 | } 69 | -------------------------------------------------------------------------------- /tests/fixtures/new_user_water.json: -------------------------------------------------------------------------------- 1 | { 2 | "compareFunction": "new_user_water", 3 | "fixtures": [ 4 | { 5 | "description": "water body created by new user", 6 | "newVersion": { 7 | "type": "Feature", 8 | "properties": { 9 | "highway": "motorway", 10 | "osm:type": "way", 11 | "osm:version": 1, 12 | "osm:user:changesetcount": 1, 13 | "natural": "water" 14 | }, 15 | "geometry": { 16 | "type": "LineString", 17 | "coordinates": [ 18 | [ 19 | 77.53283500671385, 20 | 13.032864653055398 21 | ], 22 | [ 23 | 77.53068923950195, 24 | 13.03428618497674 25 | ], 26 | [ 27 | 77.5285005569458, 28 | 13.035080566906114 29 | ], 30 | [ 31 | 77.52635478973389, 32 | 13.035665899327737 33 | ] 34 | ] 35 | } 36 | }, 37 | "oldVersion": { 38 | "type": "Feature", 39 | "properties": {}, 40 | "geometry": { 41 | "type": "LineString", 42 | "coordinates": [ 43 | [ 44 | 77.53283500671385, 45 | 13.032864653055398 46 | ], 47 | [ 48 | 77.53068923950195, 49 | 13.03428618497674 50 | ], 51 | [ 52 | 77.5285005569458, 53 | 13.035080566906114 54 | ], 55 | [ 56 | 77.52635478973389, 57 | 13.035665899327737 58 | ], 59 | [ 60 | 77.52506732940674, 61 | 13.036251230364918 62 | ] 63 | ] 64 | } 65 | }, 66 | "expectedResult": {"result:new_user_water": true} 67 | } 68 | ] 69 | } 70 | -------------------------------------------------------------------------------- /comparators/major_road_changed.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var TRIGGER_AFTER_VERSION = 10; 4 | 5 | var MAJOR_ROAD_TYPES = [ 6 | 'motorway', 7 | 'trunk', 8 | 'primary', 9 | 'secondary', 10 | 'tertiary', 11 | 'motorway_link', 12 | 'trunk_link', 13 | 'primary_link', 14 | 'secondary_link', 15 | 'tertiary_link' 16 | ]; 17 | 18 | function getHighwayType(feature) { 19 | return feature.properties.highway; 20 | } 21 | 22 | function getVersion(feature) { 23 | return feature.properties['osm:version']; 24 | } 25 | 26 | function isMajorRoad(feature) { 27 | var highwayType = getHighwayType(feature); 28 | return MAJOR_ROAD_TYPES.indexOf(highwayType) !== -1; 29 | } 30 | 31 | function majorRoadChanged(newVersion, oldVersion) { 32 | if (!oldVersion && newVersion.deleted) { 33 | return false; 34 | } 35 | 36 | if (!oldVersion && !newVersion.deleted) { 37 | return false; 38 | } 39 | 40 | var oldVersionNumber = getVersion(oldVersion); 41 | var oldHighwayType = getHighwayType(oldVersion); 42 | 43 | if (oldVersion && !newVersion.deleted) { 44 | var newVersionNumber = getVersion(newVersion); 45 | var newHighwayType = getHighwayType(newVersion); 46 | 47 | if (oldVersionNumber > TRIGGER_AFTER_VERSION && isMajorRoad(oldVersion)) { 48 | if (oldHighwayType !== newHighwayType) { 49 | return { 50 | 'result:major_road_changed': { 51 | modified: true, 52 | from: oldHighwayType, 53 | to: newHighwayType 54 | } 55 | }; 56 | } 57 | } 58 | } 59 | 60 | if (oldVersion && newVersion.deleted) { 61 | if (oldVersionNumber > TRIGGER_AFTER_VERSION && isMajorRoad(oldVersion)) { 62 | return { 63 | 'result:major_road_changed': { 64 | deleted: true, 65 | version: oldVersionNumber 66 | } 67 | }; 68 | } 69 | } 70 | 71 | return false; 72 | } 73 | 74 | module.exports = majorRoadChanged; 75 | -------------------------------------------------------------------------------- /tests/fixtures/new_user_motorway_deleted.json: -------------------------------------------------------------------------------- 1 | { 2 | "compareFunction": "new_user_motorway_deleted", 3 | "fixtures": [ 4 | { 5 | "description": "checks for a motorway deleted by a new user", 6 | "newVersion": { 7 | "type": "Feature", 8 | "properties": { 9 | "highway": "motorway", 10 | "osm:type": "way", 11 | "osm:version": 1, 12 | "osm:user:changesetcount": 10 13 | }, 14 | "geometry": { 15 | "type": "LineString", 16 | "coordinates": [ 17 | [ 18 | 77.53283500671385, 19 | 13.032864653055398 20 | ], 21 | [ 22 | 77.53068923950195, 23 | 13.03428618497674 24 | ], 25 | [ 26 | 77.5285005569458, 27 | 13.035080566906114 28 | ], 29 | [ 30 | 77.52635478973389, 31 | 13.035665899327737 32 | ] 33 | ] 34 | }, 35 | "deleted" : true 36 | }, 37 | "oldVersion": { 38 | "type": "Feature", 39 | "properties": {}, 40 | "geometry": { 41 | "type": "LineString", 42 | "coordinates": [ 43 | [ 44 | 77.53283500671385, 45 | 13.032864653055398 46 | ], 47 | [ 48 | 77.53068923950195, 49 | 13.03428618497674 50 | ], 51 | [ 52 | 77.5285005569458, 53 | 13.035080566906114 54 | ], 55 | [ 56 | 77.52635478973389, 57 | 13.035665899327737 58 | ], 59 | [ 60 | 77.52506732940674, 61 | 13.036251230364918 62 | ] 63 | ] 64 | } 65 | }, 66 | "expectedResult": {"result:new_user_motorway_deleted":true} 67 | } 68 | ] 69 | } 70 | -------------------------------------------------------------------------------- /comparators/name_modified.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var isEmpty = require('lodash.isempty'); 4 | 5 | var compareJSON = function(obj1, obj2) { 6 | var ret = {}; 7 | for (var i in obj2) { 8 | if (!obj1.hasOwnProperty(i) || obj2[i] !== obj1[i]) { 9 | ret[i] = obj2[i]; 10 | } 11 | } 12 | return ret; 13 | }; 14 | 15 | /** 16 | * Checks for existence of name tag and if it is modified between old and new version, it callbacks with the result. 17 | * @param {object} newVersion Features new version in GeoJSON. 18 | * @param {object} oldVersion Features old version in GeoJSON. 19 | * @param {Function} callback called with (error, result). 20 | * @returns {undefined} calls callback. 21 | */ 22 | function name_modified(newVersion, oldVersion) { 23 | var result = {}; 24 | var cfVersion = 2; 25 | 26 | if (newVersion.deleted || !oldVersion) { 27 | return result; 28 | } 29 | var newVersionNames = {}; 30 | var oldVersionNames = {}; 31 | 32 | if (newVersion.properties) { 33 | var props = newVersion.properties; 34 | for (var prop in props) { 35 | if (prop.indexOf('name') === 0) { 36 | newVersionNames[prop] = props[prop]; 37 | } 38 | } 39 | } 40 | 41 | if (oldVersion.properties) { 42 | props = oldVersion.properties; 43 | for (prop in props) { 44 | if (prop.indexOf('name') === 0) { 45 | oldVersionNames[prop] = props[prop]; 46 | } 47 | } 48 | } 49 | 50 | var arr = {}; 51 | arr = compareJSON(oldVersionNames, newVersionNames); 52 | 53 | result['result:name_modified'] = {}; 54 | 55 | for (var obj in arr) { 56 | result['result:name_modified'][obj] = 1; 57 | } 58 | 59 | arr = compareJSON(newVersionNames, oldVersionNames); 60 | 61 | for (obj in arr) { 62 | result['result:name_modified'][obj] = 1; 63 | } 64 | if (isEmpty(result['result:name_modified'])) { 65 | result = false; 66 | } 67 | return result; 68 | } 69 | 70 | module.exports = name_modified; 71 | -------------------------------------------------------------------------------- /tests/test_compare_function.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var fs = require('fs'); 4 | var path = require('path'); 5 | var comparators = require('../index'); 6 | var nock = require('nock'); 7 | var clc = require('cli-color'); 8 | 9 | (function() { 10 | if (process.argv.length !== 3) { 11 | console.log('\nUsage: node test_compare_function.js fixture_filename\n'); 12 | return; 13 | } 14 | 15 | nock('http://a.tiles.mapbox.com/v4/mapbox.mapbox-streets-v7') 16 | .get( 17 | '/16/46886/30383.mvt?access_token=pk.eyJ1IjoiYW1pc2hhIiwiYSI6ImNqaHVibzk5ZjBpaGQzcW9uanRrZG1kdTMifQ.YfJF2hRjuxM3FnRghgV7yw' 18 | ) 19 | .reply( 20 | 200, 21 | fs.readFileSync(path.join(__dirname, 'result_feature_overlap.pbf')) 22 | ); 23 | 24 | var filename = process.argv[2]; 25 | var jsonData = JSON.parse(fs.readFileSync(path.join('.', filename), 'utf-8')); 26 | var compareFunction = comparators[jsonData.compareFunction]; 27 | if (typeof compareFunction !== 'function') { 28 | console.log(jsonData.compareFunction); 29 | return; 30 | } 31 | 32 | jsonData.fixtures.forEach(function(fixture) { 33 | compareFunction( 34 | fixture.newVersion, 35 | fixture.oldVersion, 36 | function(error, result) { 37 | console.log(fixture.description); 38 | console.log(clc.yellow('expected', fixture.expectedResult)); 39 | if (error) console.log(clc.red(error)); 40 | console.log(clc.yellow('actual', JSON.stringify(result))); 41 | if ( 42 | JSON.stringify(fixture.expectedResult) !== 43 | JSON.stringify(removeMessage(result)) 44 | ) { 45 | console.log(clc.red('Test FAILED! Actual is not expected!\n')); 46 | } else { 47 | console.log( 48 | clc.green('OK! Test passed! Actual is same as expected\n') 49 | ); 50 | } 51 | } 52 | ); 53 | }); 54 | })(); 55 | 56 | function removeMessage(result) { 57 | if (result.message) { 58 | delete result.message; 59 | } 60 | return result; 61 | } 62 | -------------------------------------------------------------------------------- /lib/compare_all.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var queue = require('d3-queue').queue; 3 | var landmarkScore = require('../comparators/landmark_score.js'); 4 | var nameModified = require('../comparators/name_modified.js'); 5 | var disputedBorderTagChanged = require('../comparators/disputed-border-tag-changed.js'); 6 | var disputedBorderDeleted = require('../comparators/disputed-border-deleted.js'); 7 | var invalidHighwayTags = require('../comparators/invalid-highway-tags.js'); 8 | var largeBuilding = require('../comparators/large-building.js'); 9 | var majorRoadChanged = require('../comparators/major_road_changed.js'); 10 | var pathRoadChanged = require('../comparators/path_road_changed.js'); 11 | 12 | var extend = require('util')._extend; 13 | 14 | /** 15 | * Runs all compare functions defined as comparators and calls a callback with a results object. 16 | * @param {Object} newVersion - GeoJSON for new version of feature 17 | * @param {Object} oldVersion - GeoJSON for old version of feature 18 | * @param {compareAllCallback} callback - callback that is called with results object 19 | * @returns {Object} Result from all compare functions. 20 | */ 21 | var compareAll = function(newVersion, oldVersion, callback) { 22 | var q = queue(4); 23 | var comparators = [ 24 | landmarkScore, 25 | nameModified, 26 | disputedBorderTagChanged, 27 | disputedBorderDeleted, 28 | invalidHighwayTags, 29 | largeBuilding, 30 | majorRoadChanged, 31 | pathRoadChanged 32 | ]; 33 | comparators.forEach(function(comparator) { 34 | q.defer(comparator, newVersion, oldVersion); 35 | }); 36 | q.awaitAll(function(err, results) { 37 | if (err) { 38 | callback(err); 39 | } 40 | 41 | callback( 42 | err, 43 | results.reduce( 44 | function(problems, result) { 45 | return extend(problems, result); 46 | }, 47 | {} 48 | ) 49 | ); 50 | }); 51 | }; 52 | 53 | /** 54 | * @callback compareAllCallback 55 | * @param {Error} error - error, if any, else null 56 | * @param {Object} result - object containing all result keys as returns from compare functions 57 | */ 58 | 59 | module.exports = compareAll; 60 | -------------------------------------------------------------------------------- /tests/fixtures/destination_ref_changed.json: -------------------------------------------------------------------------------- 1 | { 2 | "compareFunction": "destination_ref_changed", 3 | "fixtures": [ 4 | { 5 | "description": "Case:1 motorway destination modified. Should be flagged", 6 | "newVersion": { 7 | "type": "Feature", 8 | "id": "way!90800153!24", 9 | "properties": { 10 | "highway": "motorway", 11 | "destination": "Seattle", 12 | "destination:ref": "I 23", 13 | "osm:user": "nammala" 14 | }, 15 | "geometry": { 16 | "type": "LineString", 17 | "coordinates": [ 18 | [ 19 | 83.3348845, 20 | 17.7221054 21 | ], 22 | [ 23 | 83.3346842, 24 | 17.7221352 25 | ], 26 | [ 27 | 83.3342895, 28 | 17.7221956 29 | ], 30 | [ 31 | 83.3339606, 32 | 17.7222435 33 | ], 34 | [ 35 | 83.3337141, 36 | 17.7222982 37 | ] 38 | ] 39 | } 40 | }, 41 | "oldVersion": { 42 | "type": "Feature", 43 | "id": "way!90800153!24", 44 | "properties": { 45 | "highway": "motorway", 46 | "destination": "New York", 47 | "destionation:ref": "I 24", 48 | "osm:user": "nammala" 49 | }, 50 | "geometry": { 51 | "type": "LineString", 52 | "coordinates": [ 53 | [ 54 | 73.3348845, 55 | 17.7221054 56 | ], 57 | [ 58 | 83.3346842, 59 | 17.7221352 60 | ], 61 | [ 62 | 83.3342895, 63 | 17.7221956 64 | ], 65 | [ 66 | 83.3339606, 67 | 17.7222435 68 | ], 69 | [ 70 | 83.3337141, 71 | 17.7222982 72 | ] 73 | ] 74 | } 75 | }, 76 | "expectedResult": { 77 | "result:destination_ref_changed": true 78 | } 79 | } 80 | ] 81 | } -------------------------------------------------------------------------------- /forked/naughty-words/README.md: -------------------------------------------------------------------------------- 1 | # Our List of Dirty, Naughty, Obscene, and Otherwise Bad Words # 2 | 3 | With millions of images in our library and billions of user-submitted keywords, we work hard at Shutterstock to make sure that bad words don't show up in places they shouldn't. This repo contains a list of words that we use to filter results from our autocomplete server and recommendation engine. 4 | 5 | Please add to it as you see fit (particularly in non-English languages) or use it to spice up your next game of Scrabble :) 6 | 7 | Obvious warning: These lists contain material that many will find offensive. (But that's the point!) 8 | 9 | Miscellaneous caveat: Clearly, what goes in these lists is subjective. In our case, the question we use is, "What wouldn't we want to *suggest* that people look at?" This of course varies between culture, language, and geographies, so in the end we just have to make our best guess. 10 | 11 | ## Languages 12 | 13 | | Name | Code | 14 | | ---------------- | ---- | 15 | | [Arabic](ar) | ar | 16 | | [Chinese](zh) | zh | 17 | | [Czech](cs) | cs | 18 | | [Danish](da) | da | 19 | | [Dutch](nl) | nl | 20 | | [English](en) | en | 21 | | [Esperanto](eo) | eo | 22 | | [Finnish](fi) | fi | 23 | | [French](fr) | fr | 24 | | [German](de) | de | 25 | | [Hindi](hi) | hi | 26 | | [Hungarian](hu) | hu | 27 | | [Italian](it) | it | 28 | | [Japanese](ja) | ja | 29 | | [Klingon](tlh) | tlh | 30 | | [Korean](ko) | ko | 31 | | [Norwegian](no) | no | 32 | | [Persian](fa) | fa | 33 | | [Polish](pl) | pl | 34 | | [Portuguese](pt) | pt | 35 | | [Russian](ru) | ru | 36 | | [Spanish](es) | es | 37 | | [Swedish](sv) | sv | 38 | | [Thai](th) | th | 39 | | [Turkish](tr) | tr | 40 | 41 | See also the [list of projects, documents, and organizations](USERS.md) that use these lists. 42 | 43 | © 2012–2015 Shutterstock, Inc. 44 | 45 | [![Creative Commons License](http://i.creativecommons.org/l/by/4.0/80x15.png)](http://creativecommons.org/licenses/by/4.0/) 46 | 47 | This work is licensed under a [Creative Commons Attribution 4.0 International License](http://creativecommons.org/licenses/by/4.0/). 48 | -------------------------------------------------------------------------------- /comparators/straight_segment.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const ruler = require('cheap-ruler'); 3 | 4 | module.exports = straightSegment; 5 | 6 | function straightSegment(newVersion, oldVersion) { 7 | if (newVersion.deleted || !newVersion.geometry) return false; 8 | 9 | const geom = newVersion.geometry; 10 | if (geom.type === 'Point') return false; 11 | 12 | if (newVersion.properties['hires'] || newVersion.properties['hires:imagery']) 13 | return false; 14 | if (newVersion.properties['route'] === 'ferry') return false; 15 | if ( 16 | newVersion.properties['natural'] === 'coastline' || 17 | newVersion.properties['natural'] === 'reserve' 18 | ) 19 | return false; 20 | if ( 21 | newVersion.properties['boundary'] === 'administrative' || 22 | newVersion.properties['boundary'] === 'historic' || 23 | newVersion.properties['boundary'] === 'maritime' 24 | ) 25 | return false; 26 | if (newVersion.properties['power']) return false; 27 | 28 | const threshold = newVersion.properties.boundary ? 400e3 : 50e3; 29 | 30 | function checkSegments(coords) { 31 | const measure = ruler(coords[0][1], 'meters'); 32 | for (let i = 1; i < coords.length; i++) { 33 | const segment = [coords[i], coords[i - 1]]; 34 | const distance = measure.lineDistance(segment, 'meters'); 35 | if (distance > threshold) { 36 | return true; 37 | } 38 | } 39 | return false; 40 | } 41 | 42 | if (geom.type === 'Polygon') { 43 | const rings = geom.coordinates; 44 | const incidents = rings 45 | .map(ring => checkSegments(ring)) 46 | .filter(incident => !!incident); 47 | if (incidents.length) return {'result:straight_segment': true}; 48 | } 49 | 50 | if (geom.type === 'MultiPolygon') { 51 | const incidents = [].concat 52 | .apply( 53 | [], 54 | geom.coordinates.map(polygon => 55 | polygon.map(ring => checkSegments(ring))) 56 | ) 57 | .filter(incident => !!incident); 58 | if (incidents.length) return {'result:straight_segment': true}; 59 | } 60 | 61 | if (geom.type === 'LineString') { 62 | const incident = checkSegments(geom.coordinates); 63 | if (incident) return {'result:straight_segment': true}; 64 | } 65 | 66 | return false; 67 | } 68 | -------------------------------------------------------------------------------- /scripts/save-water/get-lakes.js: -------------------------------------------------------------------------------- 1 | var osmium = require('osmium'); 2 | var argv = require('minimist')(process.argv.slice(2)); 3 | var turf = require('@turf/turf'); 4 | 5 | if (!argv.pbf) { 6 | console.log(''); 7 | console.log('node get-lakes.js OPTIONS'); 8 | console.log(''); 9 | console.log(' OPTIONS'); 10 | console.log(' --pbf osm-planet.pbf'); 11 | console.log(''); 12 | return; 13 | } 14 | 15 | var file = new osmium.File(argv.pbf); 16 | var location_handler = new osmium.LocationHandler(); 17 | var stream = new osmium.Stream(new osmium.Reader(file, location_handler)); 18 | 19 | var counts = { node: 0, way: 0, relation: 0, other: 0 }; 20 | stream.on('data', function(object) { 21 | var feature = { 22 | 'type': 'Feature', 23 | 'geometry': undefined, 24 | 'properties': undefined 25 | }; 26 | 27 | try { 28 | var type = object['type']; 29 | 30 | var version = object['version']; 31 | if (version !== 1) return; 32 | 33 | var properties = object.tags(); 34 | if (!(('water' in properties) && (properties['water'] === 'lake'))) return; 35 | 36 | // Add other properties from object to feature. 37 | for (key in object) properties[key] = object[key]; 38 | feature['properties'] = properties; 39 | 40 | // If an area feature is from a way, skip and catch the LineString version of the feature. 41 | if (type === 'area' && properties.hasOwnProperty('from_way') && (properties['from_way'] === true)) return; 42 | 43 | var geometry = object.geojson(); 44 | feature['geometry'] = geometry; 45 | 46 | // If feature is a way and geometry type is a LineString, try and make it a polygon 47 | try { 48 | // Throws an error if LineString cannot be converted to a Polygon. 49 | geometry = turf.polygon([feature['geometry']['coordinates']])['geometry']; 50 | feature['geometry'] = geometry; 51 | } catch (error) { 52 | // Nothing to do as the feature already has a default geometry. 53 | } 54 | 55 | console.log(JSON.stringify(feature)); 56 | } catch (error) { 57 | // Nothing to do for now. 58 | } 59 | }); 60 | 61 | stream.on('end', function() { 62 | }); 63 | -------------------------------------------------------------------------------- /comparators/modified_survey_point.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = modifiedSurveyPoint; 4 | 5 | function modifiedSurveyPoint(newVersion, oldVersion) { 6 | if (newVersion.deleted && !oldVersion) { 7 | // None of old version or new Version present 8 | return false; 9 | } 10 | 11 | // checks for changes 12 | if ( 13 | newVersion && 14 | !newVersion.deleted && 15 | oldVersion && 16 | oldVersion.properties && 17 | oldVersion.properties.hasOwnProperty('man_made') && 18 | oldVersion.properties.man_made === 'survey_point' 19 | ) { 20 | if ( 21 | newVersion.geometry && 22 | oldVersion.geometry && 23 | (newVersion.geometry.coordinates[0] !== 24 | oldVersion.geometry.coordinates[0] || 25 | newVersion.geometry.coordinates[1] !== 26 | oldVersion.geometry.coordinates[1]) 27 | ) { 28 | // survey_point was moved 29 | return {'result:modified_survey_point': true}; 30 | } 31 | if ( 32 | newVersion.properties && 33 | (!newVersion.properties.hasOwnProperty('man_made') || 34 | newVersion.properties.man_made !== 'survey_point') 35 | ) { 36 | // survey_point lost its main tag 37 | return {'result:modified_survey_point': true}; 38 | } 39 | if ( 40 | newVersion.properties && 41 | oldVersion.properties.hasOwnProperty('ele') && 42 | (!newVersion.properties.ele || 43 | oldVersion.properties.ele !== newVersion.properties.ele) 44 | ) { 45 | // survey_point lost or changed its ele tag 46 | return {'result:modified_survey_point': true}; 47 | } 48 | if ( 49 | newVersion.properties && 50 | oldVersion.properties.hasOwnProperty('ref') && 51 | (!newVersion.properties.ref || 52 | oldVersion.properties.ref !== newVersion.properties.ref) 53 | ) { 54 | // survey_point lost or changed its ref tag 55 | return {'result:modified_survey_point': true}; 56 | } 57 | } 58 | 59 | if ( 60 | oldVersion && 61 | newVersion.deleted && 62 | oldVersion.properties && 63 | oldVersion.properties.hasOwnProperty('man_made') && 64 | oldVersion.properties.man_made === 'survey_point' 65 | ) { 66 | return {'result:modified_survey_point': true}; 67 | } 68 | return false; 69 | } 70 | -------------------------------------------------------------------------------- /tests/fixtures/new_user_large_building.json: -------------------------------------------------------------------------------- 1 | { 2 | "compareFunction": "new_user_large_building", 3 | "fixtures": [ 4 | { 5 | "description": "checks for a large building created by new user", 6 | "newVersion": { 7 | "type": "Feature", 8 | "properties": { 9 | "building": "apartment", 10 | "osm:version" : 1, 11 | "osm:type": "way", 12 | "osm:user:changesetcount": 1 13 | }, 14 | "geometry": { 15 | "type": "Polygon", 16 | "coordinates": [ 17 | [ 18 | [136.7253597, 35.3592342], 19 | [136.724114, 35.359857], 20 | [136.723419, 35.360471], 21 | [136.7208322, 35.3598666], 22 | [136.7200165, 35.3598963], 23 | [136.719433, 35.3621494], 24 | [136.7195264, 35.3629567], 25 | [136.7210022, 35.3630405], 26 | [136.7220015, 35.3638327], 27 | [136.7221486, 35.3644496], 28 | [136.7217176, 35.3650251], 29 | [136.7195192, 35.3650408], 30 | [136.7196998, 35.3660636], 31 | [136.7208621, 35.3665899], 32 | [136.7219745, 35.3665958], 33 | [136.7220971, 35.367028], 34 | [136.7197327, 35.3681801], 35 | [136.7205912, 35.3696592], 36 | [136.7202412, 35.3709461], 37 | [136.7207406, 35.3721421], 38 | [136.7215999, 35.3741755], 39 | [136.7203577, 35.3740613], 40 | [136.720271, 35.374328], 41 | [136.721714, 35.375356], 42 | [136.7207593, 35.3755692], 43 | [136.7208154, 35.3759575], 44 | [136.7214423, 35.3762736], 45 | [136.7221465, 35.37699], 46 | [136.7239415, 35.3718394], 47 | [136.7237732, 35.3708857], 48 | [136.7231784, 35.3707483], 49 | [136.7231888, 35.3696203], 50 | [136.7241591, 35.3675495], 51 | [136.725213, 35.362314], 52 | [136.72306, 35.3612811], 53 | [136.7238135, 35.360527], 54 | [136.7253437, 35.3611792], 55 | [136.72612, 35.35958], 56 | [136.7253597, 35.3592342] 57 | ] 58 | ] 59 | } 60 | }, 61 | "oldVersion": null, 62 | "expectedResult": {"result:new_user_large_building":true} 63 | } 64 | ] 65 | } 66 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ⚠️ This repo is archived because it is no longer used and no stacks are running. 2 | Previously used only in transferred and discontinued mapbox/osmcha project. 3 | Shutdown [ticket in Jira](https://mapbox.atlassian.net/browse/RTSR-479) for reference. 4 | 5 | --- 6 | 7 | # osm-compare 8 | 9 | 10 | ![](https://img.shields.io/npm/v/@mapbox/osm-compare.svg) 11 | [![Travis CI](https://travis-ci.com/mapbox/osm-compare.svg?branch=master)](https://travis-ci.com/github/mapbox/osm-compare) 12 | 13 | 14 | Compare functions are small atomic functions that are designed to identify what changed during a feature edit on OpenStreetMap. Compare functions can be broadly split up into two categories: 15 | 16 | 1. Property (tags) checking compare function 17 | 2. Geometry checking compare functions 18 | 19 | Compare functions take as inputs the following: 20 | 21 | 1. `oldVersion` - GeoJSON of the feature's old version 22 | 2. `newVersion` - GeoJSON of the feature's new version 23 | 24 | Compare functions output the following: 25 | 26 | 1. `result` - Object containing key value pairs representing findings of the compare function or an empty object. 27 | 28 | ```sh 29 | # Format of compare function result where value can be primary data types or objects 30 | { 31 | 'result:comparator_name': value, 32 | 'message': Any custom message which corresponds to the catch 33 | } 34 | 35 | # Format of compare function if no result, (default) 36 | false 37 | 38 | ``` 39 | 40 | ## Install 41 | 42 | ```sh 43 | # Install osm-compare from the Mapbox namespace. 44 | npm install @mapbox/osm-compare 45 | ``` 46 | 47 | 48 | ### Docs 49 | 50 | - [How do I create a new compare function](https://github.com/mapbox/osm-compare/blob/master/docs/new-compare-function.md) 51 | - [Sample compare function](https://github.com/mapbox/osm-compare/blob/master/example) 52 | - [What does each compare function do](https://github.com/mapbox/osm-compare/blob/master/comparators/README.md) 53 | 54 | 55 | ### How do I build an npm package? 56 | - We use [Semantic Versioning Specification](http://semver.org/) for versioning releases. 57 | - Create an appropriate version of the npm package with `npm version [major|minor|patch]`. 58 | - Push the package tag commit with `git push --tags` 59 | - [Publish the NPM package](https://www.npmjs.com/package/osm-compare) with `npm publish` 60 | -------------------------------------------------------------------------------- /tests/fixtures/features/node-268436671-13.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "FeatureCollection", 3 | "properties": { 4 | "result:count_tag": 1, 5 | "result:significant_place": 195, 6 | "result:landmark_score": { 7 | "cfVersion": 2, 8 | "score": 3.4 9 | } 10 | }, 11 | "features": [ 12 | { 13 | "type": "Feature", 14 | "id": "node!268436671!13", 15 | "properties": { 16 | "is_in": "Slovenia, Europe", 17 | "is_in:continent": "Europe", 18 | "is_in:country": "Slovenia", 19 | "is_in:country_code": "SI", 20 | "name": "Ljutomer", 21 | "name:de": "Luttenberg in der Steiermark", 22 | "name:ru": "Лютомер", 23 | "name:sl": "Ljutomer", 24 | "place": "town", 25 | "population": "3400", 26 | "source:population": "http://www.stat.si/KrajevnaImena/", 27 | "wikipedia": "sl:Ljutomer", 28 | "osm:type": "node", 29 | "osm:id": 268436671, 30 | "osm:version": 13, 31 | "osm:changeset": 37946199, 32 | "osm:timestamp": 1458423454000, 33 | "osm:uid": 127299, 34 | "osm:user": "Batareikin" 35 | }, 36 | "geometry": { 37 | "type": "Point", 38 | "coordinates": [ 39 | 16.1967404, 40 | 46.5187046 41 | ] 42 | } 43 | }, 44 | { 45 | "type": "Feature", 46 | "id": "node!268436671!12", 47 | "properties": { 48 | "name": "Ljutomer", 49 | "is_in": "Slovenia, Europe", 50 | "place": "town", 51 | "name:de": "Luttenberg in der Steiermark", 52 | "name:sl": "Ljutomer", 53 | "wikipedia": "sl:Ljutomer", 54 | "population": "3400", 55 | "is_in:country": "Slovenia", 56 | "is_in:continent": "Europe", 57 | "source:population": "http://www.stat.si/KrajevnaImena/", 58 | "is_in:country_code": "SI", 59 | "osm:type": "node", 60 | "osm:id": 268436671, 61 | "osm:version": 12, 62 | "osm:changeset": 27920813, 63 | "osm:timestamp": 1420406991000, 64 | "osm:uid": 2146151, 65 | "osm:user": "Joško Horvat" 66 | }, 67 | "geometry": { 68 | "type": "Point", 69 | "coordinates": [ 70 | 16.1967404, 71 | 46.5187046 72 | ] 73 | } 74 | } 75 | ] 76 | } 77 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@mapbox/osm-compare", 3 | "version": "9.4.1", 4 | "description": "Compare a features new and old versions in GeoJSON", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "TAP_TIMEOUT=600 tap --coverage --no-check-coverage tests/*.js --test-ignore=tests/test_compare_function.js", 8 | "lint": "eslint ./*/*.js", 9 | "pretest": "npm run lint", 10 | "postinstall": "./scripts/download_common_tag_values.sh", 11 | "precommit": "lint-staged" 12 | }, 13 | "lint-staged": { 14 | "*.js": [ 15 | "prettier-eslint --write", 16 | "git add" 17 | ] 18 | }, 19 | "repository": { 20 | "type": "git", 21 | "url": "https://github.com/mapbox/osm-compare.git" 22 | }, 23 | "author": "Mapbox", 24 | "license": "ISC", 25 | "dependencies": { 26 | "@mapbox/vt2geojson": "^1.1.5", 27 | "@turf/area": "^5.1.5", 28 | "@turf/buffer": "^4.2.0", 29 | "@turf/difference": "^4.1.0", 30 | "@turf/line-intersect": "4.4.0", 31 | "attempt": "^1.0.1", 32 | "cheap-ruler": "^2.5.1", 33 | "csv": "^1.1.1", 34 | "d3-queue": "^3.0.7", 35 | "deep-equals": "0.0.2", 36 | "diff": "^3.5.0", 37 | "feature-filter": "^2.2.0", 38 | "geojson-polygon-self-intersections": "^1.1.1", 39 | "geopoint": "^1.0.1", 40 | "levenshtein": "^1.0.5", 41 | "lodash": "^4.17.3", 42 | "lodash.intersection": "^4.4.0", 43 | "lodash.isempty": "^4.4.0", 44 | "moment": "^2.17.1", 45 | "naughty-words": "^1.0.1", 46 | "nock": "^9.0.11", 47 | "osm-landmarks": "^0.3.1", 48 | "request": "^2.72.0", 49 | "sax": "^1.2.1", 50 | "sqlite3": "^4.1.1", 51 | "tile-cover": "^3.0.1", 52 | "turf-area": "^1.1.1", 53 | "turf-bbox-polygon": "^3.0.12", 54 | "turf-centroid": "^1.1.3", 55 | "turf-distance": "^1.1.0", 56 | "turf-envelope": "^1.0.2", 57 | "turf-featurecollection": "^1.0.1", 58 | "turf-inside": "^3.0.12", 59 | "turf-intersect": "^3.0.12", 60 | "turf-point": "^2.0.1", 61 | "turf-within": "^3.0.5" 62 | }, 63 | "devDependencies": { 64 | "cli-color": "^1.2.0", 65 | "eslint": "^4.18.2", 66 | "eslint-config-mourner": "^2.0.1", 67 | "husky": "^0.13.3", 68 | "lint-staged": "^3.4.0", 69 | "prettier": "^0.22.0", 70 | "prettier-eslint-cli": "^3.2.0", 71 | "tap": "^15.0.9" 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /tests/fixtures/features/node-158230039-14.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "FeatureCollection", 3 | "properties": { 4 | "result:significant_tag": true, 5 | "result:count_tag": 21, 6 | "result:significant_place": 420, 7 | "result:previously_deleted_feature": true 8 | }, 9 | "features": [ 10 | { 11 | "type": "Feature", 12 | "id": "node!158230039!14", 13 | "properties": { 14 | "alt_name:ru": "Баффало", 15 | "census:population": "276059;2006", 16 | "ele": "183", 17 | "gnis:Class": "Populated Place", 18 | "gnis:County": "Erie", 19 | "gnis:County_num": "029", 20 | "gnis:id": "973345", 21 | "gnis:ST_alpha": "NY", 22 | "gnis:ST_num": "36", 23 | "import_uuid": "bb7269ee-502a-5391-8056-e3ce0e66489c", 24 | "is_in": "Erie,New York,N.Y.,NY,USA", 25 | "is_in:continent": "North America", 26 | "is_in:country": "United States", 27 | "is_in:country_code": "US", 28 | "name": "Buffalo", 29 | "name:en": "Buffalo", 30 | "name:ru": "Буффало", 31 | "name:uk": "Баффало", 32 | "place": "city", 33 | "population": "276059", 34 | "wikidata": "Q40435", 35 | "osm:type": "node", 36 | "osm:id": 158230039, 37 | "osm:version": 14, 38 | "osm:changeset": 38027008, 39 | "osm:timestamp": 1458761173000, 40 | "osm:uid": 1306, 41 | "osm:user": "PlaneMad" 42 | }, 43 | "geometry": { 44 | "type": "Point", 45 | "coordinates": [ 46 | -78.8783689, 47 | 42.8864468 48 | ] 49 | } 50 | }, 51 | { 52 | "id": 158230039, 53 | "type": "node", 54 | "typeId": 1, 55 | "tags": {}, 56 | "refs": [], 57 | "version": 13, 58 | "timestamp": 1455477064000, 59 | "uid": 3627372, 60 | "user": "ELBORICUA61357", 61 | "changeset": 37210478, 62 | "lat": 42.8864468, 63 | "lon": -78.8783689, 64 | "deleted": true, 65 | "properties": { 66 | "osm:type": "node", 67 | "osm:id": 158230039, 68 | "osm:version": 13, 69 | "osm:changeset": 37210478, 70 | "osm:timestamp": 1455477064000, 71 | "osm:uid": 3627372, 72 | "osm:user": "ELBORICUA61357", 73 | "osm:action": "delete" 74 | } 75 | } 76 | ] 77 | } 78 | -------------------------------------------------------------------------------- /tests/fixtures/common.json: -------------------------------------------------------------------------------- 1 | { 2 | "compareFunction": "", 3 | "fixtures": [ 4 | { 5 | "description": "Test newVersion and oldVersion is null", 6 | "newVersion": {"deleted": true}, 7 | "oldVersion": null 8 | }, 9 | { 10 | "description": "Test oldVersion is null", 11 | "newVersion": { 12 | "properties": { 13 | "id": 158230039, 14 | "type": "node", 15 | "typeId": 1, 16 | "tags": {}, 17 | "refs": [], 18 | "version": 1, 19 | "timestamp": 1455477064000, 20 | "uid": 3627372, 21 | "user": "ELBORICUA61357", 22 | "changeset": 37210478, 23 | "lat": 42.8864468, 24 | "lon": -78.8783689 25 | } 26 | }, 27 | "oldVersion": null 28 | }, 29 | { 30 | "description": "Test newVersion is null", 31 | "newVersion": { 32 | "properties": { 33 | "id": 158230039, 34 | "type": "node", 35 | "typeId": 1, 36 | "tags": {}, 37 | "refs": [], 38 | "version": 13, 39 | "timestamp": 1455477064000, 40 | "uid": 3627372, 41 | "user": "ELBORICUA61357", 42 | "changeset": 37210478, 43 | "lat": 42.8864468, 44 | "lon": -78.8783689 45 | }, 46 | "deleted": true 47 | }, 48 | "oldVersion": { 49 | "properties": { 50 | "id": 158230039, 51 | "type": "node", 52 | "typeId": 1, 53 | "tags": {}, 54 | "refs": [], 55 | "version": 13, 56 | "timestamp": 1455477064000, 57 | "uid": 3627372, 58 | "user": "ELBORICUA61357", 59 | "changeset": 37210478, 60 | "lat": 42.8864468, 61 | "lon": -78.8783689 62 | } 63 | } 64 | } 65 | ] 66 | } 67 | -------------------------------------------------------------------------------- /tests/fixtures/features/way-110041164-3.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "FeatureCollection", 3 | "properties": { 4 | "result:location_changed": 0.12678764101667986, 5 | "result:significant_tag": true, 6 | "result:count_tag": 8 7 | }, 8 | "features": [ 9 | { 10 | "type": "Feature", 11 | "id": "way!110041164!3", 12 | "properties": { 13 | "highway": "motorway", 14 | "name": "日本海東北自動車道", 15 | "oneway": "no", 16 | "source": "YahooJapan/ALPSMAP", 17 | "osm:type": "way", 18 | "osm:id": 110041164, 19 | "osm:version": 3, 20 | "osm:changeset": 37949901, 21 | "osm:timestamp": 1458452022000, 22 | "osm:uid": 2392788, 23 | "osm:user": "shakingnine" 24 | }, 25 | "geometry": { 26 | "type": "LineString", 27 | "coordinates": [ 28 | [ 29 | 139.8028061, 30 | 38.8174395 31 | ], 32 | [ 33 | 139.8025477, 34 | 38.8189558 35 | ], 36 | [ 37 | 139.8024254, 38 | 38.8200311 39 | ], 40 | [ 41 | 139.802359, 42 | 38.821112 43 | ], 44 | [ 45 | 139.8023536, 46 | 38.822193 47 | ] 48 | ] 49 | } 50 | }, 51 | { 52 | "type": "Feature", 53 | "id": "way!110041164!2", 54 | "properties": { 55 | "name": "山形自動車道", 56 | "oneway": "no", 57 | "source": "YahooJapan/ALPSMAP", 58 | "highway": "motorway", 59 | "yh:TYPE": "高速自動車国道", 60 | "yh:WIDTH": "13.0m以上", 61 | "yh:LINE_NUM": "1020", 62 | "yh:LINE_NAME": "山形自動車道", 63 | "yh:STRUCTURE": "地上", 64 | "yh:TOTYUMONO": "供用中", 65 | "yh:WIDTH_RANK": "1", 66 | "osm:type": "way", 67 | "osm:id": 110041164, 68 | "osm:version": 2, 69 | "osm:changeset": 13605457, 70 | "osm:timestamp": 1350999648000, 71 | "osm:uid": 699554, 72 | "osm:user": "7dhd" 73 | }, 74 | "geometry": { 75 | "type": "LineString", 76 | "coordinates": [ 77 | [ 78 | 139.8028061, 79 | 38.8174395 80 | ], 81 | [ 82 | 139.8025477, 83 | 38.8189558 84 | ], 85 | [ 86 | 139.8024254, 87 | 38.8200311 88 | ] 89 | ] 90 | } 91 | } 92 | ] 93 | } -------------------------------------------------------------------------------- /comparators/modified_place_wikidata.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = modifiedPlaceWikidata; 4 | 5 | function modifiedPlaceWikidata(newVersion, oldVersion) { 6 | if (newVersion.deleted || !newVersion.properties) return false; 7 | if (!oldVersion || !oldVersion.properties) return false; 8 | // Assumption: Place features with edited wikidata tag need to flagged 9 | // Target: Finding suspicious edits that mistag wikidata value 10 | //This comparator will only look at modifications to Wikidata tags to features with place tag. 11 | 12 | // This if condition handles for new additions of place tag for features that have wikidata tag 13 | if ( 14 | !oldVersion.properties.hasOwnProperty('place') && 15 | 'wikidata' in newVersion.properties && 16 | 'place' in newVersion.properties 17 | ) { 18 | return { 19 | 'result:modified_place_wikidata': true 20 | }; 21 | } 22 | // Handles when a place feature is given a new wikidata tag 23 | if ( 24 | 'place' in oldVersion.properties && 25 | !oldVersion.properties.hasOwnProperty('wikidata') && 26 | 'place' in newVersion.properties && 27 | 'wikidata' in newVersion.properties 28 | ) { 29 | return { 30 | 'result:modified_place_wikidata': true 31 | }; 32 | } 33 | // Handles when a place feature is given a new wikidata tag 34 | if ( 35 | 'place' in oldVersion.properties && 36 | !oldVersion.properties.hasOwnProperty('wikidata') && 37 | 'place' in newVersion.properties && 38 | 'wikidata' in newVersion.properties 39 | ) { 40 | return { 41 | 'result:modified_place_wikidata': true 42 | }; 43 | } 44 | // Handles when wikidata tag is deleted for place features 45 | if ( 46 | 'place' in oldVersion.properties && 47 | 'wikidata' in oldVersion.properties && 48 | 'place' in newVersion.properties && 49 | !newVersion.properties.hasOwnProperty('wikidata') 50 | ) { 51 | return { 52 | 'result:modified_place_wikidata': true 53 | }; 54 | } 55 | // Handles modifications to wikidata tag for features with place tag 56 | if ( 57 | 'wikidata' in oldVersion.properties && 58 | 'wikidata' in newVersion.properties && 59 | 'place' in oldVersion.properties && 60 | 'place' in newVersion.properties 61 | ) { 62 | if (oldVersion.properties.wikidata !== newVersion.properties.wikidata) { 63 | return { 64 | 'result:modified_place_wikidata': true 65 | }; 66 | } 67 | } 68 | return false; 69 | } 70 | -------------------------------------------------------------------------------- /scripts/run_compare_function.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var fs = require('graceful-fs'); 4 | var path = require('path'); 5 | var queue = require('d3-queue').queue; 6 | var json2csv = require('json2csv'); 7 | var fields = ['oldVersion', 'newVersion', 'result']; 8 | var comparators = require('../index'); 9 | 10 | (function() { 11 | if (process.argv.length !== 5) { 12 | console.log( 13 | '\nUsage: node run_compare_function.js comparator_name path/to/fixturedump numberOfFixtures \n' 14 | ); 15 | return; 16 | } 17 | 18 | var compareFunction = comparators[process.argv[2]]; 19 | 20 | readFiles(process.argv[3]); 21 | var arr = []; 22 | var count = 0; 23 | function readFiles(dirname) { 24 | fs.readdir(dirname, function(err, filenames) { 25 | if (err) { 26 | console.log(err); 27 | return; 28 | } 29 | var q = queue(10); 30 | var files = filenames.slice(0, process.argv[4]); 31 | files.forEach(function(filename) { 32 | q.defer(readFile, dirname, filename); 33 | }); 34 | q.awaitAll(function(err, results) { 35 | if (err) { 36 | console.log(err); 37 | } 38 | try { 39 | var result = json2csv({data: arr, fields: fields}); 40 | console.log(result + '\n'); 41 | console.log(arr.length + ' / ' + process.argv[4] + ' Caught'); 42 | } catch (err) { 43 | console.error(err); 44 | } 45 | }); 46 | }); 47 | } 48 | 49 | function readFile(dirname, filename, callback) { 50 | fs.readFile(dirname + filename, 'utf-8', function(err, content) { 51 | if (err) { 52 | console.log(err); 53 | return; 54 | } 55 | content = JSON.parse(content); 56 | var oldVersion, newVersion; 57 | if (content['oldVersion']['type']) { 58 | oldVersion = content['oldVersion']; 59 | } else { 60 | oldVersion = null; 61 | } 62 | if (content['newVersion']['type']) { 63 | newVersion = content['newVersion']; 64 | } else { 65 | newVersion = null; 66 | } 67 | 68 | compareFunction(newVersion, oldVersion, function(error, result) { 69 | if (Object.keys(result).length !== 0) { 70 | var obj = { 71 | oldVersion: JSON.stringify(oldVersion), 72 | newVersion: JSON.stringify(newVersion), 73 | result: JSON.stringify(result) 74 | }; 75 | arr.push(obj); 76 | } 77 | callback(); 78 | }); 79 | }); 80 | } 81 | })(); 82 | -------------------------------------------------------------------------------- /forked/naughty-words/fi.json: -------------------------------------------------------------------------------- 1 | [ 2 | "alfred nussi", 3 | "bylsiä", 4 | "haahka", 5 | "haista paska", 6 | "haista vittu", 7 | "hatullinen", 8 | "helvetisti", 9 | "hevonkuusi", 10 | "hevonpaska", 11 | "hevonperse", 12 | "hevonvittu", 13 | "hevonvitunperse", 14 | "hitosti", 15 | "hitto", 16 | "huorata", 17 | "hässiä", 18 | "juosten kustu", 19 | "jutku", 20 | "jutsku", 21 | "jätkä", 22 | "kananpaska", 23 | "koiranpaska", 24 | "kuin esterin perseestä", 25 | "kulli", 26 | "kullinluikaus", 27 | "kuppainen", 28 | "kusaista", 29 | "kuseksia", 30 | "kusettaa", 31 | "kusi", 32 | "kusipää", 33 | "kusta", 34 | "kyrpiintynyt", 35 | "kyrpiintyä", 36 | "kyrpiä", 37 | "kyrpä", 38 | "kyrpänaama", 39 | "kyrvitys", 40 | "lahtari", 41 | "lutka", 42 | "molo", 43 | "molopää", 44 | "mulkero", 45 | "mulkku", 46 | "mulkvisti", 47 | "muna", 48 | "munapää", 49 | "munaton", 50 | "mutakuono", 51 | "mutiainen", 52 | "naida", 53 | "nainti", 54 | "narttu", 55 | "neekeri", 56 | "nekru", 57 | "nuolla persettä", 58 | "nussia", 59 | "nussija", 60 | "nussinta", 61 | "paljaalla", 62 | "palli", 63 | "pallit", 64 | "paneskella", 65 | "panettaa", 66 | "panna", 67 | "pano", 68 | "pantava", 69 | "paska", 70 | "paskainen", 71 | "paskamainen", 72 | "paskanmarjat", 73 | "paskantaa", 74 | "paskapuhe", 75 | "paskapää", 76 | "paskattaa", 77 | "paskiainen", 78 | "paskoa", 79 | "pehko", 80 | "pentele", 81 | "perkele", 82 | "perkeleesti", 83 | "persaukinen", 84 | "perse", 85 | "perseennuolija", 86 | "perseet olalla", 87 | "persereikä", 88 | "perseääliö", 89 | "persläpi", 90 | "perspano", 91 | "persvako", 92 | "pilkunnussija", 93 | "pillu", 94 | "pillut", 95 | "pipari", 96 | "piru", 97 | "pistää", 98 | "pyllyvako", 99 | "reikä", 100 | "reva", 101 | "ripsipiirakka", 102 | "runkata", 103 | "runkkari", 104 | "runkkaus", 105 | "runkku", 106 | "ryssä", 107 | "rättipää", 108 | "saatanasti", 109 | "suklaaosasto", 110 | "tavara", 111 | "toosa", 112 | "tuhkaluukku", 113 | "tumputtaa", 114 | "turpasauna", 115 | "tussu", 116 | "tussukka", 117 | "tussut", 118 | "vakipano", 119 | "vetää käteen", 120 | "viiksi", 121 | "vittu", 122 | "vittuilla", 123 | "vittuilu", 124 | "vittumainen", 125 | "vittuuntua", 126 | "vittuuntunut", 127 | "vitun", 128 | "vitusti", 129 | "vituttaa", 130 | "vitutus", 131 | "äpärä" 132 | ] 133 | -------------------------------------------------------------------------------- /tests/fixtures/motorway_trunk_geometry_changed.json: -------------------------------------------------------------------------------- 1 | { 2 | "compareFunction": "motorway_trunk_geometry_changed", 3 | "fixtures": [ 4 | { 5 | "description": "Case:1 motorway geometry modified. Should be flagged", 6 | "newVersion": { 7 | "type": "Feature", 8 | "id": "way!90800153!24", 9 | "properties": { 10 | "AND_a_nosr_r": "15062474", 11 | "AND:importance_level": "2", 12 | "highway": "motorway", 13 | "osm:type": "way", 14 | "osm:id": 90800153, 15 | "osm:version": 25, 16 | "osm:changeset": 44390158, 17 | "osm:timestamp": 1481696445000, 18 | "osm:uid": 3479270, 19 | "osm:user": "nammala" 20 | }, 21 | "geometry": { 22 | "type": "LineString", 23 | "coordinates": [ 24 | [ 25 | 83.3348845, 26 | 17.7221054 27 | ], 28 | [ 29 | 83.3346842, 30 | 17.7221352 31 | ], 32 | [ 33 | 83.3342895, 34 | 17.7221956 35 | ], 36 | [ 37 | 83.3339606, 38 | 17.7222435 39 | ], 40 | [ 41 | 83.3337141, 42 | 17.7222982 43 | ] 44 | ] 45 | } 46 | }, 47 | "oldVersion": { 48 | "type": "Feature", 49 | "id": "way!90800153!24", 50 | "properties": { 51 | "AND_a_nosr_r": "15062474", 52 | "AND:importance_level": "2", 53 | "highway": "motorway", 54 | "osm:type": "way", 55 | "osm:id": 90800153, 56 | "osm:version": 24, 57 | "osm:changeset": 44390158, 58 | "osm:timestamp": 1481696445000, 59 | "osm:uid": 3479270, 60 | "osm:user": "nammala" 61 | }, 62 | "geometry": { 63 | "type": "LineString", 64 | "coordinates": [ 65 | [ 66 | 73.3348845, 67 | 17.7221054 68 | ], 69 | [ 70 | 83.3346842, 71 | 17.7221352 72 | ], 73 | [ 74 | 83.3342895, 75 | 17.7221956 76 | ], 77 | [ 78 | 83.3339606, 79 | 17.7222435 80 | ], 81 | [ 82 | 83.3337141, 83 | 17.7222982 84 | ] 85 | ] 86 | } 87 | }, 88 | "expectedResult": { 89 | "result:motorway_trunk_geometry_changed": true 90 | } 91 | } 92 | ] 93 | } -------------------------------------------------------------------------------- /comparators/name_unmatches_with_wikidata.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var request = require('request'); 4 | 5 | module.exports = nameUnmatchesWithWikidata; 6 | 7 | function getWikidataName(feature, id) { 8 | if ( 9 | feature.hasOwnProperty('entities') && 10 | feature['entities'].hasOwnProperty(id) && 11 | feature['entities'][id].hasOwnProperty(['labels']) && 12 | feature['entities'][id]['labels'].hasOwnProperty('en') 13 | ) 14 | return feature['entities'][id]['labels']['en']['value']; 15 | else 16 | return undefined; 17 | } 18 | 19 | function getWikidataAliasNames(feature, id) { 20 | var names = []; 21 | if ( 22 | feature.hasOwnProperty('entities') && 23 | feature['entities'].hasOwnProperty(id) && 24 | feature['entities'][id].hasOwnProperty(['aliases']) && 25 | feature['entities'][id]['aliases'].hasOwnProperty('en') 26 | ) { 27 | var aliases = feature['entities'][id]['aliases']['en']; 28 | for (var i = 0; i < aliases.length; i++) { 29 | names.push(aliases[i]['value']); 30 | } 31 | } 32 | return names; 33 | } 34 | 35 | function nameUnmatchesWithWikidata(newVersion, oldVersion, callback) { 36 | if (newVersion.deleted) return callback(null, false); 37 | 38 | // Check if feature is newly created. 39 | if (newVersion.properties['osm:version'] !== 1) { 40 | if ( 41 | !oldVersion || 42 | newVersion.properties['name'] === oldVersion.properties['name'] 43 | ) 44 | return callback(null, false); 45 | } 46 | 47 | if ( 48 | newVersion.properties.hasOwnProperty('wikidata') && 49 | newVersion.properties.hasOwnProperty('name') 50 | ) { 51 | var osmName = newVersion.properties['name']; 52 | var wikidataID = newVersion.properties['wikidata']; 53 | var url = 'https://www.wikidata.org/w/api.php?action=wbgetentities&ids=' + 54 | wikidataID + 55 | '&format=json'; 56 | 57 | request(url, function(error, response, body) { 58 | if (!error && response && response.statusCode === 200) { 59 | var wikidataFeature = JSON.parse(body); 60 | var wikidataName = getWikidataName(wikidataFeature, wikidataID); 61 | var wikidataAliasNames = getWikidataAliasNames( 62 | wikidataFeature, 63 | wikidataID 64 | ); 65 | 66 | if ( 67 | osmName !== wikidataName && wikidataAliasNames.indexOf(osmName) === -1 68 | ) 69 | return callback(null, { 70 | 'result:name_unmatches_with_wikidata': true 71 | }); 72 | } 73 | return callback(null, false); 74 | }); 75 | } else { 76 | return callback(null, false); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /comparators/place_edited.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = placeEdited; 3 | var _ = require('lodash'); 4 | 5 | /* 6 | - If a feature has these tags 7 | - place=city 8 | - place=town 9 | - place=suburb 10 | 11 | - Any of the following are true: 12 | - The feature is deleted **or** 13 | - The feature is added **or** 14 | - The feature geometry moves **or** 15 | - One of the feature's `name*` tags changes or is removed 16 | */ 17 | function placeEdited(newVersion, oldVersion) { 18 | var isDeleted = newVersion.deleted; 19 | var isAdded = !oldVersion; 20 | var isPlace, geometryChanged, nameChanged; 21 | if (isAdded) { 22 | isPlace = getIsPlace(newVersion); 23 | } else if (isDeleted) { 24 | isPlace = getIsPlace(oldVersion); 25 | } else { 26 | isPlace = getIsPlace(newVersion) || getIsPlace(oldVersion); 27 | } 28 | if (!isPlace) { 29 | return false; 30 | } 31 | geometryChanged = getGeometryChanged(newVersion, oldVersion); 32 | nameChanged = getNameChanged(newVersion, oldVersion); 33 | if (isPlace && (isDeleted || isAdded || geometryChanged || nameChanged)) { 34 | // One could set the value to something more specific rather than 'true' 35 | return { 36 | 'result:place_edited': true 37 | }; 38 | } else { 39 | return false; 40 | } 41 | } 42 | 43 | function getIsPlace(geojson) { 44 | var placeValues = ['city', 'town', 'suburb']; 45 | return geojson.properties && 46 | geojson.properties.place && 47 | placeValues.indexOf(geojson.properties.place) !== -1; 48 | } 49 | 50 | function getGeometryChanged(newVersion, oldVersion) { 51 | if (newVersion.deleted || !oldVersion) { 52 | return false; 53 | } 54 | var oldGeom = oldVersion.geometry; 55 | var newGeom = newVersion.geometry; 56 | 57 | //FIXME: one may want to do a centroid / distance calc with turf 58 | return !_.isEqual(oldGeom, newGeom); 59 | } 60 | 61 | function getNameChanged(newVersion, oldVersion) { 62 | if (newVersion.deleted || !oldVersion) { 63 | return false; 64 | } 65 | var nameChanged = false; 66 | var newProps = newVersion.properties; 67 | var oldProps = oldVersion.properties; 68 | var uniqueKeys = _.uniq(_.keys(newProps).concat(_.keys(oldProps))); 69 | for (var i = 0; i < uniqueKeys.length; i++) { 70 | var key = uniqueKeys[i]; 71 | if (_.startsWith(key, 'name')) { 72 | if (!newProps.hasOwnProperty(key) || oldProps.hasOwnProperty(key)) { 73 | nameChanged = true; // a name tag was deleted 74 | break; 75 | } 76 | if (oldProps.hasOwnProperty(key) && oldProps[key] !== newProps[key]) { 77 | nameChanged = true; 78 | break; 79 | } 80 | } 81 | } 82 | return nameChanged; 83 | } 84 | -------------------------------------------------------------------------------- /tests/fixtures/name_ref_changes.json: -------------------------------------------------------------------------------- 1 | { 2 | "compareFunction": "name_ref_changes", 3 | "fixtures": [ 4 | { 5 | "description": "Case:1 motorway geometry modified. Should be flagged", 6 | "newVersion": { 7 | "type": "Feature", 8 | "id": "way!90800153!24", 9 | "properties": { 10 | "AND_a_nosr_r": "15062474", 11 | "AND:importance_level": "2", 12 | "highway": "motorway", 13 | "name": "23rd Street", 14 | "ref": "I 23", 15 | "osm:type": "way", 16 | "osm:id": 90800153, 17 | "osm:version": 25, 18 | "osm:changeset": 44390158, 19 | "osm:timestamp": 1481696445000, 20 | "osm:uid": 3479270, 21 | "osm:user": "nammala" 22 | }, 23 | "geometry": { 24 | "type": "LineString", 25 | "coordinates": [ 26 | [ 27 | 83.3348845, 28 | 17.7221054 29 | ], 30 | [ 31 | 83.3346842, 32 | 17.7221352 33 | ], 34 | [ 35 | 83.3342895, 36 | 17.7221956 37 | ], 38 | [ 39 | 83.3339606, 40 | 17.7222435 41 | ], 42 | [ 43 | 83.3337141, 44 | 17.7222982 45 | ] 46 | ] 47 | } 48 | }, 49 | "oldVersion": { 50 | "type": "Feature", 51 | "id": "way!90800153!24", 52 | "properties": { 53 | "AND_a_nosr_r": "15062474", 54 | "AND:importance_level": "2", 55 | "highway": "motorway", 56 | "osm:type": "way", 57 | "osm:id": 90800153, 58 | "name": "24th Street", 59 | "ref": "I 24", 60 | "osm:version": 24, 61 | "osm:changeset": 44390158, 62 | "osm:timestamp": 1481696445000, 63 | "osm:uid": 3479270, 64 | "osm:user": "nammala" 65 | }, 66 | "geometry": { 67 | "type": "LineString", 68 | "coordinates": [ 69 | [ 70 | 73.3348845, 71 | 17.7221054 72 | ], 73 | [ 74 | 83.3346842, 75 | 17.7221352 76 | ], 77 | [ 78 | 83.3342895, 79 | 17.7221956 80 | ], 81 | [ 82 | 83.3339606, 83 | 17.7222435 84 | ], 85 | [ 86 | 83.3337141, 87 | 17.7222982 88 | ] 89 | ] 90 | } 91 | }, 92 | "expectedResult": { 93 | "result:name_ref_changes": true 94 | } 95 | } 96 | ] 97 | } -------------------------------------------------------------------------------- /comparators/tag-combinations.csv: -------------------------------------------------------------------------------- 1 | ,aerialway,aeroway,amenity,barrier,boundary,building,craft,emergency,geological,highway,historic,landuse,leisure,man_made,military,natural,office,places,power,public_transport,railway,route,shop,sport,tourism,waterway 2 | aerialway,,,0.05,0.01,,2.49,,,,0.33,0.02,,0.07,0.09,,0.01,,,0.12,0.74,0.19,0.04,0.02,0.2,0.1, 3 | aeroway,,,0.03,0.14,0.01,6.69,,0.03,,0.21,0.01,0.27,0.02,0.29,0.24,0.02,,,,,,,,0.06,0.01, 4 | amenity,,,,0.41,,13.51,0.01,0.29,,0.13,0.16,0.26,0.12,0.28,,0.07,0.08,,,0.07,,,0.47,0.23,0.36,0.01 5 | barrier,,0.01,0.61,,0.05,0.14,,0.03,,0.06,0.26,2.27,1.49,0.14,0.04,0.1,0.01,,0.39,,0.02,,0.02,0.11,0.07,0.06 6 | boundary,,,0.01,0.24,,0.01,,,,1.52,0.33,1,2.28,0.04,0.01,1.46,,,0.01,,0.03,0.01,,,0.02,2.67 7 | building,,0.01,0.66,,,,0.01,,,,0.06,0.04,0.04,0.12,0.01,,0.04,,0.06,,0.01,,0.25,0.02,0.08, 8 | craft,,,1.85,0.25,,27.75,,,,0.04,0.08,1.7,0.03,1.43,,0.01,0.7,,0.03,,,,9.56,0.01,0.83,0.01 9 | emergency,,0.01,3.79,0.28,0.01,1.11,,,,5.32,0.07,0.04,0.03,0.15,,0.11,0.01,,,0.01,0.02,0.01,0.01,0.01,0.3, 10 | geological,,,0.02,0.05,0.69,0.3,,,,0.14,2.1,0.37,0.51,0.2,,60.05,,,,,,0.02,,0.03,4.16,0.12 11 | highway,,,0.01,,0.02,,,0.04,,,0.01,0.01,0.01,0.03,,,,,0.01,0.63,0.07,0.04,,0.01,0.01, 12 | historic,,0.01,2.43,2.65,0.73,17.33,0.01,0.09,0.02,0.99,,0.62,0.2,1.81,2.02,0.46,0.03,,0.01,0.01,0.68,0.1,0.05,0.06,4.34,0.13 13 | landuse,,0.01,0.15,0.89,0.09,0.5,0.01,,,0.05,0.02,,0.41,0.14,0.02,0.54,0.01,,0.04,,,,0.05,0.17,0.03,0.01 14 | leisure,,,0.35,2.9,0.96,2.15,,0.01,,0.16,0.04,2.01,,0.03,,0.39,,,,,,,0.02,28.4,0.25,0.01 15 | man_made,,0.05,1.44,0.48,0.03,12.41,0.05,0.06,,1.57,0.61,1.19,0.05,,0.01,0.77,0.02,,0.25,0.01,0.06,0.01,0.15,0.02,0.35,0.43 16 | military,,1.61,0.63,5,0.18,28.04,,0.02,,0.88,27.28,6.28,0.25,0.54,,0.19,0.44,,0.01,,,,0.01,0.33,1.07,0.03 17 | natural,,,0.03,0.03,0.09,0.02,,,0.01,0.01,0.01,0.39,0.06,0.06,,,,,0.01,,,,,0.05,0.04,0.07 18 | office,,,3.13,0.2,0.01,31.85,0.16,0.03,,0.02,0.07,0.62,0.05,0.14,0.08,0.01,,,0.01,0.01,,,1.17,0.04,0.12, 19 | places,,,,,,,,,,,,,,,,,,,,,,,,,, 20 | power,,,,0.21,,1,,,,0.1,,0.05,,0.04,,0.01,,,,,,,,,, 21 | public_transport,0.05,,0.54,0.01,,0.49,,,,51.51,0.01,0.01,,0.02,,,,,,,8.81,0.04,0.01,,0.01, 22 | railway,,,0.01,0.03,0.01,0.38,,,,2.17,0.13,0.02,,0.04,,,,,,3.33,,0.08,,,0.02, 23 | route,0.01,,0.08,,0.02,0.02,,0.01,,8.09,0.13,0.08,0.03,0.03,,,,,0.01,0.1,0.55,,,0.07,0.08,0.1 24 | shop,,,1.94,0.07,,21.33,0.25,,,0.02,0.01,0.37,0.02,0.12,,0.01,0.13,,,0.01,,,,0.05,0.11, 25 | sport,0.01,0.02,2.18,0.71,,4.11,,,,0.83,0.04,2.75,90.66,0.04,0.01,1.09,0.01,,,,0.01,0.03,0.11,,0.15,0.01 26 | tourism,0.01,,2.77,0.38,0.02,13.04,0.04,0.17,0.02,0.4,2.17,0.43,0.67,0.52,0.04,0.71,0.02,,0.01,0.01,0.05,0.03,0.2,0.12,,0.11 27 | waterway,,,0.01,0.03,0.31,0.05,,,,0.03,0.01,0.01,,0.07,,0.14,,,,,,,,,0.01, 28 | -------------------------------------------------------------------------------- /tests/fixtures/name_unmatches_with_wikidata.json: -------------------------------------------------------------------------------- 1 | { 2 | "compareFunction": "name_unmatches_with_wikidata", 3 | "fixtures": [ 4 | { 5 | "description": "Test OSM name different from Wikidata name", 6 | "expectedResult": { 7 | "result:name_unmatches_with_wikidata": true 8 | }, 9 | "newVersion": { 10 | "type": "Feature", 11 | "properties": { 12 | "osm:id": 427818536, 13 | "osm:type": "way", 14 | "name": "Central Park is now something else", 15 | "wikidata": "Q160409", 16 | "osm:version": 1 17 | }, 18 | "geometry": null 19 | }, 20 | "oldVersion": null 21 | }, 22 | { 23 | "description": "Test OSM name matches with aliases on Wikidata", 24 | "expectedResult": false, 25 | "newVersion": { 26 | "type": "Feature", 27 | "properties": { 28 | "osm:id": 3401391999, 29 | "osm:type": "node", 30 | "name": "Bengaluru", 31 | "wikidata": "Q1355" 32 | }, 33 | "geometry": null 34 | }, 35 | "oldVersion": null 36 | }, 37 | { 38 | "description": "Test feature with no Wikidata tag", 39 | "expectedResult": false, 40 | "newVersion": { 41 | "type": "Feature", 42 | "properties": { 43 | "osm:id": 3401391999, 44 | "osm:type": "node", 45 | "name": "Bengaluru" 46 | }, 47 | "geometry": null 48 | }, 49 | "oldVersion": null 50 | }, 51 | { 52 | "description": "Test feature with Wikidata tag and not a name modification", 53 | "expectedResult": false, 54 | "newVersion": { 55 | "type": "Feature", 56 | "properties": { 57 | "osm:id": 3401391999, 58 | "osm:type": "node", 59 | "name": "Bengaluru is somethong else", 60 | "osm:version": 2, 61 | "wikidata": "Q1355" 62 | }, 63 | "geometry": null 64 | }, 65 | "oldVersion": { 66 | "type": "Feature", 67 | "properties": { 68 | "osm:id": 3401391999, 69 | "osm:type": "node", 70 | "name": "Bengaluru is somethong else", 71 | "osm:version": 1, 72 | "wikidata": "Q1355" 73 | }, 74 | "geometry": null 75 | } 76 | }, 77 | { 78 | "description": "Test new feature with Wikidata", 79 | "expectedResult": { 80 | "result:name_unmatches_with_wikidata": true 81 | }, 82 | "newVersion": { 83 | "type": "Feature", 84 | "properties": { 85 | "osm:id": 3401391999, 86 | "osm:type": "node", 87 | "name": "Bengaluru is something else", 88 | "osm:version": 1, 89 | "wikidata": "Q1355" 90 | }, 91 | "geometry": null 92 | }, 93 | "oldVersion": null 94 | } 95 | ] 96 | } 97 | -------------------------------------------------------------------------------- /forked/naughty-words/ja.json: -------------------------------------------------------------------------------- 1 | [ 2 | "3p", 3 | "g スポット", 4 | "s & m", 5 | "sm", 6 | "sm女王", 7 | "xx", 8 | "アジアのかわいい女の子", 9 | "アスホール", 10 | "アナリングス", 11 | "アナル", 12 | "いたずら", 13 | "イラマチオ", 14 | "ウェブカメラ", 15 | "エクスタシー", 16 | "エスコート", 17 | "エッチ", 18 | "エロティズム", 19 | "エロティック", 20 | "オーガズム", 21 | "オカマ", 22 | "おしっこ", 23 | "おしり", 24 | "オシリ", 25 | "おしりのあな", 26 | "おっぱい", 27 | "オッパイ", 28 | "オナニー", 29 | "オマンコ", 30 | "おもらし", 31 | "お尻", 32 | "カーマスートラ", 33 | "カント", 34 | "クリトリス", 35 | "グループ・セックス", 36 | "グロ", 37 | "クンニリングス", 38 | "ゲイ・セックス", 39 | "ゲイの男性", 40 | "ゲイボーイ", 41 | "ゴールデンシャワー", 42 | "コカイン", 43 | "ゴックン", 44 | "サディズム", 45 | "しばり", 46 | "スウィンガー", 47 | "スカートの中", 48 | "スカトロ", 49 | "ストラップオン", 50 | "ストリップ劇場", 51 | "スラット", 52 | "スリット", 53 | "セクシーな", 54 | "セクシーな 10 代", 55 | "セックス", 56 | "ソドミー", 57 | "ちんこ", 58 | "ディープ・スロート", 59 | "ディック", 60 | "ディルド", 61 | "デートレイプ", 62 | "デブ", 63 | "テレフォンセックス", 64 | "ドッグスタイル", 65 | "トップレス", 66 | "なめ", 67 | "ニガー", 68 | "ヌード", 69 | "ネオ・ナチ", 70 | "ハードコア", 71 | "パイパン", 72 | "バイブレーター", 73 | "バック・スタイル", 74 | "パンティー", 75 | "ビッチ", 76 | "ファック", 77 | "ファンタジー", 78 | "フィスト", 79 | "フェティッシュ", 80 | "フェラチオ", 81 | "ふたなり", 82 | "ぶっかけ", 83 | "フック", 84 | "プリンス アルバート ピアス", 85 | "プレイボーイ", 86 | "ベアバック", 87 | "ペニス", 88 | "ペニスバンド", 89 | "ボーイズラブ", 90 | "ボールギャグ", 91 | "ボールを蹴る", 92 | "ぽっちゃり", 93 | "ホモ", 94 | "ポルノ", 95 | "ポルノグラフィー", 96 | "ボンテージ", 97 | "マザー・ファッカー", 98 | "マスターベーション", 99 | "まんこ", 100 | "やおい", 101 | "やりまん", 102 | "ユダヤ人", 103 | "ラティーナ", 104 | "ラバー", 105 | "ランジェリー", 106 | "レイプ", 107 | "レズビアン", 108 | "ローター", 109 | "ロリータ", 110 | "淫乱", 111 | "陰毛", 112 | "革抑制", 113 | "騎上位", 114 | "巨根", 115 | "巨乳", 116 | "強姦犯", 117 | "玉なめ", 118 | "玉舐め", 119 | "緊縛", 120 | "近親相姦", 121 | "嫌い", 122 | "後背位", 123 | "合意の性交", 124 | "拷問", 125 | "殺し方", 126 | "殺人事件", 127 | "殺人方法", 128 | "支配", 129 | "児童性虐待", 130 | "自己愛性", 131 | "射精", 132 | "手コキ", 133 | "獣姦", 134 | "女の子", 135 | "女王様", 136 | "女子高生", 137 | "女装", 138 | "新しいポルノ", 139 | "人妻", 140 | "人種", 141 | "性交", 142 | "正常位", 143 | "生殖器", 144 | "精液", 145 | "挿入", 146 | "足フェチ", 147 | "足を広げる", 148 | "大陰唇", 149 | "脱衣", 150 | "茶色のシャワー", 151 | "中出し", 152 | "潮吹き女", 153 | "潮吹き男性", 154 | "直腸", 155 | "剃毛", 156 | "貞操帯", 157 | "奴隷", 158 | "二穴", 159 | "乳首", 160 | "尿道プレイ", 161 | "覗き", 162 | "売春婦", 163 | "縛り", 164 | "噴出", 165 | "糞", 166 | "糞尿愛好症", 167 | "糞便", 168 | "平手打ち", 169 | "変態", 170 | "勃起する", 171 | "夢精", 172 | "毛深い", 173 | "誘惑", 174 | "幼児", 175 | "幼児性愛者", 176 | "裸", 177 | "裸の女性", 178 | "乱交", 179 | "両性", 180 | "両性具有", 181 | "両刀", 182 | "輪姦", 183 | "卍", 184 | "宦官", 185 | "肛門", 186 | "膣" 187 | ] 188 | -------------------------------------------------------------------------------- /forked/naughty-words/tr.json: -------------------------------------------------------------------------------- 1 | [ 2 | "am", 3 | "amcığa", 4 | "amcığı", 5 | "amcığın", 6 | "amcık", 7 | "amcıklar", 8 | "amcıklara", 9 | "amcıklarda", 10 | "amcıklardan", 11 | "amcıkları", 12 | "amcıkların", 13 | "amcıkta", 14 | "amcıktan", 15 | "amı", 16 | "amlar", 17 | "çingene", 18 | "Çingenede", 19 | "Çingeneden", 20 | "Çingeneler", 21 | "Çingenelerde", 22 | "Çingenelerden", 23 | "Çingenelere", 24 | "Çingeneleri", 25 | "Çingenelerin", 26 | "Çingenenin", 27 | "Çingeneye", 28 | "Çingeneyi", 29 | "göt", 30 | "göte", 31 | "götler", 32 | "götlerde", 33 | "götlerden", 34 | "götlere", 35 | "götleri", 36 | "götlerin", 37 | "götte", 38 | "götten", 39 | "götü", 40 | "götün", 41 | "götveren", 42 | "götverende", 43 | "götverenden", 44 | "götverene", 45 | "götvereni", 46 | "götverenin", 47 | "götverenler", 48 | "götverenlerde", 49 | "götverenlerden", 50 | "götverenlere", 51 | "götverenleri", 52 | "götverenlerin", 53 | "kaltağa", 54 | "kaltağı", 55 | "kaltağın", 56 | "kaltak", 57 | "kaltaklar", 58 | "kaltaklara", 59 | "kaltaklarda", 60 | "kaltaklardan", 61 | "kaltakları", 62 | "kaltakların", 63 | "kaltakta", 64 | "kaltaktan", 65 | "orospu", 66 | "orospuda", 67 | "orospudan", 68 | "orospular", 69 | "orospulara", 70 | "orospularda", 71 | "orospulardan", 72 | "orospuları", 73 | "orospuların", 74 | "orospunun", 75 | "orospuya", 76 | "orospuyu", 77 | "otuz birci", 78 | "otuz bircide", 79 | "otuz birciden", 80 | "otuz birciler", 81 | "otuz bircilerde", 82 | "otuz bircilerden", 83 | "otuz bircilere", 84 | "otuz bircileri", 85 | "otuz bircilerin", 86 | "otuz bircinin", 87 | "otuz birciye", 88 | "otuz birciyi", 89 | "saksocu", 90 | "saksocuda", 91 | "saksocudan", 92 | "saksocular", 93 | "saksoculara", 94 | "saksocularda", 95 | "saksoculardan", 96 | "saksocuları", 97 | "saksocuların", 98 | "saksocunun", 99 | "saksocuya", 100 | "saksocuyu", 101 | "sıçmak", 102 | "sik", 103 | "sike", 104 | "siker sikmez", 105 | "siki", 106 | "sikilir sikilmez", 107 | "sikin", 108 | "sikler", 109 | "siklerde", 110 | "siklerden", 111 | "siklere", 112 | "sikleri", 113 | "siklerin", 114 | "sikmek", 115 | "sikmemek", 116 | "sikte", 117 | "sikten", 118 | "siktir", 119 | "siktirir siktirmez", 120 | "taşağa", 121 | "taşağı", 122 | "taşağın", 123 | "taşak", 124 | "taşaklar", 125 | "taşaklara", 126 | "taşaklarda", 127 | "taşaklardan", 128 | "taşakları", 129 | "taşakların", 130 | "taşakta", 131 | "taşaktan", 132 | "yarağa", 133 | "yarağı", 134 | "yarağın", 135 | "yarak", 136 | "yaraklar", 137 | "yaraklara", 138 | "yaraklarda", 139 | "yaraklardan", 140 | "yarakları", 141 | "yarakların", 142 | "yarakta", 143 | "yaraktan" 144 | ] 145 | -------------------------------------------------------------------------------- /tests/fixtures/modified_place_wikidata.json: -------------------------------------------------------------------------------- 1 | { 2 | "compareFunction":"modified_place_wikidata", 3 | "fixtures":[ 4 | { 5 | "description":"wikidata tag modified", 6 | "newVersion":{ 7 | "type":"Feature", 8 | "properties":{ 9 | "place":"city", 10 | "wikidata":"Q79" 11 | }, 12 | "geometry":null 13 | }, 14 | "oldVersion":{ 15 | "type":"Feature", 16 | "properties":{ 17 | "place":"city", 18 | "wikidata":"Q40435" 19 | }, 20 | "geometry":null 21 | }, 22 | "expectedResult":{ 23 | "result:modified_place_wikidata": true 24 | } 25 | }, 26 | { 27 | "description":"wikidata tag added to a place feature", 28 | "newVersion":{ 29 | "type":"Feature", 30 | "properties":{ 31 | "place":"city", 32 | "wikidata":"Q40435" 33 | }, 34 | "geometry":null 35 | }, 36 | "oldVersion":{ 37 | "type":"Feature", 38 | "properties":{ 39 | "place":"city" 40 | }, 41 | "geometry":null 42 | }, 43 | "expectedResult":{ 44 | "result:modified_place_wikidata": true 45 | } 46 | }, 47 | { 48 | "description":"place tag added to a wikidata feature", 49 | "newVersion":{ 50 | "type":"Feature", 51 | "properties":{ 52 | "place":"city", 53 | "wikidata":"Q40435" 54 | }, 55 | "geometry":null 56 | }, 57 | "oldVersion":{ 58 | "type":"Feature", 59 | "properties":{ 60 | "wikidata":"Q70" 61 | }, 62 | "geometry":null 63 | }, 64 | "expectedResult":{ 65 | "result:modified_place_wikidata": true 66 | } 67 | }, 68 | { 69 | "description":"wikidata tag deleted from a place feature", 70 | "newVersion":{ 71 | "type":"Feature", 72 | "properties":{ 73 | "place":"city" 74 | }, 75 | "geometry":null 76 | }, 77 | "oldVersion":{ 78 | "type":"Feature", 79 | "properties":{ 80 | "place":"city", 81 | "wikidata":"Q70" 82 | }, 83 | "geometry":null 84 | }, 85 | "expectedResult":{ 86 | "result:modified_place_wikidata": true 87 | } 88 | }, 89 | { 90 | "description":"someother tag added to a wikidata feature", 91 | "newVersion":{ 92 | "type":"Feature", 93 | "properties":{ 94 | "administrative":"boundary", 95 | "wikidata":"Q40435" 96 | }, 97 | "geometry":null 98 | }, 99 | "oldVersion":{ 100 | "type":"Feature", 101 | "properties":{ 102 | "wikidata":"Q70" 103 | }, 104 | "geometry":null 105 | }, 106 | "expectedResult": false 107 | } 108 | ] 109 | } 110 | -------------------------------------------------------------------------------- /comparators/wrong_turn_restriction.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = wrongTurnRestriction; 4 | 5 | /** 6 | * Checks for existence of name tag and if it is modified between old and new version, it callbacks with the result. 7 | * @param {object} newVersion Features new version in GeoJSON. 8 | * @param {object} oldVersion Features old version in GeoJSON. 9 | * @returns {bool} Boolean indicating a turn restriction. 10 | */ 11 | 12 | function via_in_ways(via, ways) { 13 | return ways.filter(function(e) { 14 | return JSON.stringify(via.geometry.coordinates) !== 15 | JSON.stringify(e.geometry.coordinates[0]) && 16 | JSON.stringify(via.geometry.coordinates) !== 17 | JSON.stringify( 18 | e.geometry.coordinates[e.geometry.coordinates.length - 1] 19 | ); 20 | }).length > 0; 21 | } 22 | 23 | function wrongTurnRestriction(newVersion, oldVersion) { 24 | if ( 25 | newVersion && 26 | newVersion.properties && 27 | newVersion.properties.hasOwnProperty('restriction') && 28 | newVersion.properties.relations 29 | ) { 30 | var roles = newVersion.properties.relations.map(function(e) { 31 | return e.properties.role; 32 | }); 33 | // a turn restriction needs at least 3 members 34 | if (roles.length < 3) { 35 | return {'result:wrong_turn_restriction': true}; 36 | } 37 | // detect members with wrong roles 38 | if ( 39 | roles.filter(function(e) { 40 | return ['from', 'via', 'to', 'location_hint'].indexOf(e) === -1; 41 | }).length > 0 42 | ) { 43 | return {'result:wrong_turn_restriction': true}; 44 | } 45 | // only no_entry restrictions can have more than one FROM way 46 | if ( 47 | roles.filter(function(e) { 48 | return e === 'from'; 49 | }).length > 1 && newVersion.properties.restriction !== 'no_entry' 50 | ) { 51 | return {'result:wrong_turn_restriction': true}; 52 | } 53 | // only no_exit restrictions can have more than one TO way 54 | if ( 55 | roles.filter(function(e) { 56 | return e === 'to'; 57 | }).length > 1 && newVersion.properties.restriction !== 'no_exit' 58 | ) { 59 | return {'result:wrong_turn_restriction': true}; 60 | } 61 | // a turn restriction can have only one VIA node 62 | var via_nodes = newVersion.properties.relations.filter(function(e) { 63 | return e.properties.role === 'via' && e.properties.type === 'node'; 64 | }); 65 | if (via_nodes.length > 1) { 66 | return {'result:wrong_turn_restriction': true}; 67 | } 68 | // check if the VIA node is present in the FROM and TWO ways 69 | var via = via_nodes[0]; 70 | var ways = newVersion.properties.relations.filter(function(e) { 71 | return e.properties.type === 'way'; 72 | }); 73 | if ( 74 | via && via.geometry && via.geometry.coordinates && via_in_ways(via, ways) 75 | ) { 76 | return {'result:wrong_turn_restriction': true}; 77 | } 78 | } 79 | return false; 80 | } 81 | -------------------------------------------------------------------------------- /tests/fixtures/profanity.json: -------------------------------------------------------------------------------- 1 | { 2 | "compareFunction": "profanity", 3 | "fixtures": [ 4 | { 5 | "description": "Flags profanity in name changes", 6 | "newVersion": { 7 | "type": "Feature", 8 | "properties": { 9 | "osm:id": 1234, 10 | "osm:type": "node", 11 | "osm:uid": 123, 12 | "osm:changeset": 123, 13 | "name:en": "shit" 14 | }, 15 | "geometry": {} 16 | }, 17 | "oldVersion": { 18 | "type": "Feature", 19 | "properties": { 20 | "osm:id": 1234, 21 | "osm:type": "node", 22 | "osm:uid": 124, 23 | "osm:changeset": 124, 24 | "name:en": "no name" 25 | }, 26 | "geometry": {} 27 | }, 28 | "expectedResult": { 29 | "result:profanity": true 30 | } 31 | }, 32 | { 33 | "description": "Does not flag profanity", 34 | "newVersion": { 35 | "type": "Feature", 36 | "properties": { 37 | "osm:id": 1234, 38 | "osm:type": "node", 39 | "osm:uid": 123, 40 | "osm:changeset": 123, 41 | "name:en": "yes name" 42 | }, 43 | "geometry": {} 44 | }, 45 | "oldVersion": { 46 | "type": "Feature", 47 | "properties": { 48 | "osm:id": 1234, 49 | "osm:type": "node", 50 | "osm:uid": 124, 51 | "osm:changeset": 124, 52 | "name:en": "no name" 53 | }, 54 | "geometry": {} 55 | }, 56 | "expectedResult": false 57 | }, 58 | { 59 | "description": "Flags profanity", 60 | "newVersion": { 61 | "type": "Feature", 62 | "properties": { 63 | "osm:id": 1234, 64 | "osm:type": "node", 65 | "osm:uid": 123, 66 | "osm:changeset": 123, 67 | "name:en": "fuck" 68 | }, 69 | "geometry": {} 70 | }, 71 | "oldVersion": { 72 | "type": "Feature", 73 | "properties": { 74 | "osm:id": 1234, 75 | "osm:type": "node", 76 | "osm:uid": 124, 77 | "osm:changeset": 124, 78 | "name:en": "normal name" 79 | }, 80 | "geometry": {} 81 | }, 82 | "expectedResult": { 83 | "result:profanity": true 84 | } 85 | } 86 | ] 87 | } 88 | -------------------------------------------------------------------------------- /tests/fixtures/added_place.json: -------------------------------------------------------------------------------- 1 | { 2 | "compareFunction": "added_place", 3 | "fixtures": [ 4 | { 5 | "description": "No old version, new version", 6 | "newVersion": {"deleted": true}, 7 | "oldVersion": null, 8 | "expectedResult": false 9 | }, 10 | { 11 | "description": "Checks for added place tag", 12 | "newVersion": { 13 | "type": "Feature", 14 | "properties": { 15 | "osm:id": 1234, 16 | "osm:type": "node", 17 | "osm:uid": 123, 18 | "osm:changeset": 123, 19 | "place": "city" 20 | }, 21 | "geometry": { 22 | "type": "Point", 23 | "coordinates": [ 24 | 10, 25 | 10 26 | ] 27 | } 28 | }, 29 | "oldVersion": null, 30 | "expectedResult": { 31 | "result:added_place": true 32 | } 33 | }, 34 | { 35 | "description": "Checks for added place tag", 36 | "newVersion": { 37 | "type": "Feature", 38 | "properties": { 39 | "osm:id": 1234, 40 | "osm:type": "node", 41 | "osm:uid": 123, 42 | "osm:changeset": 123, 43 | "place": "city" 44 | }, 45 | "geometry": { 46 | "type": "Point", 47 | "coordinates": [ 48 | 10, 49 | 10 50 | ] 51 | } 52 | }, 53 | "oldVersion": { 54 | "type": "Feature", 55 | "properties": { 56 | "osm:id": 1234, 57 | "osm:type": "node", 58 | "osm:uid": 124, 59 | "osm:changeset": 124 60 | }, 61 | "geometry": { 62 | "type": "Point", 63 | "coordinates": [ 64 | 11, 65 | 11 66 | ] 67 | } 68 | }, 69 | "expectedResult": { 70 | "result:added_place": true 71 | } 72 | }, 73 | { 74 | "description": "Place in old and new version", 75 | "newVersion": { 76 | "type": "Feature", 77 | "properties": { 78 | "osm:id": 1234, 79 | "osm:type": "node", 80 | "osm:uid": 123, 81 | "osm:changeset": 123, 82 | "place": "city" 83 | }, 84 | "geometry": { 85 | "type": "Point", 86 | "coordinates": [ 87 | 10, 88 | 10 89 | ] 90 | } 91 | }, 92 | "oldVersion": { 93 | "type": "Feature", 94 | "properties": { 95 | "osm:id": 1234, 96 | "osm:type": "node", 97 | "osm:uid": 124, 98 | "osm:changeset": 124, 99 | "place": "city" 100 | }, 101 | "geometry": { 102 | "type": "Point", 103 | "coordinates": [ 104 | 11, 105 | 11 106 | ] 107 | } 108 | }, 109 | "expectedResult": false 110 | } 111 | ] 112 | } 113 | -------------------------------------------------------------------------------- /tests/fixtures/very_long_name.json: -------------------------------------------------------------------------------- 1 | { 2 | "compareFunction": "very_long_name", 3 | "fixtures": [ 4 | { 5 | "description": "Test feature whose name has 78 characters", 6 | "expectedResult": false, 7 | "newVersion": { 8 | "type": "Feature", 9 | "properties": { 10 | "name": "Universidade Federal do Rio Grande do Sul - Campus Universitário Darcy Ribeiro" 11 | }, 12 | "geometry": null 13 | }, 14 | "oldVersion": null 15 | }, 16 | { 17 | "description": "Test feature whose name has 66 characters", 18 | "expectedResult": false, 19 | "newVersion": { 20 | "type": "Feature", 21 | "properties": { 22 | "name": "International Airport of Brasília - President Juscelino Kubitschek" 23 | }, 24 | "geometry": null 25 | }, 26 | "oldVersion": null 27 | }, 28 | { 29 | "description": "Test feature whose name has 255 characters", 30 | "expectedResult": {"result:very_long_name": true}, 31 | "newVersion": { 32 | "type": "Feature", 33 | "properties": { 34 | "name": "Новоуренгойский городской отдел статистики, Управление соц.защиты населения,Упраление образования города Новый Уренгой ,Управление архитектуры и градостроительства, Регистрационный отдел администрации Новый Уренгой,Комитет по земельным ресурсам и землеуст" 35 | }, 36 | "geometry": null 37 | }, 38 | "oldVersion": null 39 | }, 40 | { 41 | "description": "Test feature whose name has 100 characters", 42 | "expectedResult": {"result:very_long_name": true}, 43 | "newVersion": { 44 | "type": "Feature", 45 | "properties": { 46 | "name": "A very long name with more than eighty characters. Eighty one chars to be precise" 47 | }, 48 | "geometry": null 49 | }, 50 | "oldVersion": null 51 | }, 52 | { 53 | "description": "Test route whose name has 88 characters", 54 | "expectedResult": false, 55 | "newVersion": { 56 | "type": "Feature", 57 | "properties": { 58 | "name": "Bus MKK-68 (1-2) Gelnhausen Bahnhof ⇒ Langenselbold Bahnhof über Gelnhausen Schulzentrum", 59 | "route": "bus", 60 | "type":"route", 61 | "osm:type": "relation" 62 | }, 63 | "geometry": null 64 | }, 65 | "oldVersion": null 66 | }, 67 | { 68 | "description": "Test route whose name has 121 characters", 69 | "expectedResult": {"result:very_long_name": true}, 70 | "newVersion": { 71 | "type": "Feature", 72 | "properties": { 73 | "name": "Linha 526 - Terminal Vila Velha / Terminal Campo Grande - via Vasco da Gama / Expedito Garcia / Centro / Avenida Sessenta", 74 | "route": "bus", 75 | "type":"route", 76 | "osm:type": "relation" 77 | }, 78 | "geometry": null 79 | }, 80 | "oldVersion": null 81 | } 82 | ] 83 | } 84 | -------------------------------------------------------------------------------- /scripts/primary-map-features.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var argv = require('minimist')(process.argv.slice(2)); 4 | var queue = require('d3-queue').queue; 5 | var request = require('request'); 6 | var csv = require('csv'); 7 | var fs = require('fs'); 8 | (function() { 9 | if (!argv.output) { 10 | console.log(''); 11 | console.log('USAGE: node primary-map-features.js OPTIONS'); 12 | console.log(''); 13 | console.log(' OPTIONS'); 14 | console.log(' --output counts.csv'); 15 | console.log(''); 16 | return; 17 | } 18 | 19 | // From: https://wiki.openstreetmap.org/wiki/Map_Features 20 | var features = [ 21 | 'aerialway', 22 | 'aeroway', 23 | 'amenity', 24 | 'barrier', 25 | 'boundary', 26 | 'building', 27 | 'craft', 28 | 'emergency', 29 | 'geological', 30 | 'highway', 31 | 'historic', 32 | 'landuse', 33 | 'leisure', 34 | 'man_made', 35 | 'military', 36 | 'natural', 37 | 'office', 38 | 'place', 39 | 'power', 40 | 'public_transport', 41 | 'railway', 42 | 'route', 43 | 'shop', 44 | 'sport', 45 | 'tourism', 46 | 'waterway' 47 | ]; 48 | 49 | function readURL(url, callback) { 50 | console.log(url); 51 | request(url, function(error, response, body) { 52 | if (error) callback(error); 53 | 54 | var counts = []; 55 | if (!error && response.statusCode === 200) { 56 | var combinations = JSON.parse(body).data; 57 | 58 | for (var i = 0; i < features.length; i++) { 59 | // Initialize counts to zero. 60 | counts[i] = 0; 61 | 62 | for (var j = 0; j < combinations.length; j++) { 63 | if (combinations[j]['other_key'] === features[i]) { 64 | counts[i] = 100.0 * combinations[j]['to_fraction']; 65 | } 66 | } 67 | } 68 | 69 | callback(null, counts); 70 | } 71 | }); 72 | } 73 | 74 | var q = queue(1); 75 | 76 | for (var i = 0; i < features.length; i++) { 77 | var url = 'https://taginfo.openstreetmap.org/api/4/key/combinations?key='; 78 | var featureURL = encodeURI(url + features[i]); 79 | 80 | q.defer(readURL, featureURL); 81 | } 82 | 83 | q.awaitAll(function(error, results) { 84 | if (error) console.log(error); 85 | 86 | results = [features].concat(results); 87 | csv.stringify(results, function(error, resultsAsString) { 88 | fs.writeFileSync(argv.output, resultsAsString); 89 | }); 90 | }); 91 | })(); 92 | 93 | /* 94 | 95 | Vertical headers of 26 primary features for csv file. 96 | 97 | aerialway 98 | aeroway 99 | amenity 100 | barrier 101 | boundary 102 | building 103 | craft 104 | emergency 105 | geological 106 | highway 107 | historic 108 | landuse 109 | leisure 110 | man_made 111 | military 112 | natural 113 | office 114 | place 115 | power 116 | public_transport 117 | railway 118 | route 119 | shop 120 | sport 121 | tourism 122 | waterway 123 | 124 | */ 125 | -------------------------------------------------------------------------------- /tests/fixtures/missing_primary_tag.json: -------------------------------------------------------------------------------- 1 | { 2 | "compareFunction": "missing_primary_tag", 3 | "fixtures": [ 4 | { 5 | "description": "Feature with no new version and old version", 6 | "newVersion": {"deleted": true}, 7 | "oldVersion": {}, 8 | "expectedResult": false 9 | }, 10 | { 11 | "description": "Feature with invalid highway value but with correct key", 12 | "newVersion": {"properties": {"highway": "random"}}, 13 | "oldVersion": {}, 14 | "expectedResult": false 15 | }, 16 | { 17 | "description": "Feature with invalid shop value but with correct key", 18 | "newVersion": {"properties":{"shop": "random_shop"}}, 19 | "oldVersion": {}, 20 | "expectedResult": false 21 | }, 22 | { 23 | "description": "Feature with correct address tags", 24 | "newVersion": { 25 | "properties": {"addr:street": "Via 1", "addr:housenumber": 123} 26 | }, 27 | "oldVersion": {}, 28 | "expectedResult": false 29 | }, 30 | { 31 | "description": "Feature with missing addr key", 32 | "newVersion": { 33 | "properties": {"addr:country": "Fiji", "addr:housenumber": 123} 34 | }, 35 | "oldVersion": {}, 36 | "expectedResult": { 37 | "result:missing_primary_tag": true 38 | } 39 | }, 40 | { 41 | "description": "Feature with valid highway tag", 42 | "newVersion": {"properties": {"highway" : "primary"}}, 43 | "oldVersion": {}, 44 | "expectedResult": false 45 | }, 46 | { 47 | "description": "Feature with invalid highway key", 48 | "newVersion": {"properties": {"higway" : "residential"}}, 49 | "oldVersion": {}, 50 | "expectedResult": { 51 | "result:missing_primary_tag": true 52 | } 53 | }, 54 | { 55 | "description": "Feature with only area=yes", 56 | "newVersion": {"properties": {"area" : "yes"}}, 57 | "oldVersion": {}, 58 | "expectedResult": { 59 | "result:missing_primary_tag": true 60 | } 61 | }, 62 | { 63 | "description": "Feature without primary tag", 64 | "newVersion": {"properties": {"area" : "yes", "name": "The Shop"}}, 65 | "oldVersion": {}, 66 | "expectedResult": { 67 | "result:missing_primary_tag": true 68 | } 69 | }, 70 | { 71 | "description": "Feature with invalid amenity key", 72 | "newVersion": { 73 | "properties": {"amennity" : "school", "name": "The school"} 74 | }, 75 | "oldVersion": {}, 76 | "expectedResult": { 77 | "result:missing_primary_tag": true 78 | } 79 | } 80 | ] 81 | } 82 | -------------------------------------------------------------------------------- /tests/fixtures/place_type_change.json: -------------------------------------------------------------------------------- 1 | { 2 | "compareFunction": "place_type_change", 3 | "fixtures": [ 4 | { 5 | "description": "Flags place type is changed", 6 | "newVersion": { 7 | "type": "Feature", 8 | "properties": { 9 | "osm:id": 1234, 10 | "osm:type": "node", 11 | "osm:uid": 123, 12 | "osm:changeset": 123, 13 | "place": "city", 14 | "name": "Ellenton", 15 | "osm:version": 10, 16 | "wikidata": "Q12345" 17 | }, 18 | "geometry": { 19 | "type": "Point", 20 | "coordinates": [ 21 | 10, 22 | 10 23 | ] 24 | } 25 | }, 26 | "oldVersion": { 27 | "type": "Feature", 28 | "properties": { 29 | "osm:id": 1234, 30 | "osm:type": "node", 31 | "osm:uid": 123, 32 | "osm:changeset": 123, 33 | "place": "village", 34 | "name": "Ellenton", 35 | "osm:version": 10, 36 | "wikidata": "Q12345" 37 | }, 38 | "geometry": { 39 | "type": "Point", 40 | "coordinates": [ 41 | 11, 42 | 11 43 | ] 44 | } 45 | }, 46 | "expectedResult": { 47 | "result:place_type_change": true 48 | } 49 | }, 50 | { 51 | "description": "Does not flag non-important place tag edited - geometry changed", 52 | "newVersion": { 53 | "type": "Feature", 54 | "properties": { 55 | "osm:id": 1234, 56 | "osm:type": "node", 57 | "osm:uid": 123, 58 | "osm:changeset": 123, 59 | "place": "abcd" 60 | }, 61 | "geometry": { 62 | "type": "Point", 63 | "coordinates": [ 64 | 10, 65 | 10 66 | ] 67 | } 68 | }, 69 | "oldVersion": { 70 | "type": "Feature", 71 | "properties": { 72 | "osm:id": 1234, 73 | "osm:type": "node", 74 | "osm:uid": 124, 75 | "osm:changeset": 124, 76 | "place": "abcd" 77 | }, 78 | "geometry": { 79 | "type": "Point", 80 | "coordinates": [ 81 | 11, 82 | 11 83 | ] 84 | } 85 | }, 86 | "expectedResult": false 87 | } 88 | ] 89 | } 90 | -------------------------------------------------------------------------------- /forked/naughty-words/ru.json: -------------------------------------------------------------------------------- 1 | [ 2 | "bychara", 3 | "byk", 4 | "chernozhopyi", 5 | "dolboy'eb", 6 | "ebalnik", 7 | "ebalo", 8 | "ebalom sch'elkat", 9 | "gol", 10 | "mudack", 11 | "opizdenet", 12 | "osto'eblo", 13 | "ostokhuitel'no", 14 | "ot'ebis", 15 | "otmudohat", 16 | "otpizdit", 17 | "otsosi", 18 | "padlo", 19 | "pedik", 20 | "perdet", 21 | "petuh", 22 | "pidar gnoinyj", 23 | "pizda", 24 | "pizdato", 25 | "pizdatyi", 26 | "piz'det", 27 | "pizdetc", 28 | "pizdoi nakryt'sja", 29 | "pizd'uk", 30 | "pizdyulina", 31 | "podi ku'evo", 32 | "poeben", 33 | "po'imat' na konchik", 34 | "po'iti posrat", 35 | "po khuy", 36 | "poluchit pizdy", 37 | "pososi moyu konfetku", 38 | "prissat", 39 | "proebat", 40 | "promudobl'adsksya pizdopro'ebina", 41 | "propezdoloch", 42 | "prosrat", 43 | "raspeezdeyi", 44 | "raspizdatyi", 45 | "raz'yebuy", 46 | "raz'yoba", 47 | "s'ebat'sya", 48 | "shalava", 49 | "styervo", 50 | "sukin syn", 51 | "svodit posrat", 52 | "svoloch", 53 | "trakhat'sya", 54 | "trimandoblydskiy pizdoproyob", 55 | "ubl'yudok", 56 | "uboy", 57 | "u'ebitsche", 58 | "vafl'a", 59 | "vafli lovit", 60 | "v pizdu", 61 | "vyperdysh", 62 | "vzdrochennyi", 63 | "yeb vas", 64 | "za'ebat", 65 | "zaebis", 66 | "zalupa", 67 | "zalupat", 68 | "zasranetc", 69 | "zassat", 70 | "zlo'ebuchy", 71 | "бардак", 72 | "бздёнок", 73 | "блядки", 74 | "блядовать", 75 | "блядство", 76 | "блядь", 77 | "бугор", 78 | "во пизду", 79 | "встать раком", 80 | "выёбываться", 81 | "гандон", 82 | "говно", 83 | "говнюк", 84 | "голый", 85 | "дать пизды", 86 | "дерьмо", 87 | "дрочить", 88 | "другой дразнится", 89 | "ёбарь", 90 | "ебать", 91 | "ебать-копать", 92 | "ебло", 93 | "ебнуть", 94 | "ёб твою мать", 95 | "жопа", 96 | "жополиз", 97 | "играть на кожаной флейте", 98 | "измудохать", 99 | "каждый дрочит как он хочет", 100 | "какая разница", 101 | "как два пальца обоссать", 102 | "курите мою трубку", 103 | "лысого в кулаке гонять", 104 | "малофя", 105 | "манда", 106 | "мандавошка", 107 | "мент", 108 | "муда", 109 | "мудило", 110 | "мудозмон", 111 | "наебать", 112 | "наебениться", 113 | "наебнуться", 114 | "на фиг", 115 | "на хуй", 116 | "на хую вертеть", 117 | "на хуя", 118 | "нахуячиться", 119 | "невебенный", 120 | "не ебет", 121 | "ни за хуй собачу", 122 | "ни хуя", 123 | "обнаженный", 124 | "обоссаться можно", 125 | "один ебётся", 126 | "опесдол", 127 | "офигеть", 128 | "охуеть", 129 | "охуительно", 130 | "половое сношение", 131 | "секс", 132 | "сиски", 133 | "спиздить", 134 | "срать", 135 | "ссать", 136 | "траxать", 137 | "ты мне ваньку не валяй", 138 | "фига", 139 | "хапать", 140 | "хер с ней", 141 | "хер с ним", 142 | "хохол", 143 | "хрен", 144 | "хуёво", 145 | "хуёвый", 146 | "хуем груши околачивать", 147 | "хуеплет", 148 | "хуило", 149 | "хуиней страдать", 150 | "хуиня", 151 | "хуй", 152 | "хуйнуть", 153 | "хуй пинать" 154 | ] 155 | -------------------------------------------------------------------------------- /scripts/download_user_blocks.py: -------------------------------------------------------------------------------- 1 | """Download user blocks from: http://www.openstreetmap.org/user_blocks 2 | 3 | Following is the content format: 4 |
5 |
6 |
7 |

Firefishy blocked by blackadder

8 | 11 |
12 |
13 |
14 |
15 |

Created: over 6 years ago

16 |

Status: Ended over 6 years ago.

17 |

Reason for block:

18 |

This is testing the soft ban feature. Firefishy is not really a bad chap.

19 |
20 |
21 |
22 | """ 23 | import json 24 | import datetime 25 | import requests 26 | from bs4 import BeautifulSoup 27 | 28 | filename = 'user_blocks.json' 29 | url = 'http://www.openstreetmap.org/user_blocks' 30 | dt_format = '%d %B %Y at %H:%M' 31 | 32 | ## Start and end of block ids. 33 | block_ids = range(1, 940) 34 | 35 | def parse_dt(string): 36 | return datetime.datetime.strptime(string, dt_format) 37 | 38 | for i in block_ids: 39 | print i 40 | result = dict() 41 | result['id'] = i 42 | response = requests.get('{}/{}'.format(url, i)) 43 | soup = BeautifulSoup(response.text, 'html.parser') 44 | content = soup.find(id='content') 45 | print content 46 | 47 | if content.find('h1') is None: 48 | continue 49 | 50 | blocked_user, blocked_by = [user.text for user in content.find('h1').find_all('a')] 51 | result['blocked_user'] = blocked_user 52 | result['blocked_by'] = blocked_by 53 | 54 | paragraphs = content.find('div', class_='content-body').find('div', class_='content-inner').find_all('p') 55 | # print '***********************' 56 | # print paragraphs 57 | 58 | created_at_index, ends_at_index = (0, 1) 59 | if 'Revoker' in paragraphs[0].text: 60 | created_at_index, ends_at_index = (1, 2) 61 | 62 | # Convert to string to write as JSON. 63 | created_at = str(parse_dt(paragraphs[created_at_index].find('span')['title'])) 64 | result['created_at'] = str(created_at) 65 | 66 | if paragraphs[1].find('span') is not None: 67 | ends_at = parse_dt(paragraphs[ends_at_index].find('span')['title']) 68 | else: 69 | ends_at = paragraphs[1].text.replace('Status:', '').strip() 70 | ends_at = str(ends_at) 71 | result['ends_at'] = str(ends_at) 72 | 73 | print created_at, ends_at 74 | 75 | reason = content.find('div', class_='richtext') 76 | if reason is not None: 77 | reason = str(reason) 78 | else: 79 | reason = str() 80 | result['reason'] = reason 81 | 82 | # Read previously downloaded results. 83 | with open(filename) as f: 84 | results = json.load(f) 85 | 86 | # Append new result. 87 | results.append(result) 88 | 89 | # Write back results to file. 90 | with open(filename, 'w') as f: 91 | json.dump(results, f, indent=4) 92 | --------------------------------------------------------------------------------