├── src ├── assets │ ├── .gitkeep │ ├── nr1.png │ ├── nr2.png │ ├── help1.png │ ├── help2.png │ ├── help3.png │ ├── nr1-24x24.png │ ├── nr2-24x24.png │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── favicon-96x96.png │ ├── transport │ │ ├── bus.png │ │ ├── train.png │ │ ├── tram.png │ │ ├── airport.png │ │ ├── platform.png │ │ ├── station.png │ │ ├── bus_small.png │ │ ├── trolleybus.png │ │ ├── railway │ │ │ ├── tram.png │ │ │ ├── crossing.png │ │ │ ├── crossing.svg │ │ │ ├── subway.svg │ │ │ └── tram.svg │ │ ├── railway_station.png │ │ ├── bus.svg │ │ ├── train.svg │ │ ├── platform.svg │ │ ├── trolleybus.svg │ │ └── station.svg │ ├── favicon-194x194.png │ ├── traffic │ │ ├── crossing.png │ │ ├── traffic_signals.png │ │ ├── traffic_signals.svg │ │ └── crossing.svg │ └── android-chrome-192x192.png ├── components │ ├── expert │ │ ├── expert.component.less │ │ ├── expert.component.ts │ │ └── expert.component.html │ ├── lang │ │ ├── lang.component.less │ │ ├── lang.component.ts │ │ └── lang.component.html │ ├── beginner │ │ ├── beginner.component.less │ │ ├── beginner.component.html │ │ └── beginner.component.ts │ ├── settings │ │ ├── settings.component.less │ │ ├── settings.component.html │ │ └── settings.component.ts │ ├── sidebar │ │ ├── route-browser.component.less │ │ ├── stop-browser.component.less │ │ ├── tag-browser.component.less │ │ ├── relation-browser.component.less │ │ ├── validation-browser.component.less │ │ ├── relation-browser.component.html │ │ ├── stop-browser.component.ts │ │ ├── stop-browser.component.html │ │ ├── relation-browser.component.ts │ │ └── route-browser.component.html │ ├── tutorials │ │ ├── tutorials.component.less │ │ ├── tutorials.component.html │ │ └── tutorials.component.ts │ ├── modal │ │ └── modal.component.less │ ├── auth │ │ ├── auth.component.less │ │ ├── auth.component.html │ │ └── auth.component.ts │ ├── transporter │ │ ├── transporter.component.less │ │ ├── transporter.component.ts │ │ └── transporter.component.html │ ├── navigator │ │ ├── navigator.component.less │ │ ├── navigator.component.html │ │ └── navigator.component.ts │ ├── editor │ │ ├── editor.component.less │ │ └── editor.component.html │ ├── app │ │ ├── app.component.less │ │ ├── app.component.html │ │ └── app.component.ts │ ├── route-master-wizard │ │ └── route-master-wizard.component.less │ ├── route-wizard │ │ └── route-wizard.component.less │ └── toolbar │ │ ├── toolbar.component.less │ │ └── toolbar.component.html ├── .stylelintrc ├── styles.css ├── core │ ├── latLng.interface.ts │ ├── ptMember.ts │ ├── ptWay.interface.ts │ ├── areaRef.interface.ts │ ├── location.class.ts │ ├── osmAuthOptions.interface.ts │ ├── osmElement.interface.ts │ ├── ptRelationStopArea.interface.ts │ ├── metadata.interface.ts │ ├── overpassResponse.interface.ts │ ├── ptRelationTags.interface.ts │ ├── ptRelationNew.interface.ts │ ├── errorObject.interface.ts │ ├── ptRouteMasterNew.interface.ts │ ├── editingOptions.interface.ts │ ├── ptStop.interface.ts │ ├── ptRelation.interface.ts │ ├── responses.interface.ts │ ├── other.ts │ ├── utils.class.ts │ └── ptTags.class.ts ├── environments │ ├── environment.hmr.ts │ ├── environment.prod.ts │ └── environment.ts ├── typings │ ├── leaflet.vectorgrid.d.ts │ └── require.d.ts ├── typings.d.ts ├── store │ ├── epics.ts │ ├── reducers.ts │ ├── model.ts │ ├── module.ts │ └── app │ │ ├── reducer.ts │ │ └── actions.ts ├── tsconfig.app.json ├── land.html ├── pipes │ ├── keys.pipe.ts │ └── tags-prop.pipe.ts ├── raven-error-handler.ts ├── .browserslistrc ├── services │ ├── auth.service.ts │ ├── warn.service.ts │ ├── conf.service.ts │ ├── geocode.service.ts │ └── tutorials.json ├── hmr.ts ├── land_single.html ├── test.ts ├── main.ts ├── database │ └── dexiedb.ts ├── karma.conf.js ├── manifest.json ├── styles │ └── main.less ├── index.html ├── polyfills.ts └── app.module.ts ├── .github ├── PULL_REQUEST_TEMPLATE.md ├── ISSUE_TEMPLATE.md ├── dependabot.yml ├── CONTRIBUTING.md └── CODE_OF_CONDUCT.md ├── nginx-custom.conf ├── e2e └── src │ └── app.po.ts ├── tsconfig.app.json ├── .editorconfig ├── .vscode ├── settings.json ├── launch.json ├── extensions.json └── tasks.json ├── Dockerfile ├── tsconfig.json ├── test └── main.spec.ts ├── .eslintrc ├── LICENSE ├── .gitignore ├── README.md ├── package.json └── angular.json /src/assets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/expert/expert.component.less: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/lang/lang.component.less: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/beginner/beginner.component.less: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/settings/settings.component.less: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/sidebar/route-browser.component.less: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/sidebar/stop-browser.component.less: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/sidebar/tag-browser.component.less: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/tutorials/tutorials.component.less: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/sidebar/relation-browser.component.less: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/.stylelintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "stylelint-config-standard" 3 | } 4 | -------------------------------------------------------------------------------- /src/assets/nr1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dkocich/osm-pt-ngx-leaflet/HEAD/src/assets/nr1.png -------------------------------------------------------------------------------- /src/assets/nr2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dkocich/osm-pt-ngx-leaflet/HEAD/src/assets/nr2.png -------------------------------------------------------------------------------- /src/styles.css: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | -------------------------------------------------------------------------------- /src/assets/help1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dkocich/osm-pt-ngx-leaflet/HEAD/src/assets/help1.png -------------------------------------------------------------------------------- /src/assets/help2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dkocich/osm-pt-ngx-leaflet/HEAD/src/assets/help2.png -------------------------------------------------------------------------------- /src/assets/help3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dkocich/osm-pt-ngx-leaflet/HEAD/src/assets/help3.png -------------------------------------------------------------------------------- /src/assets/nr1-24x24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dkocich/osm-pt-ngx-leaflet/HEAD/src/assets/nr1-24x24.png -------------------------------------------------------------------------------- /src/assets/nr2-24x24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dkocich/osm-pt-ngx-leaflet/HEAD/src/assets/nr2-24x24.png -------------------------------------------------------------------------------- /src/assets/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dkocich/osm-pt-ngx-leaflet/HEAD/src/assets/favicon-16x16.png -------------------------------------------------------------------------------- /src/assets/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dkocich/osm-pt-ngx-leaflet/HEAD/src/assets/favicon-32x32.png -------------------------------------------------------------------------------- /src/assets/favicon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dkocich/osm-pt-ngx-leaflet/HEAD/src/assets/favicon-96x96.png -------------------------------------------------------------------------------- /src/assets/transport/bus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dkocich/osm-pt-ngx-leaflet/HEAD/src/assets/transport/bus.png -------------------------------------------------------------------------------- /src/core/latLng.interface.ts: -------------------------------------------------------------------------------- 1 | export interface ILatLng { 2 | latitude: number; 3 | longitude: number; 4 | } 5 | -------------------------------------------------------------------------------- /src/core/ptMember.ts: -------------------------------------------------------------------------------- 1 | export interface IPtMember { 2 | type: string; 3 | ref: number; 4 | role: string; 5 | } 6 | -------------------------------------------------------------------------------- /src/environments/environment.hmr.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: false, 3 | hmr: true, 4 | }; 5 | -------------------------------------------------------------------------------- /src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true, 3 | hmr: false, 4 | }; 5 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Related to/Fixes issue #XXX 2 | 3 | ## Proposed Changes 4 | 5 | - 6 | - 7 | - 8 | -------------------------------------------------------------------------------- /src/assets/favicon-194x194.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dkocich/osm-pt-ngx-leaflet/HEAD/src/assets/favicon-194x194.png -------------------------------------------------------------------------------- /src/assets/transport/train.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dkocich/osm-pt-ngx-leaflet/HEAD/src/assets/transport/train.png -------------------------------------------------------------------------------- /src/assets/transport/tram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dkocich/osm-pt-ngx-leaflet/HEAD/src/assets/transport/tram.png -------------------------------------------------------------------------------- /src/assets/traffic/crossing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dkocich/osm-pt-ngx-leaflet/HEAD/src/assets/traffic/crossing.png -------------------------------------------------------------------------------- /src/assets/transport/airport.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dkocich/osm-pt-ngx-leaflet/HEAD/src/assets/transport/airport.png -------------------------------------------------------------------------------- /src/assets/transport/platform.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dkocich/osm-pt-ngx-leaflet/HEAD/src/assets/transport/platform.png -------------------------------------------------------------------------------- /src/assets/transport/station.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dkocich/osm-pt-ngx-leaflet/HEAD/src/assets/transport/station.png -------------------------------------------------------------------------------- /src/assets/transport/bus_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dkocich/osm-pt-ngx-leaflet/HEAD/src/assets/transport/bus_small.png -------------------------------------------------------------------------------- /src/assets/transport/trolleybus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dkocich/osm-pt-ngx-leaflet/HEAD/src/assets/transport/trolleybus.png -------------------------------------------------------------------------------- /src/assets/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dkocich/osm-pt-ngx-leaflet/HEAD/src/assets/android-chrome-192x192.png -------------------------------------------------------------------------------- /src/assets/traffic/traffic_signals.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dkocich/osm-pt-ngx-leaflet/HEAD/src/assets/traffic/traffic_signals.png -------------------------------------------------------------------------------- /src/assets/transport/railway/tram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dkocich/osm-pt-ngx-leaflet/HEAD/src/assets/transport/railway/tram.png -------------------------------------------------------------------------------- /src/assets/transport/railway_station.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dkocich/osm-pt-ngx-leaflet/HEAD/src/assets/transport/railway_station.png -------------------------------------------------------------------------------- /src/assets/transport/railway/crossing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dkocich/osm-pt-ngx-leaflet/HEAD/src/assets/transport/railway/crossing.png -------------------------------------------------------------------------------- /src/components/sidebar/validation-browser.component.less: -------------------------------------------------------------------------------- 1 | .scrollable-panel { 2 | height: 300px; 3 | overflow-y: scroll; 4 | width: 100%; 5 | } 6 | -------------------------------------------------------------------------------- /src/typings/leaflet.vectorgrid.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace L { 2 | namespace vectorGrid { 3 | export function slicer(data, options?); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/core/ptWay.interface.ts: -------------------------------------------------------------------------------- 1 | import { IOsmElement } from './osmElement.interface'; 2 | 3 | export interface IPtWay extends IOsmElement { 4 | nodes: number[]; 5 | } 6 | -------------------------------------------------------------------------------- /src/components/modal/modal.component.less: -------------------------------------------------------------------------------- 1 | .scrollable-panel { 2 | height: 200px; 3 | overflow-y: scroll; 4 | width: 100%; 5 | } 6 | 7 | li { 8 | text-align: left; 9 | } 10 | -------------------------------------------------------------------------------- /src/core/areaRef.interface.ts: -------------------------------------------------------------------------------- 1 | import { LatLng } from 'leaflet'; 2 | 3 | export interface IAreaRef { 4 | areaPseudoId: string; 5 | overpassBox: string[]; 6 | viewCenter: LatLng; 7 | } 8 | -------------------------------------------------------------------------------- /nginx-custom.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | location / { 4 | root /usr/share/nginx/html; 5 | index index.html index.htm; 6 | try_files $uri $uri/ /index.html =404; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/components/auth/auth.component.less: -------------------------------------------------------------------------------- 1 | .user_thumbnail_tiny { 2 | border-radius: 2px; 3 | height: auto; 4 | margin: 4px 0 0 4px; 5 | max-width: 40px; 6 | max-height: 40px; 7 | width: auto; 8 | } 9 | -------------------------------------------------------------------------------- /src/components/transporter/transporter.component.less: -------------------------------------------------------------------------------- 1 | @import '../../styles/main.less'; 2 | 3 | .tag { 4 | margin: 0 10px 0 10px; 5 | cursor: pointer; 6 | } 7 | 8 | .modal-body { 9 | padding-bottom: 50px; 10 | } 11 | -------------------------------------------------------------------------------- /src/typings.d.ts: -------------------------------------------------------------------------------- 1 | /* SystemJS module definition */ 2 | declare let module: INodeModule; 3 | 4 | interface INodeModule { 5 | id: string; 6 | } 7 | 8 | declare module '*.json' { 9 | const value; 10 | export default value; 11 | } 12 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Expected Behavior 2 | 3 | 4 | ## Actual Behavior 5 | 6 | 7 | ## Steps to Reproduce the Problem 8 | 9 | 1. 10 | 2. 11 | 3. 12 | 13 | ## Specifications 14 | 15 | - Browser version: 16 | - OS: 17 | -------------------------------------------------------------------------------- /src/store/epics.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | 3 | @Injectable() 4 | export class RootEpics { 5 | constructor() { 6 | // 7 | } 8 | 9 | createEpics() { 10 | return [ 11 | // 'app', 12 | ]; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "module": "es2015", 6 | "types": [] 7 | }, 8 | "exclude": [ 9 | "test.ts", 10 | "**/*.spec.ts" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /e2e/src/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, by, element } from 'protractor'; 2 | 3 | export class AppPage { 4 | navigateTo() { 5 | return browser.get('/'); 6 | } 7 | 8 | getParagraphText() { 9 | return element(by.css('app-root h1')).getText(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/land.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | osm auth 6 | 7 | 8 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/core/location.class.ts: -------------------------------------------------------------------------------- 1 | import { LatLngBounds } from 'leaflet'; 2 | import { ILatLng } from './latLng.interface'; 3 | 4 | export class Location implements ILatLng { 5 | latitude: number; 6 | longitude: number; 7 | address: string; 8 | viewBounds: LatLngBounds; 9 | } 10 | -------------------------------------------------------------------------------- /tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./out-tsc/app", 5 | "types": [] 6 | }, 7 | "files": [ 8 | "src/main.ts", 9 | "src/polyfills.ts" 10 | ], 11 | "include": [ 12 | "src/**/*.d.ts" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /src/core/osmAuthOptions.interface.ts: -------------------------------------------------------------------------------- 1 | export interface IOSMAuthOptions { 2 | oauth_consumer_key: string; 3 | oauth_secret: string; 4 | url?: string; 5 | auto?: boolean; 6 | loading?: () => any; 7 | done?: () => any; 8 | landing?: string; 9 | singlepage?: boolean; 10 | } 11 | -------------------------------------------------------------------------------- /src/core/osmElement.interface.ts: -------------------------------------------------------------------------------- 1 | import { IPtTags } from './ptTags.class'; 2 | 3 | export interface IOsmElement { 4 | type: string; 5 | id: number; 6 | timestamp: string; 7 | version: number; 8 | changeset: number; 9 | user: string; 10 | uid: number; 11 | tags: IPtTags; 12 | } 13 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.ts] 12 | quote_type = single 13 | 14 | [*.md] 15 | max_line_length = off 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /src/core/ptRelationStopArea.interface.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * "name": "Frýdek-Místek, Frýdek, Revoluční", 3 | * "public_transport": "stop_area", 4 | * "type": "public_transport" 5 | */ 6 | import { TStrStopArea } from './other'; 7 | 8 | export interface IPtRelationStopArea { 9 | name?: string; 10 | public_transport: TStrStopArea; 11 | type?: string; 12 | } 13 | -------------------------------------------------------------------------------- /src/typings/require.d.ts: -------------------------------------------------------------------------------- 1 | // webpack require() definition for ts-loader 2 | // source: https://github.com/TypeStrong/ts-loader 3 | declare let require: { 4 | (path: string): T; 5 | (paths: string[], callback: (...modules) => void): void; 6 | ensure: ( 7 | paths: string[], 8 | callback: (require: (path: string) => T) => void, 9 | ) => void; 10 | }; 11 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "jira-plugin.workingProject": "", 3 | "i18n-ally.localesPaths": [ 4 | "src/assets/i18n", 5 | "src/components/lang" 6 | ], 7 | "files.exclude": {}, 8 | "docwriter.progress.trackFunctions": false, 9 | "docwriter.progress.trackClasses": true, 10 | "docwriter.progress.trackMethods": false, 11 | "docwriter.progress.trackTypes": true 12 | } 13 | -------------------------------------------------------------------------------- /src/pipes/keys.pipe.ts: -------------------------------------------------------------------------------- 1 | import { Pipe, PipeTransform } from '@angular/core'; 2 | 3 | @Pipe({ name: 'keys', pure: true }) 4 | export class KeysPipe implements PipeTransform { 5 | transform(value, args: string[]) { 6 | const keys = []; 7 | for (const key in value) { 8 | if (value.hasOwnProperty(key)) { 9 | keys.push({ key, value: value[key] }); 10 | } 11 | } 12 | return keys; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // The file contents for the current environment will overwrite these during build. 2 | // The build system defaults to the dev environment which uses `environment.ts`, but if you do 3 | // `ng build --env=prod` then `environment.prod.ts` will be used instead. 4 | // The list of which env maps to which file can be found in `.angular-cli.json`. 5 | 6 | export const environment = { 7 | production: false, 8 | hmr: false, 9 | }; 10 | -------------------------------------------------------------------------------- /src/raven-error-handler.ts: -------------------------------------------------------------------------------- 1 | import { ErrorHandler } from '@angular/core'; 2 | import * as Raven from 'raven-js'; 3 | import { Utils } from './core/utils.class'; 4 | 5 | if (Utils.isProductionDeployment()) { 6 | Raven.config( 7 | 'https://6a8266c320b44a1890c43313027c1f2b@sentry.io/1199897', 8 | ).install(); 9 | } 10 | 11 | export class RavenErrorHandler implements ErrorHandler { 12 | handleError(err): void { 13 | Raven.captureException(err); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/.browserslistrc: -------------------------------------------------------------------------------- 1 | # This file is used by the build system to adjust CSS and JS output to support the specified browsers below. 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | 5 | # You can see what browsers were selected by your queries by running: 6 | # npx browserslist 7 | 8 | > 0.5% 9 | last 2 versions 10 | Firefox ESR 11 | not dead 12 | not IE 9-11 # For IE 9-11 support, remove 'not'. -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: npm 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 5 8 | ignore: 9 | # For all packages, ignore all patch updates 10 | - dependency-name: "*" 11 | update-types: [ "version-update:semver-patch" ] 12 | 13 | # For all packages, ignore all patch updates 14 | - dependency-name: "*" 15 | update-types: [ "version-update:semver-major" ] 16 | -------------------------------------------------------------------------------- /src/core/metadata.interface.ts: -------------------------------------------------------------------------------- 1 | export interface IMetadata { 2 | id: number; 3 | timestamp: number; 4 | type: string; 5 | 6 | // for stop/platforms: all parent routes have been downloaded?, 7 | // for routes: all members have been downloaded? 8 | isCompletelyDownloaded?: number; 9 | parentRoutes?: number[]; // only for routes 10 | memberStops?: number[]; // only for stops 11 | memberPlatforms?: number[]; // only for platforms 12 | isQueriedForMasters?: number; // only for routes 13 | } 14 | -------------------------------------------------------------------------------- /src/pipes/tags-prop.pipe.ts: -------------------------------------------------------------------------------- 1 | import { Pipe, PipeTransform } from '@angular/core'; 2 | 3 | @Pipe({ 4 | name: 'tagsProp', 5 | }) 6 | export class TagsPropPipe implements PipeTransform { 7 | transform(valueObj: { tags?: object }, ...args: unknown[]): unknown { 8 | const keys = []; 9 | const tagsProp = valueObj.tags; 10 | for (const key in tagsProp) { 11 | if (valueObj.hasOwnProperty(key)) { 12 | keys.push({ key, value: valueObj[key] }); 13 | } 14 | } 15 | return keys; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/core/overpassResponse.interface.ts: -------------------------------------------------------------------------------- 1 | import { IOsmElement } from './osmElement.interface'; 2 | 3 | export interface IOverpassResponse extends Response { 4 | elements: [IOsmElement]; 5 | generator: string; // "Overpass API 0.7.55 579b1eec" 6 | osm3s: { 7 | copyright: string; // "The data included in this document is from www.openstreetmap.org. 8 | // The data is made available under ODbL." 9 | timestamp_osm_base: string; // date string - "2018-05-02T19:51:03Z" 10 | }; 11 | version: number; // 0.6 12 | } 13 | -------------------------------------------------------------------------------- /src/components/navigator/navigator.component.less: -------------------------------------------------------------------------------- 1 | @import '../../styles/main.less'; 2 | 3 | input { 4 | margin: 20px 0 0 20px; 5 | width: 300px; 6 | height: 35px; 7 | border: 2px solid rgba(77, 156, 237, 0.7); 8 | font: rgb(142, 142, 142); 9 | font-size: 16px; 10 | } 11 | 12 | #goto { 13 | margin: 20px 0 0 330px; 14 | .map-button 15 | } 16 | 17 | @media screen and (min-width: 320px) and (max-width: 767px) { 18 | input { 19 | width: 133px; 20 | } 21 | 22 | #goto { 23 | margin: 20px 0 0 165px; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/store/reducers.ts: -------------------------------------------------------------------------------- 1 | // Define the global store shape by combining our application's 2 | // reducers together into a given structure. 3 | import { composeReducers, defaultFormReducer } from '@angular-redux/form'; 4 | import { routerReducer } from '@angular-redux/router'; 5 | import { combineReducers } from 'redux'; 6 | import { appReducer } from './app/reducer'; 7 | 8 | export const rootReducer = composeReducers( 9 | defaultFormReducer(), 10 | combineReducers({ 11 | app: appReducer, 12 | router: routerReducer, 13 | }), 14 | ); 15 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Stage 0, based on Node.js, to build and compile Angular 2 | FROM node:20 as node 3 | 4 | WORKDIR /code 5 | COPY ./ /code 6 | 7 | RUN npm ci 8 | 9 | # NOTE: build is currently run after installation phase automatically 10 | # ARG env=prod 11 | # RUN npm run ngbuild -- --environment $env 12 | 13 | # Stage 1, based on Nginx, to have only the compiled app, ready for production with Nginx 14 | FROM nginx:1-25 15 | 16 | COPY --from=node /code/public/ /usr/share/nginx/html 17 | 18 | COPY ./nginx-custom.conf /etc/nginx/conf.d/default.conf 19 | -------------------------------------------------------------------------------- /src/components/editor/editor.component.less: -------------------------------------------------------------------------------- 1 | @import '../../styles/main.less'; 2 | 3 | #edits-backward-btn { 4 | margin: 20px 0 0 595px; 5 | .map-button; 6 | } 7 | 8 | #edits-forward-btn { 9 | margin: 20px 0 0 640px; 10 | .map-button; 11 | } 12 | 13 | #edits-count { 14 | margin: 20px 0 0 685px; 15 | } 16 | 17 | #toggle-edit { 18 | margin: 20px 0 0 550px; 19 | .map-button; 20 | } 21 | 22 | #stop-btn { 23 | margin: 80px 0 0 20px; 24 | } 25 | 26 | #platform-btn { 27 | margin: 120px 0 0 20px; 28 | } 29 | 30 | #wizard-btn { 31 | margin: 160px 0 0 20px; 32 | } 33 | -------------------------------------------------------------------------------- /src/core/ptRelationTags.interface.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * "from": "Řepiště, U kříže", 3 | * "name": " Bus 11: Řepiště, U kříže -> Místek,Riviéra", 4 | * "operator": "ČSAD Frýdek-Místek", 5 | * "public_transport:version": "2", 6 | * "route": "bus", 7 | * "to": "Místek,Riviéra", 8 | * "type": "route" 9 | */ 10 | 11 | export interface IPtRelationTags { 12 | colour?: string; 13 | from?: string; 14 | to?: string; 15 | name?: string; 16 | 'public_transport:version'?: string; 17 | type?: string; 18 | route?: string; 19 | route_ref?: string; 20 | operator?: string; 21 | ref?: string; 22 | } 23 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Launch Angular", 6 | "type": "chrome", 7 | "request": "launch", 8 | "preLaunchTask": "npm: start", 9 | "url": "http://localhost:4200/", 10 | "webRoot": "${workspaceFolder}" 11 | }, 12 | { 13 | "name": "Launch Angular", 14 | "type": "edge", 15 | "version": "dev", 16 | "request": "launch", 17 | "preLaunchTask": "npm: start", 18 | "url": "http://localhost:4200/", 19 | "webRoot": "${workspaceFolder}" 20 | } 21 | ], 22 | "compounds": [] 23 | } 24 | -------------------------------------------------------------------------------- /src/services/auth.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { IOSMAuthOptions } from '../core/osmAuthOptions.interface'; 3 | import { ConfService } from './conf.service'; 4 | 5 | @Injectable() 6 | export class AuthService { 7 | private osmAuth = require('osm-auth'); 8 | private osmAuthOptions: IOSMAuthOptions = { 9 | oauth_consumer_key: ConfService.apiConsumerKey, 10 | oauth_secret: ConfService.apiConsumerSecret, 11 | singlepage: false, 12 | url: ConfService.baseOsmUrl, 13 | }; 14 | oauth = this.osmAuth(this.osmAuthOptions); 15 | constructor() { 16 | // 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "outDir": "./dist/out-tsc", 6 | "sourceMap": true, 7 | "declaration": false, 8 | "downlevelIteration": true, 9 | "experimentalDecorators": true, 10 | "module": "esnext", 11 | "moduleResolution": "node", 12 | "importHelpers": true, 13 | "target": "es2015", 14 | "lib": [ 15 | "es2018", 16 | "dom" 17 | ] 18 | }, 19 | "angularCompilerOptions": { 20 | "fullTemplateTypeCheck": true, 21 | "strictInjectionParameters": true, 22 | "enableIvy": false, 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/components/navigator/navigator.component.html: -------------------------------------------------------------------------------- 1 | 4 | 7 | 8 | 13 | -------------------------------------------------------------------------------- /src/core/ptRelationNew.interface.ts: -------------------------------------------------------------------------------- 1 | import { IOsmElement } from './osmElement.interface'; 2 | import { EnumWheelchair, TStrPtv2, TStrRelation, TStrRoute } from './other'; 3 | import { IPtMember } from './ptMember'; 4 | 5 | export interface IPtRelationNew extends IOsmElement { 6 | type: TStrRelation; 7 | members: IPtMember[]; 8 | tags: { 9 | type: TStrRoute; 10 | route: string; 11 | ref: string; 12 | network: string; 13 | operator: string; 14 | name: string; 15 | from: string; 16 | to: string; 17 | wheelchair: EnumWheelchair; 18 | colour: string; 19 | 'public_transport:version': TStrPtv2; 20 | }; 21 | } 22 | -------------------------------------------------------------------------------- /src/assets/transport/railway/crossing.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/core/errorObject.interface.ts: -------------------------------------------------------------------------------- 1 | import { IPtStop } from './ptStop.interface'; 2 | 3 | export interface INameErrorObject { 4 | stop: IPtStop; 5 | corrected: string; 6 | } 7 | 8 | export interface IRefErrorObject { 9 | stop: IPtStop; 10 | corrected: string; 11 | totalConnectedRefs: number; 12 | missingConnectedRefs: number; 13 | } 14 | 15 | export interface IWayErrorObject { 16 | stop: IPtStop; 17 | corrected: string; 18 | wayIDs: number[]; 19 | } 20 | 21 | export interface IPTvErrorObject { 22 | stop: IPtStop; 23 | corrected: string; 24 | } 25 | 26 | export interface IPTPairErrorObject { 27 | stop: IPtStop; 28 | corrected: string; 29 | } 30 | -------------------------------------------------------------------------------- /src/core/ptRouteMasterNew.interface.ts: -------------------------------------------------------------------------------- 1 | import { IOsmElement } from './osmElement.interface'; 2 | import { 3 | EnumRouteMaster, 4 | EnumWheelchair, 5 | TStrRelation, 6 | TStrRouteMaster, 7 | } from './other'; 8 | import { IPtMember } from './ptMember'; 9 | 10 | export interface IPtRouteMasterNew extends IOsmElement { 11 | type: TStrRelation; 12 | members: IPtMember[]; 13 | tags: { 14 | type?: TStrRouteMaster; 15 | route_master?: EnumRouteMaster; 16 | ref?: string; 17 | network?: string; 18 | operator?: string; 19 | name?: string; 20 | wheelchair?: EnumWheelchair; 21 | colour?: string; 22 | 'public_transport:version'?: '2'; 23 | }; 24 | } 25 | -------------------------------------------------------------------------------- /src/components/lang/lang.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { TranslateService } from '@ngx-translate/core'; 3 | 4 | // import { BsDropdownModule } from "ngx-bootstrap/dropdown"; 5 | 6 | @Component({ 7 | providers: [], 8 | selector: 'lang', 9 | styleUrls: ['./lang.component.less', '../../styles/main.less'], 10 | templateUrl: './lang.component.html', 11 | }) 12 | export class LangComponent { 13 | constructor(public translate: TranslateService) { 14 | translate.setDefaultLang('en'); 15 | this.switchLanguage(translate.defaultLang); 16 | } 17 | 18 | switchLanguage(language: string): void { 19 | this.translate.use(language); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/hmr.ts: -------------------------------------------------------------------------------- 1 | import { ApplicationRef, NgModuleRef } from '@angular/core'; 2 | import { createNewHosts } from '@angularclass/hmr'; 3 | 4 | export const hmrBootstrap = ( 5 | module, 6 | bootstrap: () => Promise>, 7 | ) => { 8 | let ngModule: NgModuleRef; 9 | module.hot.accept(); 10 | bootstrap().then((currentModule) => (ngModule = currentModule)); 11 | module.hot.dispose(() => { 12 | const appRef: ApplicationRef = ngModule.injector.get(ApplicationRef); 13 | const elements = appRef.components.map((c) => c.location.nativeElement); 14 | const removeOldHosts = createNewHosts(elements); 15 | ngModule.destroy(); 16 | removeOldHosts(); 17 | }); 18 | }; 19 | -------------------------------------------------------------------------------- /src/core/editingOptions.interface.ts: -------------------------------------------------------------------------------- 1 | export interface IRouteBrowserOptions { 2 | changeMembers: boolean; 3 | createRoute: boolean; 4 | membersEditing: boolean; 5 | toggleFilteredView: boolean; 6 | } 7 | 8 | export interface ITagBrowserOptions { 9 | allowedKeys?: string[]; 10 | limitedKeys: boolean; 11 | makeKeysReadOnly: boolean; 12 | } 13 | 14 | export interface ISuggestionsBrowserOptions { 15 | nameSuggestions: ISuggestions; 16 | refSuggestions: ISuggestions; 17 | waySuggestions: ISuggestions; 18 | PTvSuggestions: ISuggestions; 19 | ptPairSuggestions: ISuggestions; 20 | } 21 | 22 | export interface ISuggestions { 23 | found: boolean; 24 | startCorrection: boolean; 25 | } 26 | -------------------------------------------------------------------------------- /src/core/ptStop.interface.ts: -------------------------------------------------------------------------------- 1 | import { IOsmElement } from './osmElement.interface'; 2 | 3 | /** 4 | * { 5 | * "type": "node", 6 | * "id": 447767772, 7 | * "lat": 49.6769377, 8 | * "lon": 18.3665044, 9 | * "timestamp": "2017-04-20T01:22:48Z", 10 | * "version": 3, 11 | * "changeset": 47956115, 12 | * "user": "dkocich", 13 | * "uid": 1784758, 14 | * "tags": { 15 | * "bench": "yes", 16 | * "bus": "yes", 17 | * "name": "Frýdek-Místek, Frýdek, U Gustlíčka", 18 | * "public_transport": "platform", 19 | * "shelter": "yes" 20 | * } 21 | * } 22 | */ 23 | export interface IPtStop extends IOsmElement { 24 | lat: number; 25 | lon: number; 26 | } 27 | -------------------------------------------------------------------------------- /src/land_single.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | osm auth 6 | 7 | 8 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | import 'zone.js/dist/zone-testing'; 3 | import { getTestBed } from '@angular/core/testing'; 4 | import { 5 | BrowserDynamicTestingModule, 6 | platformBrowserDynamicTesting, 7 | } from '@angular/platform-browser-dynamic/testing'; 8 | 9 | declare const require; 10 | 11 | // First, initialize the Angular testing environment. 12 | getTestBed().initTestEnvironment( 13 | BrowserDynamicTestingModule, 14 | platformBrowserDynamicTesting(), 15 | ); 16 | // Then we find all the tests. 17 | const context = require.context('./', true, /\.spec\.ts$/); 18 | // And load the modules. 19 | context.keys().map(context); 20 | -------------------------------------------------------------------------------- /src/components/app/app.component.less: -------------------------------------------------------------------------------- 1 | @import '../../styles/main.less'; 2 | 3 | body { 4 | margin: 0; 5 | padding: 0; 6 | } 7 | 8 | #map { 9 | position: absolute; 10 | top: 50px; 11 | bottom: 0; 12 | width: 65%; 13 | } 14 | 15 | @media (min-width: 768px) { 16 | #map { 17 | top: 50px; 18 | } 19 | } 20 | 21 | @media (max-width: 767px) { 22 | #map { 23 | top: 0; 24 | } 25 | } 26 | 27 | #sidebar { 28 | position: absolute; 29 | bottom: 0; 30 | right: 0; 31 | width: 35%; 32 | overflow-x: hidden; 33 | overflow-y: auto; 34 | } 35 | 36 | @media (min-width: 768px) { 37 | #sidebar { 38 | top: 50px; 39 | } 40 | } 41 | 42 | @media (max-width: 767px) { 43 | #sidebar { 44 | top: 0; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /test/main.spec.ts: -------------------------------------------------------------------------------- 1 | import 'core-js'; // ES6 + reflect-metadata 2 | // zone.js 3 | import 'zone.js/dist/zone'; 4 | import 'zone.js/dist/proxy'; 5 | import 'zone.js/dist/sync-test'; 6 | import 'zone.js/dist/async-test'; 7 | import 'zone.js/dist/jasmine-patch'; 8 | 9 | // TestBed initialization 10 | import { TestBed } from '@angular/core/testing'; 11 | import { 12 | BrowserDynamicTestingModule, 13 | platformBrowserDynamicTesting, 14 | } from '@angular/platform-browser-dynamic/testing'; 15 | TestBed.initTestEnvironment( 16 | BrowserDynamicTestingModule, 17 | platformBrowserDynamicTesting(), 18 | ); 19 | 20 | // load all specs in ./src 21 | const context = (require as any).context('./', true, /\.spec\.ts$/); 22 | context.keys().map(context); 23 | -------------------------------------------------------------------------------- /src/store/model.ts: -------------------------------------------------------------------------------- 1 | // Initial state is the place you define all initial values for the Redux store of the feature. 2 | // In the 'standard' way, initialState is defined in reducers: http://redux.js.org/docs/basics/Reducers.html 3 | import { ISuggestionsBrowserOptions } from '../core/editingOptions.interface'; 4 | 5 | export interface IRootAppState { 6 | // app: IAppState; 7 | routes?; 8 | } 9 | 10 | export interface IAppState { 11 | editing: boolean; 12 | selectObject: number; 13 | advancedExpMode: boolean; 14 | goodConnectMode: boolean; 15 | errorCorrectionMode: ISuggestionsBrowserOptions; 16 | beginnerView: string; 17 | switchMode: boolean; 18 | wizardMode: string | null | 'router wizard' | 'router master wizard'; 19 | tutorialMode: boolean; 20 | } 21 | -------------------------------------------------------------------------------- /src/components/auth/auth.component.html: -------------------------------------------------------------------------------- 1 | 16 | -------------------------------------------------------------------------------- /src/components/route-master-wizard/route-master-wizard.component.less: -------------------------------------------------------------------------------- 1 | #master-route-wizard-modal-map { 2 | position: absolute; 3 | width: 100%; 4 | height: 95%; 5 | } 6 | 7 | #container { 8 | height: 90vh; 9 | width: 100%; 10 | } 11 | 12 | .modal-body { 13 | height: 80%; 14 | width: 100%; 15 | overflow-y: auto; 16 | } 17 | 18 | .modal-header { 19 | height: 10%; 20 | } 21 | 22 | .modal-footer { 23 | height: 10%; 24 | } 25 | 26 | .full-height { 27 | height: 100%; 28 | } 29 | 30 | .step-text { 31 | margin-top: 50px; 32 | } 33 | 34 | .instruction-text { 35 | margin-top: 5px; 36 | margin-bottom: 5px; 37 | } 38 | 39 | .suggested-rm-list { 40 | padding-left: 0; 41 | } 42 | 43 | .modal-dialog { 44 | overflow-y: initial !important; 45 | } 46 | 47 | .list-group-item { 48 | cursor: pointer; 49 | } 50 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | import { AppModule } from './app.module'; 4 | import { environment } from './environments/environment'; 5 | import { hmrBootstrap } from './hmr'; 6 | 7 | if (window.location.hostname !== 'localhost' || environment.production) { 8 | enableProdMode(); // run angular development mode outside testing environment 9 | } 10 | const bootstrap = () => platformBrowserDynamic().bootstrapModule(AppModule); 11 | 12 | if (environment.hmr) { 13 | if (module['hot']) { 14 | hmrBootstrap(module, bootstrap); 15 | } else { 16 | console.error('HMR is not enabled for webpack-dev-server!'); 17 | console.log('Are you using the --hmr flag for ng serve?'); 18 | } 19 | } else { 20 | bootstrap(); 21 | } 22 | -------------------------------------------------------------------------------- /src/components/route-wizard/route-wizard.component.less: -------------------------------------------------------------------------------- 1 | #auto-route-modal-map { 2 | position: absolute; 3 | width: 100%; 4 | height: 95%; 5 | } 6 | 7 | #container { 8 | height: 90vh; 9 | width: 100%; 10 | } 11 | 12 | #list { 13 | width: 900px; 14 | } 15 | 16 | .modal-body { 17 | height: 80%; 18 | width: 100%; 19 | } 20 | 21 | .modal-header { 22 | height: 10%; 23 | } 24 | 25 | .modal-footer { 26 | height: 10%; 27 | } 28 | 29 | .full-height { 30 | height: 100%; 31 | } 32 | 33 | .scrollable-panel { 34 | height: 200px; 35 | overflow-y: scroll; 36 | width: 100%; 37 | } 38 | 39 | .right-panel { 40 | margin: 20px; 41 | } 42 | 43 | .step-1-text { 44 | margin-top: 50px; 45 | } 46 | 47 | li { 48 | text-align: left; 49 | } 50 | 51 | #Stops { 52 | margin: 5px; 53 | } 54 | 55 | #Platforms { 56 | margin: 6px; 57 | margin-right: 0; 58 | } 59 | -------------------------------------------------------------------------------- /src/components/navigator/navigator.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { Map } from 'leaflet'; 3 | import { GeocodeService } from '../../services/geocode.service'; 4 | import { MapService } from '../../services/map.service'; 5 | 6 | @Component({ 7 | providers: [], 8 | selector: 'navigator', 9 | styleUrls: ['./navigator.component.less', '../../styles/main.less'], 10 | templateUrl: './navigator.component.html', 11 | }) 12 | export class NavigatorComponent implements OnInit { 13 | address: string; 14 | 15 | private map: Map; 16 | 17 | constructor( 18 | private geocodeSrv: GeocodeService, 19 | private mapSrv: MapService, 20 | ) { 21 | this.address = ''; 22 | } 23 | 24 | ngOnInit(): void { 25 | this.mapSrv.disableMouseEvent('goto'); 26 | this.mapSrv.disableMouseEvent('place-input'); 27 | this.map = this.mapSrv.map; 28 | } 29 | 30 | goto() { 31 | if (!this.address) { 32 | return; 33 | } 34 | this.geocodeSrv.geocode(this.address); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "angular.ng-template", 4 | "doggy8088.angular-extension-pack", 5 | "loiane.angular-extension-pack", 6 | "ng-42.ng-fortytwo-vscode-extension", 7 | "angulardoc.angulardoc-vscode", 8 | "alexiv.vscode-angular2-files", 9 | "cyrilletuzi.angular-schematics", 10 | "1tontech.angular-material", 11 | "danwahlin.angular2-snippets", 12 | "herrherrmann.angular-bootstrap", 13 | "infinity1207.angular2-switcher", 14 | "johnpapa.angular-essentials", 15 | "johnpapa.angular2", 16 | "marinhobrandao.angular2tests", 17 | "mikael.angular-beastcode", 18 | "nrwl.angular-console", 19 | "uvbrain.angular2", 20 | "ms-vscode.typescript-javascript-grammar", 21 | "rbbit.typescript-hero", 22 | "msjsdiag.debugger-for-chrome", 23 | "coenraads.bracket-pair-colorizer-2", 24 | "lmcarreiro.vscode-smart-column-indenter", 25 | "eamodio.gitlens", 26 | "donjayamanne.githistory", 27 | "mhutchie.git-graph", 28 | "devboosts.angular-productivity-pack" 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /src/database/dexiedb.ts: -------------------------------------------------------------------------------- 1 | import Dexie from 'dexie'; 2 | import { IMetadata } from '../core/metadata.interface'; 3 | import { IPtRelationNew } from '../core/ptRelationNew.interface'; 4 | import { IPtRouteMasterNew } from '../core/ptRouteMasterNew.interface'; 5 | import { IPtStop } from '../core/ptStop.interface'; 6 | import { IPtWay } from '../core/ptWay.interface'; 7 | 8 | export class Db extends Dexie { 9 | AreasGrid: Dexie.Table; 10 | PtStops: Dexie.Table; 11 | PtPlatforms: Dexie.Table; 12 | PtRoutes: Dexie.Table; 13 | PtRouteMasters: Dexie.Table; 14 | OSMWays: Dexie.Table; 15 | MetaData: Dexie.Table; 16 | 17 | constructor() { 18 | super('Db'); 19 | this.version(1).stores({ 20 | AreasGrid: '', 21 | PtStops: 'id', 22 | PtPlatforms: 'id', 23 | PtRoutes: 'id', 24 | PtRouteMasters: 'id', 25 | OSMWays: 'id', 26 | MetaData: 'id, isCompletelyDownloaded, type', 27 | }); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/components/lang/lang.component.html: -------------------------------------------------------------------------------- 1 | 20 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "ignorePatterns": ["projects/**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts"], 7 | "extends": [ 8 | "eslint:recommended", 9 | "plugin:@typescript-eslint/recommended", 10 | "plugin:@angular-eslint/recommended", 11 | "plugin:@angular-eslint/template/process-inline-templates", 12 | "plugin:prettier/recommended" // <--- here we inherit from the recommended setup from eslint-plugin-prettier for TS 13 | ], 14 | "rules": { 15 | "@typescript-eslint/ban-ts-comment": "warn", 16 | "@typescript-eslint/ban-types": "warn", 17 | "@typescript-eslint/no-explicit-any": "warn", 18 | "no-case-declarations": "warn", 19 | "no-prototype-builtins": "warn" 20 | } 21 | }, 22 | { 23 | "files": ["*.html"], 24 | "extends": [ 25 | "plugin:@angular-eslint/template/recommended", 26 | "plugin:prettier/recommended" // <--- here we inherit from the recommended setup from eslint-plugin-prettier for HTML 27 | ], 28 | "rules": {} 29 | } 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /src/components/tutorials/tutorials.component.html: -------------------------------------------------------------------------------- 1 |
2 |
Tutorials 3 | 4 |
5 |
6 | 7 |

8 | Beginner Topics 9 |

10 |
    11 | 12 | FIXME tutorials.component.html 13 | 14 |
15 |
16 | 17 |

18 | Expert Topics 19 |

20 |
    21 | 22 | FIXME tutorials.component.html 23 | 24 |
25 |
26 |
27 |
28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 dkocich 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /src/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage-istanbul-reporter'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | coverageIstanbulReporter: { 19 | dir: require('path').join(__dirname, './coverage/osm-pt-ngx-leaflet'), 20 | reports: ['html', 'lcovonly', 'text-summary'], 21 | fixWebpackSourcePaths: true 22 | }, 23 | reporters: ['progress', 'kjhtml'], 24 | port: 9876, 25 | colors: true, 26 | logLevel: config.LOG_INFO, 27 | autoWatch: true, 28 | browsers: ['Chrome'], 29 | singleRun: false, 30 | restartOnFileChange: true 31 | }); 32 | }; 33 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "My Task", 6 | "command": "echo hello", 7 | "type": "shell", 8 | "args": [], 9 | "problemMatcher": [ 10 | "$tsc" 11 | ], 12 | "presentation": { 13 | "reveal": "always" 14 | }, 15 | "group": "build" 16 | }, 17 | { 18 | "type": "npm", 19 | "script": "start", 20 | "isBackground": true, 21 | "presentation": { 22 | "focus": true, 23 | "panel": "dedicated" 24 | }, 25 | "group": { 26 | "kind": "build", 27 | "isDefault": true 28 | }, 29 | "problemMatcher": { 30 | "owner": "typescript", 31 | "source": "ts", 32 | "applyTo": "closedDocuments", 33 | "fileLocation": [ 34 | "relative", 35 | "${cwd}" 36 | ], 37 | "pattern": "$tsc", 38 | "background": { 39 | "activeOnStart": true, 40 | "beginsPattern": { 41 | "regexp": "(.*?)" 42 | }, 43 | "endsPattern": { 44 | "regexp": "Compiled |Failed to compile." 45 | } 46 | } 47 | } 48 | } 49 | ] 50 | } 51 | -------------------------------------------------------------------------------- /src/components/toolbar/toolbar.component.less: -------------------------------------------------------------------------------- 1 | @import '../../styles/main.less'; 2 | 3 | #toggle-download { 4 | margin: 20px 0 0 375px; 5 | .map-button; 6 | } 7 | 8 | #toggle-filter { 9 | margin: 60px 0 0 375px; 10 | display: none; 11 | .map-button; 12 | } 13 | 14 | #download-data { 15 | margin: 20px 0 0 420px; 16 | .map-button; 17 | } 18 | 19 | #upload-data { 20 | margin: 20px 0 0 465px; 21 | } 22 | 23 | #radio-highlight { 24 | bottom: 25px; 25 | right: 60px; 26 | position: absolute; 27 | z-index: 401; 28 | } 29 | 30 | #clear-highlight { 31 | bottom: 25px; 32 | right: 15px; 33 | .map-button; 34 | } 35 | 36 | #stats { 37 | position: absolute; 38 | bottom: 5px; 39 | left: 150px; 40 | z-index: 401; 41 | background-color: white; 42 | opacity: 0.8; 43 | padding: 10px; 44 | } 45 | 46 | #selection { 47 | position: absolute; 48 | bottom: 50px; 49 | left: 5px; 50 | z-index: 401; 51 | background-color: white; 52 | opacity: 0.8; 53 | padding: 10px; 54 | } 55 | 56 | #drop-down { 57 | margin: 0; 58 | padding: 0; 59 | } 60 | 61 | #drop-down-items { 62 | width: 100%; 63 | } 64 | 65 | .route-labels-btn { 66 | bottom: 25px; 67 | right: 220px; 68 | position: absolute; 69 | z-index: 401; 70 | } 71 | -------------------------------------------------------------------------------- /src/assets/traffic/traffic_signals.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "app": { 3 | "urls": [ 4 | "http://osm-pt.herokuapp.com" 5 | ], 6 | "launch": { 7 | "web_url": "http://osm-pt.herokuapp.com" 8 | } 9 | }, 10 | "applications": { 11 | "gecko": { 12 | "id": "addon@example.com", 13 | "strict_min_version": "42.0" 14 | } 15 | }, 16 | "background_color": "#347ab7", 17 | "display": "standalone", 18 | "default_locale": "en", 19 | "description": "An OSM public transport editor based on Leaflet and Angular JS framework.", 20 | "icons": [{ 21 | "src": "assets/favicon-32x32.png", 22 | "type": "image/png", 23 | "sizes": "32x32" 24 | }, { 25 | "src": "assets/favicon-96x96.png", 26 | "type": "image/png", 27 | "sizes": "96x96" 28 | }, { 29 | "src": "assets/android-chrome-192x192.png", 30 | "type": "image/png", 31 | "sizes": "192x192" 32 | }, { 33 | "src": "assets/favicon-194x194.png", 34 | "type": "image/png", 35 | "sizes": "194x194" 36 | }], 37 | "manifest_version": 2, 38 | "name": "OSM PT Editor", 39 | "orientation": "landscape", 40 | "permissions": ["webNavigation"], 41 | "short_name": "OSMPTEditor", 42 | "start_url": "./index.html?utm_source=homescreen", 43 | "theme_color": "#f3f3f3", 44 | "version": "0.0.1" 45 | } 46 | -------------------------------------------------------------------------------- /src/services/warn.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { TranslateService, TranslationChangeEvent } from '@ngx-translate/core'; 3 | import { ToastrService } from 'ngx-toastr'; 4 | 5 | @Injectable() 6 | export class WarnService { 7 | private successMessage = 'Data fetched successfully'; 8 | private errorMessage = 'Error in fetching data'; 9 | private genericSuccessMessage = 'Success!'; 10 | 11 | constructor( 12 | private toastrSrv: ToastrService, 13 | private translateSrv: TranslateService, 14 | ) { 15 | /** 16 | * Listens to language change event and translates error and success messages 17 | */ 18 | this.translateSrv.onLangChange.subscribe( 19 | (event: TranslationChangeEvent) => { 20 | this.successMessage = event.translations[this.successMessage]; 21 | this.errorMessage = event.translations[this.errorMessage]; 22 | this.genericSuccessMessage = 23 | event.translations[this.genericSuccessMessage]; 24 | }, 25 | ); 26 | } 27 | 28 | showError(): void { 29 | this.toastrSrv.error(this.errorMessage); 30 | } 31 | 32 | showSuccess(): void { 33 | this.toastrSrv.success(this.successMessage); 34 | } 35 | 36 | showGenericSuccess(): void { 37 | this.toastrSrv.success(this.genericSuccessMessage); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/assets/traffic/crossing.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/core/ptRelation.interface.ts: -------------------------------------------------------------------------------- 1 | import { IOsmElement } from './osmElement.interface'; 2 | import { TStrRelation } from './other'; 3 | import { IPtMember } from './ptMember'; 4 | import { IPtTags } from './ptTags.class'; 5 | 6 | /** 7 | * { 8 | * "type": "relation", 9 | * "id": 7157492, 10 | * "timestamp": "2017-05-15T22:23:20Z", 11 | * "version": 5, 12 | * "changeset": 48714598, 13 | * "user": "dkocich", 14 | * "uid": 1784758, 15 | * "members": [ 16 | * { 17 | * "type": "node", 18 | * "ref": 2184049214, 19 | * "role": "stop" 20 | * }, 21 | * { 22 | * "type": "node", 23 | * "ref": 2162278060, 24 | * "role": "platform" 25 | * }, 26 | * { 27 | * "type": "way", 28 | * "ref": 387730713, 29 | * "role": "" 30 | * } 31 | * ], 32 | * "tags": { 33 | * "complete": "no", 34 | * "from": "Řepiště, U kříže", 35 | * "name": "Bus 11: Řepiště, U kříže -> Místek,Riviéra", 36 | * "operator": "ČSAD Frýdek-Místek", 37 | * "public_transport:version": "2", 38 | * "route": "bus", 39 | * "to": "Místek,Riviéra", 40 | * "type": "route" 41 | * } 42 | * } 43 | */ 44 | export interface IPtRelation extends IOsmElement { 45 | type: TStrRelation; 46 | members: IPtMember[]; 47 | tags: IPtTags; 48 | } 49 | -------------------------------------------------------------------------------- /src/styles/main.less: -------------------------------------------------------------------------------- 1 | * { 2 | -khtml-user-select: none; 3 | -moz-user-select: none; 4 | -o-user-select: none; 5 | -webkit-user-drag: none; 6 | -webkit-user-select: none; 7 | user-select: none; 8 | } 9 | 10 | ::-webkit-scrollbar-track { 11 | -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3); 12 | background-color: #F5F5F5; 13 | } 14 | 15 | ::-webkit-scrollbar { 16 | width: 7px; 17 | height: 7px; 18 | background-color: grey; 19 | } 20 | 21 | ::-webkit-scrollbar-thumb { 22 | background-color: gray; 23 | background-image: -webkit-gradient(linear, 0 0, 0 100%, 24 | color-stop(.5, rgba(255, 255, 255, .2)), 25 | color-stop(.5, transparent), to(transparent)); 26 | } 27 | 28 | .on-map { 29 | position: absolute; 30 | z-index: 401; 31 | } 32 | 33 | .map-button { 34 | width: 35px; 35 | height: 35px; 36 | text-align: center; 37 | } 38 | 39 | .leaflet-clickable { 40 | cursor: crosshair !important; 41 | } 42 | 43 | .leaflet-container { 44 | cursor: default; 45 | background-color: grey; 46 | } 47 | 48 | .content { 49 | white-space: nowrap; 50 | overflow-y: auto; 51 | min-height: 20vh; 52 | max-height: 350px; 53 | } 54 | 55 | .explore { 56 | cursor: pointer; 57 | } 58 | 59 | .wide-table { 60 | width: 100%; 61 | } 62 | 63 | .selected { 64 | font-weight: bold; 65 | background-color: cyan; 66 | } 67 | 68 | .panel-body { 69 | padding: 7px; 70 | } 71 | 72 | .platform-cursor { 73 | cursor: url('/assets/transport/platform.png'), auto; 74 | } 75 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (https://nodejs.org/api/addons.html) 23 | build/Release 24 | 25 | # Dependency directory 26 | node_modules 27 | 28 | public/**/* 29 | 30 | .tscache 31 | 32 | # editors settings 33 | .idea 34 | 35 | 36 | .now 37 | 38 | # See http://help.github.com/ignore-files/ for more about ignoring files. 39 | 40 | # compiled output 41 | /dist 42 | /tmp 43 | /out-tsc 44 | # Only exists if Bazel was run 45 | /bazel-out 46 | 47 | # dependencies 48 | /node_modules 49 | 50 | # profiling files 51 | chrome-profiler-events*.json 52 | speed-measure-plugin*.json 53 | 54 | # IDEs and editors 55 | /.idea 56 | .project 57 | .classpath 58 | .c9/ 59 | *.launch 60 | .settings/ 61 | *.sublime-workspace 62 | 63 | # IDE - VSCode 64 | .vscode/* 65 | !.vscode/settings.json 66 | !.vscode/tasks.json 67 | !.vscode/launch.json 68 | !.vscode/extensions.json 69 | .history/* 70 | 71 | # misc 72 | /.sass-cache 73 | /connect.lock 74 | /coverage 75 | /libpeerconnection.log 76 | npm-debug.log 77 | yarn-error.log 78 | testem.log 79 | /typings 80 | 81 | # System Files 82 | .DS_Store 83 | Thumbs.db 84 | /.angular/cache/* 85 | -------------------------------------------------------------------------------- /src/services/conf.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | 3 | import p from '../../package.json'; 4 | 5 | @Injectable() 6 | export class ConfService { 7 | static readonly overpassUrl = 'https://overpass-api.de/api/interpreter'; 8 | static readonly baseOsmUrl = 'https://www.openstreetmap.org'; 9 | static readonly apiUrl = 'https://api.openstreetmap.org/api/0.6'; 10 | static readonly apiConsumerSecret = 11 | 'vFXps19FPNhWzzGmWbrhNpMv3RYiI1RFL4oK8NPz'; 12 | static readonly apiConsumerKey = 'rPEtcWkEykSKlLccsDS0FaZ9DpGAVPoJfQXWubXl'; 13 | static readonly apiTestUrl = 'https://master.apis.dev.openstreetmap.org'; 14 | static readonly apiTestConsumerKey = 15 | 'myPQ4WewlhUBa5zRs00zwHjWV4nEIsrg6SAF9zat'; 16 | static readonly apiTestConsumerSecret = 17 | '7hAymlaBzyUqGU0ecbdUqXgYt4w59ru3t3JIM9xp'; 18 | static readonly appName = `OSMPTeditor v${p['version']}`; 19 | static readonly hereAppId = 'wCnyB1NFdlBOGYDZ8wvz'; 20 | static readonly hereAppCode = 'n8AtIn2aXHff9D-Dp_S6rA'; 21 | static readonly mapboxToken = 22 | 'pk.eyJ1IjoiZGtvY2ljaCIsImEiOiJjajR5ZGc5b3kxbnozMnFwZjI4eWo4N2piIn0.EizwwnWouhfRkJznDpEWCw'; 23 | static readonly minDownloadZoom = 15; 24 | static readonly minDownloadZoomForErrors = 13; 25 | static readonly minDownloadZoomForRouteWizard = 8; 26 | static readonly minDownloadZoomForRouteMasterWizard = 8; 27 | static readonly minDownloadDistance = 1500; 28 | 29 | static readonly geocodingApiUrl = 'http://api.ipstack.com/'; 30 | static readonly geocodingApiKey = 31 | '?access_key=2be8b578b9cef6bddf3a706d10c620dd'; 32 | 33 | cfgFilterLines = true; 34 | } 35 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | First of all, thanks for thinking about a future contribution to this project. 4 | 5 | When contributing to this repository, please first discuss the change you 6 | wish to make via issue, email, or any other method with the owners of this 7 | repository before making a change. 8 | 9 | ## Github 10 | Code, tests, documentation, wiki and issue tracking are all managed on GitHub 11 | so make sure that you have a [GitHub account](). 12 | The [Github wiki](https://github.com/dkocich/osm-pt-ngx-leaflet/wiki) is 13 | available to document the general overview of the application. 14 | For more details about issues/PRs consider installing [Zenhub](https://www.zenhub.com/). 15 | 16 | ## Bugs 17 | 18 | The project's [issue tracker](https://github.com/dkocich/osm-pt-ngx-leaflet/issues) is 19 | available to report all bugs. Please make sure that you are using the latest 20 | version and that you provide a description of how to reproduce the bug. 21 | 22 | ## GitHub Commit Guidelines 23 | 24 | - enhancements and bug fixes should be identified with a GitHub issue 25 | - commits should be granular enough for other developers to understand the 26 | nature/implications of the changes 27 | - commits shall include a description of changes 28 | - non-trivial commits shall be associated with an issue ID "#1234" in the 29 | Git commit log message 30 | - for trivial commits that do not need [Travis CI](https://travis-ci.org/) 31 | to run, include "[ci skip]" as part of the commit message 32 | - all enhancements or bug fixes must successfully pass all tests before 33 | they are committed 34 | -------------------------------------------------------------------------------- /src/components/tutorials/tutorials.component.ts: -------------------------------------------------------------------------------- 1 | import { NgRedux, select } from '@angular-redux/store'; 2 | import { Component, OnInit } from '@angular/core'; 3 | import * as introJs from 'intro.js'; 4 | import { Observable } from 'rxjs'; 5 | import { TutorialService } from '../../services/tutorial.service'; 6 | import * as data from '../../services/tutorials.json'; 7 | import { AppActions } from '../../store/app/actions'; 8 | import { IAppState } from '../../store/model'; 9 | 10 | @Component({ 11 | providers: [], 12 | selector: 'tutorials', 13 | styleUrls: [ 14 | './tutorials.component.less', 15 | './tutorials.component.less', 16 | '../../styles/main.less', 17 | ], 18 | templateUrl: './tutorials.component.html', 19 | }) 20 | export class TutorialsComponent implements OnInit { 21 | tutorialsData = null; 22 | @select(['app', 'advancedExpMode']) 23 | readonly advancedExpMode$: Observable; 24 | constructor( 25 | private ngRedux: NgRedux, 26 | private tutorialSrv: TutorialService, 27 | public appActions: AppActions, 28 | ) {} 29 | 30 | ngOnInit(): void { 31 | this.tutorialSrv.intro = introJs(); 32 | this.tutorialsData = data; 33 | } 34 | 35 | startTutorial(tutorialTitle: string, expertMode: string): void { 36 | this.appActions.actToggleTutorialMode(false); 37 | this.appActions.actSetAdvancedExpMode(false); 38 | if (this.ngRedux.getState()['app']['editing']) { 39 | this.appActions.actToggleEditing(); 40 | } 41 | this.tutorialSrv.startTutorial(tutorialTitle, expertMode); 42 | } 43 | 44 | quitTutorialMode(): void { 45 | this.appActions.actToggleTutorialMode(null); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/core/responses.interface.ts: -------------------------------------------------------------------------------- 1 | export interface IResponseIp { 2 | ip: string; 3 | } 4 | 5 | /** 6 | * TODO 7 | * deprecation message in the response: 8 | * 9 | * "This API endpoint is deprecated and will stop working on July 1st, 2018. 10 | * For more information please visit: https://github.com/apilayer/freegeoip#readme" 11 | */ 12 | export interface IResponseFreeGeoIp { 13 | city: string; 14 | country_code: string; 15 | country_name: string; 16 | ip: string; 17 | latitude: number; 18 | longitude: number; 19 | metro_code: number; 20 | region_code: string; 21 | region_name: string; 22 | time_zone: string; 23 | zip_code: string; 24 | __deprecation_message__: string; 25 | } 26 | 27 | export interface IResponseGeocodeGMaps { 28 | error_message?: string; 29 | results: [IGeocodeResultGMaps]; 30 | status: string; // "OVER_QUERY_LIMIT" / "OK" / ... 31 | } 32 | 33 | export interface IGeocodeResultGMaps { 34 | address_components: IAddressComponentGMaps[]; 35 | formatted_address: string; 36 | geometry: IGeometryGMaps; 37 | place_id: string; 38 | types: string[]; // "locality", "political",... 39 | } 40 | 41 | export interface IAddressComponentGMaps { 42 | long_name: string; 43 | short_name: string; 44 | types: string[]; // "locality", "political",... 45 | } 46 | 47 | export interface IGeometryGMaps { 48 | bounds: IViewportGMaps; 49 | location: ILatLngGMaps; 50 | location_type: string; // "APPROXIMATE",... 51 | viewport: IViewportGMaps; 52 | } 53 | 54 | export interface IViewportGMaps { 55 | southwest: ILatLngGMaps; 56 | northeast: ILatLngGMaps; 57 | } 58 | 59 | export interface ILatLngGMaps { 60 | lat: number; 61 | lng: number; 62 | } 63 | -------------------------------------------------------------------------------- /src/core/other.ts: -------------------------------------------------------------------------------- 1 | export type EnumRouteMaster = 2 | | 'train' 3 | | 'subway' 4 | | 'monorail' 5 | | 'tram' 6 | | 'bus' 7 | | 'trolleybus' 8 | | 'aerialway' 9 | | 'ferry' 10 | | ''; 11 | export type EnumWheelchair = 'yes' | 'no' | 'limited' | 'designated' | ''; 12 | 13 | export type expectedKeys = 14 | | 'ascent' 15 | | 'bench' 16 | | 'building' 17 | | 'bus' 18 | | 'colour' 19 | | 'covered' 20 | | 'descent' 21 | | 'description' 22 | | 'distance' 23 | | 'highway' 24 | | 'layer' 25 | | 'level' 26 | | 'name' 27 | | 'network' 28 | | 'operator' 29 | | 'public_transport' 30 | | 'public_transport:version' 31 | | 'railway' 32 | | 'ref' 33 | | 'roundtrip' 34 | | 'route' 35 | | 'route_ref' 36 | | 'shelter' 37 | | 'surface' 38 | | 'symbol' 39 | | 'tactile_paving' 40 | | 'type' 41 | | 'uic_name' 42 | | 'uic_ref'; 43 | 44 | export type expectedValues = 45 | | 'aerialway' 46 | | 'backward' 47 | | 'bus' 48 | | 'bus_stop' 49 | | 'coach' 50 | | 'ferry' 51 | | 'forward' 52 | | 'gate' 53 | | 'limited' 54 | | 'monorail' 55 | | 'no' 56 | | 'platform' 57 | | 'public_transport' 58 | | 'route' 59 | | 'route_master' 60 | | 'share_taxi' 61 | | 'station' 62 | | 'stop' 63 | | 'stop_area' 64 | | 'stop_position' 65 | | 'subway' 66 | | 'taxi' 67 | | 'train' 68 | | 'tram' 69 | | 'trolleybus' 70 | | 'yes'; 71 | 72 | export type EnumElementTypes = TStrNode | TStrWay | TStrRelation; 73 | 74 | export type TStrNode = 'node'; 75 | export type TStrWay = 'way'; 76 | export type TStrRelation = 'relation'; 77 | export type TStrRouteMaster = 'route_master'; 78 | export type TStrRoute = 'route'; 79 | export type TStrPtv2 = '2'; 80 | export type TStrStopArea = 'stop_area'; 81 | -------------------------------------------------------------------------------- /src/store/module.ts: -------------------------------------------------------------------------------- 1 | import { provideReduxForms } from '@angular-redux/form'; 2 | import { NgReduxRouter, NgReduxRouterModule } from '@angular-redux/router'; 3 | // Angular-redux ecosystem stuff. 4 | // @angular-redux/form and @angular-redux/router are optional 5 | // extensions that sync form and route location state between 6 | // our store and Angular. 7 | import { 8 | DevToolsExtension, 9 | NgRedux, 10 | NgReduxModule, 11 | } from '@angular-redux/store'; 12 | import { NgModule } from '@angular/core'; 13 | // Redux ecosystem stuff. 14 | import { createLogger } from 'redux-logger'; 15 | import { RootEpics } from './epics'; 16 | // The top-level reducers and epics that make up our app's logic. 17 | import { IRootAppState } from './model'; 18 | import { rootReducer } from './reducers'; 19 | 20 | @NgModule({ 21 | imports: [NgReduxModule, NgReduxRouterModule.forRoot()], 22 | providers: [RootEpics], 23 | }) 24 | export class StoreModule { 25 | constructor( 26 | store: NgRedux, 27 | devTools: DevToolsExtension, 28 | ngReduxRouter: NgReduxRouter, 29 | rootEpics: RootEpics, 30 | ) { 31 | // Tell Redux about our reducers and epics. If the Redux DevTools 32 | // chrome extension is available in the browser, tell Redux about 33 | // it too. 34 | 35 | const storeEnhancers = devTools.isEnabled() ? [devTools.enhancer()] : []; 36 | 37 | store.configureStore( 38 | rootReducer, 39 | {}, 40 | [createLogger()], // , ...rootEpics.createEpics() 41 | storeEnhancers, 42 | ); 43 | 44 | // Enable syncing of Angular router state with our Redux store. 45 | if (ngReduxRouter) { 46 | ngReduxRouter.initialize(); 47 | } 48 | 49 | // Enable syncing of Angular form state with our Redux store. 50 | provideReduxForms(store); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/store/app/reducer.ts: -------------------------------------------------------------------------------- 1 | import { AnyAction } from 'redux'; 2 | import { IAppState } from '../model'; 3 | import { AppActions } from './actions'; 4 | 5 | export const INITIAL_STATE: IAppState = { 6 | editing: false, 7 | selectObject: null, 8 | advancedExpMode: false, 9 | goodConnectMode: false, 10 | errorCorrectionMode: null, 11 | beginnerView: 'stop', 12 | switchMode: false, 13 | wizardMode: null, 14 | tutorialMode: null, 15 | }; 16 | 17 | export function appReducer(state: IAppState = INITIAL_STATE, actionAction) { 18 | switch (action.type) { 19 | case AppActions.TOGGLE_EDITING: 20 | return { 21 | ...state, 22 | editing: !state.editing, 23 | }; 24 | case AppActions.SELECT_ELEMENT: 25 | return { 26 | ...state, 27 | selectObject: action.payload, 28 | }; 29 | case AppActions.SET_ADVANCED_EXP_MODE: 30 | return { 31 | ...state, 32 | advancedExpMode: action.payload, 33 | }; 34 | case AppActions.SET_GOOD_CONNECT_MODE: 35 | return { 36 | ...state, 37 | goodConnectMode: action.payload, 38 | }; 39 | case AppActions.SET_ERROR_CORRECTION_MODE: 40 | return { 41 | ...state, 42 | errorCorrectionMode: action.payload, 43 | }; 44 | case AppActions.SET_BEGINNER_VIEW: 45 | return { 46 | ...state, 47 | beginnerView: action.payload, 48 | }; 49 | case AppActions.TOGGLE_SWITCH_MODE: 50 | return { 51 | ...state, 52 | switchMode: action.payload, 53 | }; 54 | case AppActions.SET_WIZARD_MODE: 55 | return { 56 | ...state, 57 | wizardMode: action.payload, 58 | }; 59 | case AppActions.TOGGLE_TUTORIAL_MODE: 60 | return { 61 | ...state, 62 | tutorialMode: action.payload, 63 | }; 64 | default: 65 | // We don't care about any other actions right now. 66 | return state; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/components/settings/settings.component.html: -------------------------------------------------------------------------------- 1 | 45 | -------------------------------------------------------------------------------- /src/core/utils.class.ts: -------------------------------------------------------------------------------- 1 | import { HttpHeaders } from '@angular/common/http'; 2 | import * as L from 'leaflet'; 3 | 4 | export class Utils { 5 | constructor() { 6 | // 7 | } 8 | 9 | static readonly DEFAULT_ICON = L.icon({ 10 | iconUrl: '', 11 | shadowAnchor: [22, 94], 12 | shadowSize: [24, 24], 13 | shadowUrl: '', 14 | }); 15 | static readonly HIGHLIGHT_FILL = { 16 | color: '#ffff00', 17 | opacity: 0.6, 18 | weight: 6, 19 | }; 20 | static readonly HIGHLIGHT_STROKE = { 21 | color: '#FF0000', 22 | opacity: 0.6, 23 | weight: 12, 24 | }; 25 | static readonly FROM_TO_LABEL = { 26 | color: '#00FFFF', 27 | opacity: 0.6, 28 | }; 29 | static readonly REL_BUS_STYLE = { 30 | color: '#0000FF', 31 | opacity: 0.3, 32 | weight: 6, 33 | }; 34 | static readonly REL_TRAIN_STYLE = { 35 | color: '#000000', 36 | opacity: 0.3, 37 | weight: 6, 38 | }; 39 | static readonly REL_TRAM_STYLE = { 40 | color: '#FF0000', 41 | opacity: 0.3, 42 | weight: 6, 43 | }; 44 | static readonly OTHER_STYLE = { 45 | color: '#00FF00', 46 | opacity: 0.3, 47 | weight: 6, 48 | }; 49 | static readonly CONTINUOUS_QUERY: string = ` 50 | [out:json][timeout:25][bbox:{{bbox}}]; 51 | ( 52 | node["route"="train"]; 53 | node["route"="subway"]; 54 | node["route"="monorail"]; 55 | node["route"="tram"]; 56 | node["route"="bus"]; 57 | node["route"="trolleybus"]; 58 | node["route"="aerialway"]; 59 | node["route"="ferry"]; 60 | node["public_transport"]; 61 | ); 62 | (._;>;); 63 | out meta;`; 64 | static HTTP_HEADERS: HttpHeaders = new HttpHeaders({ 65 | 'Content-Type': 'application/X-www-form-urlencoded', 66 | }); 67 | 68 | static isProductionDeployment(): boolean { 69 | return ( 70 | ['osm-pt.herokuapp.com', 'osm-pt-dev.herokuapp.com'].indexOf( 71 | window.location.hostname, 72 | ) > -1 73 | ); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/assets/transport/bus.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/components/beginner/beginner.component.html: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 |
7 | 8 | {{ 'Tags' | translate }} 9 | {{ 'Route Tags' | translate }} 10 | 12 |
13 | 15 | 16 |
17 | 18 |
19 |  {{ 'Parent Routes' | translate }} 20 | 22 |
23 | 24 | 25 |
26 | 27 |
28 | {{ 'Suggestions for quick edit' | translate }} 29 | 31 |
32 | 33 |
34 |
35 | -------------------------------------------------------------------------------- /src/components/expert/expert.component.ts: -------------------------------------------------------------------------------- 1 | import { NgRedux, select } from '@angular-redux/store'; 2 | import { Component } from '@angular/core'; 3 | import { Observable } from 'rxjs'; 4 | import { 5 | IRouteBrowserOptions, 6 | ISuggestionsBrowserOptions, 7 | ITagBrowserOptions, 8 | } from '../../core/editingOptions.interface'; 9 | import { StorageService } from '../../services/storage.service'; 10 | import { TutorialService } from '../../services/tutorial.service'; 11 | import { IAppState } from '../../store/model'; 12 | 13 | @Component({ 14 | providers: [], 15 | selector: 'expert', 16 | styleUrls: ['./expert.component.less', '../../styles/main.less'], 17 | templateUrl: './expert.component.html', 18 | }) 19 | export class ExpertComponent { 20 | @select(['app', 'editing']) readonly editing$: Observable; 21 | constructor( 22 | private ngRedux: NgRedux, 23 | private tutorialSrv: TutorialService, 24 | private storageSrv: StorageService, 25 | ) {} 26 | isRouteBrowserOpen = false; 27 | routeBrowserOptions: IRouteBrowserOptions = { 28 | changeMembers: true, 29 | createRoute: true, 30 | membersEditing: true, 31 | toggleFilteredView: true, 32 | }; 33 | 34 | tagBrowserOptions: ITagBrowserOptions = { 35 | limitedKeys: false, 36 | makeKeysReadOnly: false, 37 | }; 38 | 39 | suggestionsBrowserOptions: ISuggestionsBrowserOptions = { 40 | nameSuggestions: { 41 | found: false, 42 | startCorrection: false, 43 | }, 44 | refSuggestions: { 45 | found: false, 46 | startCorrection: false, 47 | }, 48 | waySuggestions: { 49 | found: false, 50 | startCorrection: false, 51 | }, 52 | PTvSuggestions: { 53 | found: false, 54 | startCorrection: false, 55 | }, 56 | ptPairSuggestions: { 57 | found: false, 58 | startCorrection: false, 59 | }, 60 | }; 61 | 62 | openBrowser(name: string): void { 63 | if ( 64 | name === 'route-browser' && 65 | this.ngRedux.getState()['app']['tutorialMode'] === false && 66 | this.isRouteBrowserOpen 67 | ) { 68 | this.storageSrv.tutorialStepCompleted.emit('open route browser expert'); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/assets/transport/railway/subway.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/core/ptTags.class.ts: -------------------------------------------------------------------------------- 1 | import { expectedKeys, expectedValues } from './other'; 2 | 3 | export interface IPtTags { 4 | aerialway?: string; 5 | ascent?: string; 6 | bench?: string; 7 | building?: string; 8 | bus?: string; 9 | colour?: string; 10 | covered?: string; 11 | descent?: string; 12 | description?: string; 13 | distance?: string; 14 | highway?: string; 15 | layer?: string; 16 | level?: string; 17 | name?: string; 18 | network?: string; 19 | operator?: string; 20 | public_transport?: string; 21 | 'public_transport:version'?: string; 22 | railway?: string; 23 | ref?: string; 24 | roundtrip?: string; 25 | ferry?: string; 26 | trolleybus?: string; 27 | tram?: string; 28 | monorail?: string; 29 | subway?: string; 30 | train?: string; 31 | route?: string; 32 | route_ref?: string; 33 | shelter?: string; 34 | surface?: string; 35 | symbol?: string; 36 | tactile_paving?: string; 37 | type?: string; 38 | uic_name?: string; 39 | uic_ref?: number; 40 | } 41 | 42 | export class PtTags { 43 | static readonly expectedKeys: expectedKeys[] = [ 44 | 'ascent', 45 | 'bench', 46 | 'building', 47 | 'bus', 48 | 'colour', 49 | 'covered', 50 | 'descent', 51 | 'description', 52 | 'distance', 53 | 'highway', 54 | 'layer', 55 | 'level', 56 | 'name', 57 | 'network', 58 | 'operator', 59 | 'public_transport', 60 | 'public_transport:version', 61 | 'railway', 62 | 'ref', 63 | 'roundtrip', 64 | 'route', 65 | 'route_ref', 66 | 'shelter', 67 | 'surface', 68 | 'symbol', 69 | 'tactile_paving', 70 | 'type', 71 | 'uic_name', 72 | 'uic_ref', 73 | ]; 74 | 75 | static readonly expectedValues: expectedValues[] = [ 76 | 'aerialway', 77 | 'backward', 78 | 'bus', 79 | 'bus_stop', 80 | 'coach', 81 | 'ferry', 82 | 'forward', 83 | 'gate', 84 | 'limited', 85 | 'monorail', 86 | 'no', 87 | 'platform', 88 | 'public_transport', 89 | 'route', 90 | 'route_master', 91 | 'share_taxi', 92 | 'station', 93 | 'stop', 94 | 'stop_area', 95 | 'stop_position', 96 | 'subway', 97 | 'taxi', 98 | 'train', 99 | 'tram', 100 | 'trolleybus', 101 | 'yes', 102 | ]; 103 | } 104 | -------------------------------------------------------------------------------- /src/components/expert/expert.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |  {{ 'Relations' | translate }} 5 | 7 |
8 | 9 |
10 | 11 |
12 |  {{ 'Tags' | translate }} 13 | 15 |
16 | 17 |
18 | 19 |
20 |  {{ 'Routes' | translate }} 21 | 23 |
24 | 25 |
26 | 27 |
28 |  {{ 'Stops/platforms' | translate }} 29 | 31 |
32 | 33 |
34 | 35 |
36 | {{ 'Suggestions for quick edit' | translate }} 37 | 39 |
40 | 41 |
42 |
43 | -------------------------------------------------------------------------------- /src/store/app/actions.ts: -------------------------------------------------------------------------------- 1 | import { NgRedux } from '@angular-redux/store'; 2 | import { Injectable } from '@angular/core'; 3 | import { Action } from 'redux'; 4 | import { ISuggestionsBrowserOptions } from '../../core/editingOptions.interface'; 5 | import { IAppState } from '../model'; 6 | 7 | @Injectable() 8 | export class AppActions { 9 | constructor( 10 | private ngRedux: NgRedux, 11 | ) { 12 | // 13 | } 14 | 15 | static readonly TOGGLE_EDITING = 'TOGGLE_EDITING'; 16 | static readonly SELECT_ELEMENT = 'SELECT_ELEMENT'; 17 | static readonly SET_ADVANCED_EXP_MODE = 'SET_ADVANCED_EXP_MODE'; 18 | static readonly SET_GOOD_CONNECT_MODE = 'SET_GOOD_CONNECT_MODE'; 19 | static readonly SET_ERROR_CORRECTION_MODE = 'SET_ERROR_CORRECTION_MODE'; 20 | static readonly SET_BEGINNER_VIEW = 'SET_BEGINNER_VIEW'; 21 | static readonly TOGGLE_SWITCH_MODE = 'TOGGLE_SWITCH_MODE'; 22 | static readonly SET_WIZARD_MODE = 'SET_WIZARD_MODE'; 23 | static readonly TOGGLE_TUTORIAL_MODE = 'TOGGLE_TUTORIAL_MODE'; 24 | 25 | // basic sync action 26 | actToggleEditing = (): Action => { 27 | return this.ngRedux.dispatch({ 28 | type: AppActions.TOGGLE_EDITING, 29 | }); 30 | } 31 | 32 | // basic sync action 33 | actSelectElement = (args): Action => { 34 | const { element } = args; 35 | return this.ngRedux.dispatch({ 36 | type: AppActions.SELECT_ELEMENT, 37 | payload: element, 38 | }); 39 | } 40 | 41 | // basic sync action 42 | actSetAdvancedExpMode = (payload: boolean): Action => { 43 | return this.ngRedux.dispatch({ type: AppActions.SET_ADVANCED_EXP_MODE, payload }); 44 | } 45 | 46 | // basic sync action 47 | actSetGoodConnectMode = (payload: boolean): Action => { 48 | return this.ngRedux.dispatch({ type: AppActions.SET_GOOD_CONNECT_MODE, payload }); 49 | } 50 | 51 | actSetErrorCorrectionMode = (payload: ISuggestionsBrowserOptions): Action => { 52 | return this.ngRedux.dispatch({ type: AppActions.SET_ERROR_CORRECTION_MODE, payload }); 53 | } 54 | 55 | actSetBeginnerView = (payload: string): Action => { 56 | return this.ngRedux.dispatch({ type: AppActions.SET_BEGINNER_VIEW, payload }); 57 | } 58 | 59 | actToggleSwitchMode = (payload: boolean): Action => { 60 | return this.ngRedux.dispatch({ type: AppActions.TOGGLE_SWITCH_MODE, payload }); 61 | } 62 | 63 | actSetWizardMode = (payload: string): Action => { 64 | return this.ngRedux.dispatch({ type: AppActions.SET_WIZARD_MODE, payload }); 65 | } 66 | 67 | actToggleTutorialMode = (payload: boolean): Action => { 68 | return this.ngRedux.dispatch({ type: AppActions.TOGGLE_TUTORIAL_MODE, payload }); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/assets/transport/train.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Public Transport editor for OSM 6 | 7 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 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 |
Loading...
57 |
58 |
59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /src/components/auth/auth.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { AuthService } from '../../services/auth.service'; 3 | import { ConfService } from '../../services/conf.service'; 4 | import { StorageService } from '../../services/storage.service'; 5 | 6 | @Component({ 7 | providers: [], 8 | selector: 'auth', 9 | styleUrls: ['./auth.component.less', '../../styles/main.less'], 10 | templateUrl: './auth.component.html', 11 | }) 12 | export class AuthComponent { 13 | displayName: string; 14 | imgHref: string; 15 | 16 | constructor( 17 | private authSrv: AuthService, 18 | private storageSrv: StorageService, 19 | ) { 20 | this.displayName = this.getDisplayName(); 21 | this.imgHref = this.storageSrv.getImgHref(); 22 | } 23 | 24 | isAuthenticated = (): boolean => { 25 | return this.authSrv.oauth.authenticated(); 26 | }; 27 | 28 | private getDisplayName = (): string => { 29 | return this.storageSrv.getDisplayName(); 30 | }; 31 | 32 | authenticate = (): void => { 33 | this.authSrv.oauth.authenticate(this.gotAuthenticatedCallback); 34 | }; 35 | 36 | private gotAuthenticatedCallback = (err, response): void => { 37 | if (err) { 38 | throw new Error(JSON.stringify(err)); 39 | } 40 | this.showDetails(); 41 | }; 42 | 43 | logout = (): void => { 44 | this.authSrv.oauth.logout(); 45 | document.getElementById('display_name').innerHTML = ''; 46 | this.storageSrv.clearLocalStorage(); 47 | }; 48 | 49 | private done = (err, res) => { 50 | if (err) { 51 | document.getElementById('user').innerHTML = 52 | 'error! try clearing your browser cache'; 53 | document.getElementById('user').style.display = 'block'; 54 | throw new Error(JSON.stringify(err)); 55 | } 56 | const u = res.getElementsByTagName('user')[0]; 57 | const changesets = res.getElementsByTagName('changesets')[0]; 58 | const d = res.getElementsByTagName('description')[0]; 59 | const i = res.getElementsByTagName('img')[0]; 60 | const userDetails = { 61 | account_created: u.getAttribute('account_created'), 62 | count: changesets.getAttribute('count'), 63 | description: d.innerHTML, 64 | display_name: u.getAttribute('display_name'), 65 | id: u.getAttribute('id'), 66 | img_href: i.getAttribute('href'), 67 | }; 68 | this.storageSrv.setUserData(userDetails); 69 | // document.getElementById("display_name").innerHTML = userDetails["display_name"]; 70 | }; 71 | 72 | private showDetails = (): void => { 73 | this.authSrv.oauth.xhr( 74 | { 75 | method: 'GET', 76 | path: '/api/0.6/user/details', 77 | singlepage: true, 78 | url: ConfService.apiUrl, 79 | }, 80 | this.gotDetailsCallback.bind(this), 81 | ); 82 | }; 83 | 84 | private gotDetailsCallback = (err, xmlResponse): void => { 85 | this.done(err, xmlResponse); 86 | }; 87 | } 88 | -------------------------------------------------------------------------------- /src/assets/transport/platform.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/services/geocode.service.ts: -------------------------------------------------------------------------------- 1 | import { HttpClient } from '@angular/common/http'; 2 | import { Injectable } from '@angular/core'; 3 | import * as L from 'leaflet'; 4 | import { Location } from '../core/location.class'; 5 | import { 6 | IResponseFreeGeoIp, 7 | IResponseGeocodeGMaps, 8 | IResponseIp, 9 | } from '../core/responses.interface'; 10 | import { ConfService } from './conf.service'; 11 | import { MapService } from './map.service'; 12 | 13 | @Injectable() 14 | export class GeocodeService { 15 | httpClient: HttpClient; 16 | 17 | constructor( 18 | httpClient: HttpClient, 19 | private mapSrv: MapService, 20 | ) { 21 | this.httpClient = httpClient; 22 | } 23 | 24 | geocode(address: string) { 25 | return this.httpClient 26 | .get( 27 | `https://maps.googleapis.com/maps/api/geocode/json?address=${encodeURIComponent( 28 | address, 29 | )}`, 30 | ) 31 | .subscribe( 32 | (response: IResponseGeocodeGMaps) => { 33 | if (response.status !== 'OK') { 34 | throw new Error(`${response.status}; ${response.error_message}`); 35 | } 36 | const location = new Location(); 37 | location.address = response.results[0].formatted_address; 38 | location.latitude = response.results[0].geometry.location.lat; 39 | location.longitude = response.results[0].geometry.location.lng; 40 | const viewPort = response.results[0].geometry.viewport; 41 | location.viewBounds = L.latLngBounds( 42 | { 43 | lat: viewPort.southwest.lat, 44 | lng: viewPort.southwest.lng, 45 | }, 46 | { 47 | lat: viewPort.northeast.lat, 48 | lng: viewPort.northeast.lng, 49 | }, 50 | ); 51 | this.mapSrv.map.fitBounds(location.viewBounds, {}); 52 | }, 53 | (err) => { 54 | throw new Error(JSON.stringify(err)); 55 | }, 56 | ); 57 | } 58 | 59 | getCurrentLocation() { 60 | return this.httpClient 61 | .get('https://ipv4.myexternalip.com/json') 62 | .subscribe( 63 | (resp1: IResponseIp) => { 64 | this.httpClient 65 | .get( 66 | `${ConfService.geocodingApiUrl}${resp1.ip}${ConfService.geocodingApiKey}`, 67 | ) 68 | .subscribe( 69 | (resp2: IResponseFreeGeoIp) => { 70 | const location = new Location(); 71 | location.address = `${resp2.city}, ${resp2.region_code} ${resp2.zip_code}, ${resp2.country_code}`; 72 | location.latitude = resp2.latitude; 73 | location.longitude = resp2.longitude; 74 | this.mapSrv.map.panTo([location.latitude, location.longitude]); 75 | }, 76 | (err) => { 77 | throw new Error(JSON.stringify(err)); 78 | }, 79 | ); 80 | }, 81 | (err) => { 82 | throw new Error(JSON.stringify(err)); 83 | }, 84 | ); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/assets/transport/trolleybus.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # osm-pt-ngx-leaflet 2 | 3 | [![Gitpod Ready-to-Code](https://img.shields.io/badge/Gitpod-Ready--to--Code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/dkocich/osm-pt-ngx-leaflet) 4 | 5 | Stable release (master branch) 6 | [![Codacy Badge](https://api.codacy.com/project/badge/Grade/231206018f6e41d9bddee2b4bb4683dc)](https://app.codacy.com/app/dkocich/osm-pt-ngx-leaflet/dashboard) 7 | [![codebeat badge](https://codebeat.co/badges/525de5f2-8276-4e4a-ad5f-0b8c6c854f0d)](https://codebeat.co/projects/github-com-dkocich-osm-pt-ngx-leaflet-master) 8 | [![BCH compliance](https://bettercodehub.com/edge/badge/dkocich/osm-pt-ngx-leaflet?branch=master)](https://bettercodehub.com/) 9 | [![Known Vulnerabilities](https://snyk.io/test/github/snyk/goof/badge.svg)](https://snyk.io/test/github/snyk/goof) 10 | [![Build Status](https://api.travis-ci.org/dkocich/osm-pt-ngx-leaflet.svg?branch=master)](https://travis-ci.org/dkocich/osm-pt-ngx-leaflet) 11 | 12 | Latest release (develop branch) 13 | [![Codacy Badge](https://api.codacy.com/project/badge/Grade/231206018f6e41d9bddee2b4bb4683dc)](https://app.codacy.com/app/dkocich/osm-pt-ngx-leaflet/dashboard?bid=6156493) 14 | [![codebeat badge](https://codebeat.co/badges/525de5f2-8276-4e4a-ad5f-0b8c6c854f0d)](https://codebeat.co/projects/github-com-dkocich-osm-pt-ngx-leaflet-develop) 15 | [![BCH compliance](https://bettercodehub.com/edge/badge/dkocich/osm-pt-ngx-leaflet?branch=develop)](https://bettercodehub.com/) 16 | [![Known Vulnerabilities](https://snyk.io/test/github/snyk/goof/badge.svg)](https://snyk.io/test/github/snyk/goof) 17 | [![Build Status](https://api.travis-ci.org/dkocich/osm-pt-ngx-leaflet.svg?branch=develop)](https://travis-ci.org/dkocich/osm-pt-ngx-leaflet) 18 | 19 | 20 | 21 | 22 | An online web editor used to edit [public transport routes on OpenStreetMap](https://wiki.openstreetmap.org/wiki/Public_transport) with desktop and mobile devices. 23 | 24 | If you want to submit issue or PR, please study the [Contributing.md](https://github.com/dkocich/osm-pt-ngx-leaflet/blob/master/.github/CONTRIBUTING.md). 25 | 26 | Visit the [wiki page](https://github.com/dkocich/osm-pt-ngx-leaflet/wiki) for more information. 27 | 28 | ## How to start? 29 | 30 | ## Developing 👷 31 | 32 | #### Setup project locally 33 | 34 | This project requires [npm](https://npmpkg.com/) to run locally and it is built mainly with 35 | [Angular](https://angular.io/) and [Leaflet](http://leafletjs.com/). 36 | 37 | 1. Run `npm install` to install all dependencies. 38 | 2. Run `npm start` to enable local development server. 39 | 3. Open the browser at `http://localhost:4200/` to see the application. 40 | 41 | ## Communication 42 | 43 | Contact me via OSM messages [here](https://www.openstreetmap.org/user/dkocich). 44 | 45 | ## Acknowledgment 46 | 47 | The development was supported by Google during the [Google Summer of Code 2017 and 2018](https://summerofcode.withgoogle.com/) for [OpenStreetMap](https://www.openstreetmap.org/) organization. Please visit [wiki-Acknowledgment](https://github.com/dkocich/osm-pt-ngx-leaflet/wiki/Acknowledgment) for details. 48 | 49 | ## Support 50 | Please visit [wiki-Support](https://github.com/dkocich/osm-pt-ngx-leaflet/wiki/Support) for details. 51 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at dkocich [at] gmail [dot] com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** 22 | * IE9, IE10 and IE11 requires all of the following polyfills. 23 | */ 24 | // import 'core-js/es6/symbol'; 25 | // import 'core-js/es6/object'; 26 | // import 'core-js/es6/function'; 27 | // import 'core-js/es6/parse-int'; 28 | // import 'core-js/es6/parse-float'; 29 | // import 'core-js/es6/number'; 30 | // import 'core-js/es6/math'; 31 | // import 'core-js/es6/string'; 32 | // import 'core-js/es6/date'; 33 | // import 'core-js/es6/array'; 34 | // import 'core-js/es6/regexp'; 35 | // import 'core-js/es6/map'; 36 | // import 'core-js/es6/weak-map'; 37 | // import 'core-js/es6/set'; 38 | 39 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */ 40 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 41 | 42 | /** IE10 and IE11 requires the following for the Reflect API. */ 43 | // import 'core-js/es6/reflect'; 44 | 45 | /** 46 | * Evergreen browsers require these. 47 | */ 48 | // Used for reflect-metadata in JIT. If you use AOT (and only Angular decorators), you can remove. 49 | import 'core-js/es7/reflect'; 50 | /*************************************************************************************************** 51 | * Zone JS is required by default for Angular itself. 52 | */ 53 | import 'zone.js/dist/zone'; // Included with Angular CLI. 54 | 55 | /** 56 | * Web Animations `@angular/platform-browser/animations` 57 | * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. 58 | * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). 59 | */ 60 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 61 | 62 | /** 63 | * By default, zone.js will patch all possible macroTask and DomEvents 64 | * user can disable parts of macroTask/DomEvents patch by setting following flags 65 | */ 66 | 67 | // (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 68 | // (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 69 | // (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 70 | 71 | /** 72 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 73 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 74 | */ 75 | // (window as any).__Zone_enable_cross_context_check = true; 76 | 77 | /*************************************************************************************************** 78 | * APPLICATION IMPORTS 79 | */ 80 | 81 | // FIXME - temporal fix for ngcli@6 https://github.com/aws/aws-amplify/pull/866 (1 of 2 parts - search the other one) 82 | (window as any).global = window; 83 | -------------------------------------------------------------------------------- /src/components/app/app.component.html: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | 40 |
41 |
42 | 43 | 44 |
45 |
46 | 57 | 58 | 59 | 60 | 98 | -------------------------------------------------------------------------------- /src/assets/transport/station.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/components/sidebar/relation-browser.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

{{ 'Selected relation has no master relation.' | translate }}

4 |
5 | 10 | 15 |
16 |
17 |
18 | This master relation contains {{listOfVariants.length}} route variants. 19 |

{{ 'Variants of route_master' | translate }}

20 |
21 | {{ 'Incomplete relation (click to download it by reference ID)...' | translate }} 22 | 23 |
24 | 25 | 27 | 28 | 29 | 32 | 35 | 36 | {{rel.tags.route || "#TYPE" }} Route {{rel.tags.ref || "#REF"}}: {{rel.tags.from || "#FROM"}} => {{rel.tags.to || "#TO"}}, {{rel.tags.name || "#NAME"}} 37 |
38 |
39 |
40 |
41 |
42 | 43 | 70 | -------------------------------------------------------------------------------- /src/components/settings/settings.component.ts: -------------------------------------------------------------------------------- 1 | import { NgRedux, select } from '@angular-redux/store'; 2 | import { Component } from '@angular/core'; 3 | import { Hotkey, HotkeysService } from 'angular2-hotkeys'; 4 | import { Observable } from 'rxjs'; 5 | import { ErrorHighlightService } from '../../services/error-highlight.service'; 6 | import { MapService } from '../../services/map.service'; 7 | import { ProcessService } from '../../services/process.service'; 8 | import { StorageService } from '../../services/storage.service'; 9 | import { TutorialService } from '../../services/tutorial.service'; 10 | import { AppActions } from '../../store/app/actions'; 11 | import { IAppState } from '../../store/model'; 12 | 13 | @Component({ 14 | selector: 'settings', 15 | styleUrls: ['./settings.component.less', '../../styles/main.less'], 16 | templateUrl: './settings.component.html', 17 | }) 18 | export class SettingsComponent { 19 | @select(['app', 'advancedExpMode']) 20 | readonly advancedExpMode$: Observable; 21 | @select(['app', 'goodConnectMode']) 22 | readonly goodConnectMode$: Observable; 23 | @select(['app', 'editing']) readonly editing$: Observable; 24 | 25 | constructor( 26 | public appActions: AppActions, 27 | private errorHighlightSrv: ErrorHighlightService, 28 | private processSrv: ProcessService, 29 | private storageSrv: StorageService, 30 | private mapSrv: MapService, 31 | private tutorialSrv: TutorialService, 32 | private ngRedux: NgRedux, 33 | private hotkeysService: HotkeysService, 34 | ) { 35 | this.hotkeysService.add([ 36 | new Hotkey( 37 | 'shift+c', 38 | (): boolean => { 39 | this.toggleConnMode(); 40 | return false; 41 | }, 42 | undefined, 43 | 'Toggle connection mode', 44 | ), 45 | new Hotkey( 46 | 'shift+a', 47 | (): boolean => { 48 | this.toggleExpMode(); 49 | return false; 50 | }, 51 | undefined, 52 | 'Toggle expert mode', 53 | ), 54 | ]); 55 | } 56 | 57 | changeConnMode(goodConnectMode: boolean): void { 58 | this.appActions.actSetGoodConnectMode(goodConnectMode); 59 | localStorage.setItem('goodConnectMode', JSON.stringify(goodConnectMode)); 60 | } 61 | 62 | changeExpMode(advancedExpMode: boolean): void { 63 | this.appActions.actToggleSwitchMode(false); 64 | this.processSrv.refreshSidebarView('cancel selection'); 65 | this.mapSrv.removePopUps(); 66 | this.storageSrv.currentElement = null; 67 | this.storageSrv.currentElementsChange.emit(null); 68 | this.appActions.actSetErrorCorrectionMode(null); 69 | this.appActions.actSetAdvancedExpMode(advancedExpMode); 70 | localStorage.setItem('advancedMode', JSON.stringify(advancedExpMode)); 71 | if ( 72 | this.ngRedux.getState()['app']['tutorialMode'] === false && 73 | advancedExpMode 74 | ) { 75 | this.storageSrv.tutorialStepCompleted.emit(); 76 | } 77 | } 78 | 79 | toggleConnMode(): void { 80 | const connMode = this.ngRedux.getState()['app']['goodConnectMode']; 81 | this.appActions.actSetGoodConnectMode(!connMode); 82 | localStorage.setItem('goodConnectMode', JSON.stringify(!connMode)); 83 | } 84 | 85 | toggleExpMode(): void { 86 | const advancedExpMode = this.ngRedux.getState()['app']['advancedExpMode']; 87 | this.appActions.actToggleSwitchMode(false); 88 | this.processSrv.refreshSidebarView('cancel selection'); 89 | this.mapSrv.removePopUps(); 90 | this.storageSrv.currentElement = null; 91 | this.storageSrv.currentElementsChange.emit(null); 92 | this.appActions.actSetErrorCorrectionMode(null); 93 | this.appActions.actSetAdvancedExpMode(!advancedExpMode); 94 | localStorage.setItem('advancedMode', JSON.stringify(!advancedExpMode)); 95 | if ( 96 | this.ngRedux.getState()['app']['tutorialMode'] === false && 97 | !advancedExpMode 98 | ) { 99 | this.storageSrv.tutorialStepCompleted.emit(); 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/components/beginner/beginner.component.ts: -------------------------------------------------------------------------------- 1 | import { NgRedux, select } from '@angular-redux/store'; 2 | import { Component, ElementRef, ViewChild } from '@angular/core'; 3 | import { Observable } from 'rxjs'; 4 | import { 5 | IRouteBrowserOptions, 6 | ISuggestionsBrowserOptions, 7 | ITagBrowserOptions, 8 | } from '../../core/editingOptions.interface'; 9 | import { PtTags } from '../../core/ptTags.class'; 10 | import { ProcessService } from '../../services/process.service'; 11 | import { StorageService } from '../../services/storage.service'; 12 | import { AppActions } from '../../store/app/actions'; 13 | import { IAppState } from '../../store/model'; 14 | 15 | @Component({ 16 | providers: [], 17 | selector: 'beginner', 18 | styleUrls: ['./beginner.component.less', '../../styles/main.less'], 19 | templateUrl: './beginner.component.html', 20 | }) 21 | export class BeginnerComponent { 22 | @ViewChild('accordion1') a1: ElementRef; 23 | 24 | @select(['app', 'beginnerView']) readonly beginnerView$: Observable; 25 | @select(['app', 'errorCorrectionMode']) 26 | readonly errorCorrectionMode$: Observable; 27 | @select(['app', 'editing']) readonly editing$: Observable; 28 | expectedKeys = PtTags.expectedKeys; 29 | routeBrowserOptions: IRouteBrowserOptions = { 30 | createRoute: false, 31 | changeMembers: false, 32 | membersEditing: false, 33 | toggleFilteredView: false, 34 | }; 35 | stopTagBrowserOptions: ITagBrowserOptions = { 36 | limitedKeys: true, 37 | allowedKeys: this.expectedKeys.filter(this.filterStopKeysForBeginner), 38 | makeKeysReadOnly: true, 39 | }; 40 | routeTagBrowserOptions: ITagBrowserOptions = { 41 | limitedKeys: true, 42 | allowedKeys: this.expectedKeys.filter(this.filterRouteKeysForBeginner), 43 | makeKeysReadOnly: true, 44 | }; 45 | 46 | suggestionsBrowserOptions: ISuggestionsBrowserOptions = { 47 | nameSuggestions: { 48 | found: false, 49 | startCorrection: false, 50 | }, 51 | refSuggestions: null, 52 | waySuggestions: null, 53 | PTvSuggestions: { 54 | found: false, 55 | startCorrection: false, 56 | }, 57 | ptPairSuggestions: { 58 | found: false, 59 | startCorrection: false, 60 | }, 61 | }; 62 | a2; 63 | 64 | constructor( 65 | private appActions: AppActions, 66 | private processSrv: ProcessService, 67 | private storageSrv: StorageService, 68 | private ngRedux: NgRedux, 69 | ) {} 70 | 71 | /** 72 | * Returns allowed keys (for tags) for beginner mode stops 73 | */ 74 | private filterStopKeysForBeginner(key: string): boolean { 75 | return key === 'name' || key === 'ref'; 76 | } 77 | 78 | /** 79 | * Returns allowed keys (for tags) for beginner mode routes 80 | */ 81 | private filterRouteKeysForBeginner(key: string): boolean { 82 | return key === 'name' || key === 'ref'; 83 | } 84 | 85 | /** 86 | * Refreshes view for back button functionality 87 | */ 88 | back(): void { 89 | this.appActions.actSetBeginnerView('stop'); 90 | this.storageSrv.currentElement = this.storageSrv.selectedStopBeginnerMode; 91 | this.processSrv.refreshSidebarView('tag'); 92 | this.processSrv.exploreStop( 93 | this.storageSrv.currentElement, 94 | false, 95 | true, 96 | true, 97 | ); 98 | this.storageSrv.tutorialStepCompleted.emit('click back button'); 99 | } 100 | 101 | /** 102 | * Determines whether given component should be viewed 103 | */ 104 | shouldView(windowName: string): boolean { 105 | const beginnerView = this.ngRedux.getState()['app']['beginnerView']; 106 | const editing = this.ngRedux.getState()['app']['editing']; 107 | 108 | switch (windowName) { 109 | case 'route-browser': 110 | return beginnerView === 'stop'; 111 | 112 | case 'tag-browser': 113 | return true; 114 | 115 | case 'validation-browser': 116 | return beginnerView === 'stop' && editing; 117 | 118 | default: 119 | return false; 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/components/sidebar/stop-browser.component.ts: -------------------------------------------------------------------------------- 1 | import { select } from '@angular-redux/store'; 2 | import { Component, OnInit } from '@angular/core'; 3 | import { DragulaService } from 'ng2-dragula'; 4 | import { Observable } from 'rxjs'; 5 | import { IPtRelation } from '../../core/ptRelation.interface'; 6 | import { IPtStop } from '../../core/ptStop.interface'; 7 | import { EditService } from '../../services/edit.service'; 8 | import { MapService } from '../../services/map.service'; 9 | import { ProcessService } from '../../services/process.service'; 10 | import { StorageService } from '../../services/storage.service'; 11 | 12 | @Component({ 13 | providers: [], 14 | selector: 'stop-browser', 15 | styleUrls: ['./stop-browser.component.less', '../../styles/main.less'], 16 | templateUrl: './stop-browser.component.html', 17 | }) 18 | export class StopBrowserComponent implements OnInit { 19 | listOfStopsForRoute: IPtStop[] = this.storageSrv.listOfStopsForRoute; 20 | currentElement; 21 | listOfStops: IPtStop[] = this.storageSrv.listOfStops; 22 | filteredView: boolean; 23 | @select(['app', 'editing']) readonly editing$: Observable; 24 | 25 | constructor( 26 | private dragulaSrv: DragulaService, 27 | private editSrv: EditService, 28 | private mapSrv: MapService, 29 | private processSrv: ProcessService, 30 | private storageSrv: StorageService, 31 | ) { 32 | dragulaSrv.drop.subscribe((value) => { 33 | this.onDrop(value.slice(1)); 34 | }); 35 | } 36 | 37 | ngOnInit(): void { 38 | this.processSrv.showStopsForRoute$.subscribe((data) => { 39 | this.filteredView = data; 40 | }); 41 | 42 | this.processSrv.refreshSidebarViews$.subscribe((data) => { 43 | if (data === 'stop') { 44 | this.listOfStopsForRoute = this.storageSrv.listOfStopsForRoute; 45 | this.currentElement = this.storageSrv.currentElement; 46 | console.log(this.currentElement, this.listOfStopsForRoute); 47 | } else if (data === 'tag') { 48 | this.currentElement = this.storageSrv.currentElement; 49 | } else if (data === 'cancel selection') { 50 | this.listOfStopsForRoute = undefined; 51 | delete this.listOfStopsForRoute; 52 | this.currentElement = undefined; 53 | delete this.currentElement; 54 | this.filteredView = false; 55 | } 56 | }); 57 | } 58 | 59 | reorderingEnabled(): boolean { 60 | if (this.currentElement) { 61 | return this.currentElement.type === 'relation' && this.filteredView; 62 | } else { 63 | return false; 64 | } 65 | } 66 | 67 | isDownloaded(nodeId: number): boolean { 68 | return this.storageSrv.elementsDownloaded.has(nodeId); 69 | } 70 | 71 | reorderMembers(rel: IPtRelation): void { 72 | this.editSrv.reorderMembers(rel); 73 | } 74 | 75 | private createChange(): void { 76 | const type = 'change members'; 77 | const elementsWithoutRole = this.currentElement['members'].filter( 78 | (member) => { 79 | return member['role'] === ''; 80 | }, 81 | ); 82 | const change = { 83 | from: JSON.parse(JSON.stringify(this.currentElement['members'])), 84 | to: JSON.parse( 85 | JSON.stringify([...this.listOfStopsForRoute, ...elementsWithoutRole]), 86 | ), 87 | }; 88 | this.editSrv.addChange(this.currentElement, type, change); 89 | } 90 | 91 | private onDrop(args): void { 92 | if (this.currentElement.type !== 'relation') { 93 | return alert( 94 | 'Current element has incorrent type. Select relation one more time please.', 95 | ); 96 | } 97 | this.createChange(); 98 | } 99 | 100 | cancelFilter(): void { 101 | this.processSrv.activateFilteredStopView(false); 102 | } 103 | 104 | exploreStop($event, stop: IPtStop): void { 105 | this.processSrv.exploreStop(stop, true, true, true); 106 | } 107 | 108 | isSelected(relId: number): boolean { 109 | return this.processSrv.haveSameIds(relId, this.currentElement.id); 110 | } 111 | 112 | /** 113 | * NgFor track function which helps to re-render rows faster. 114 | */ 115 | trackByFn(index: number, item): number { 116 | return item.id; 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/assets/transport/railway/tram.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/components/editor/editor.component.html: -------------------------------------------------------------------------------- 1 | 9 |
10 | 17 | 24 |
25 |
26 |
{{currentEditStep }}/{{totalEditSteps }}
27 |
28 | 29 |
30 | 36 | 42 | 43 |
44 | 49 | 54 |
55 |
56 | 57 | 88 | -------------------------------------------------------------------------------- /src/components/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { NgRedux, select } from '@angular-redux/store'; 2 | import { Component, isDevMode, OnInit, ViewChild } from '@angular/core'; 3 | import * as L from 'leaflet'; 4 | import { Spinkit } from 'ng-http-loader'; 5 | import { CarouselConfig, ModalDirective } from 'ngx-bootstrap'; 6 | import { Observable, Subject } from 'rxjs'; 7 | import { debounceTime, distinctUntilChanged } from 'rxjs/operators'; 8 | import { AuthService } from '../../services/auth.service'; 9 | import { DbService } from '../../services/db.service'; 10 | import { EditService } from '../../services/edit.service'; 11 | import { GeocodeService } from '../../services/geocode.service'; 12 | import { MapService } from '../../services/map.service'; 13 | import { OverpassService } from '../../services/overpass.service'; 14 | import { ProcessService } from '../../services/process.service'; 15 | import { AppActions } from '../../store/app/actions'; 16 | import { IAppState } from '../../store/model'; 17 | import { AuthComponent } from '../auth/auth.component'; 18 | import { ToolbarComponent } from '../toolbar/toolbar.component'; 19 | 20 | @Component({ 21 | providers: [{ provide: CarouselConfig, useValue: { noPause: false } }], 22 | selector: 'app', 23 | styleUrls: ['./app.component.less'], 24 | templateUrl: './app.component.html', 25 | }) 26 | export class AppComponent implements OnInit { 27 | spinkit = Spinkit; 28 | advancedMode = Boolean(localStorage.getItem('advancedMode')); 29 | 30 | @ViewChild(ToolbarComponent) toolbarComponent: ToolbarComponent; 31 | @ViewChild(AuthComponent) authComponent: AuthComponent; 32 | @ViewChild('helpModal') helpModal: ModalDirective; 33 | 34 | @select(['app', 'editing']) readonly editing$: Observable; 35 | @select(['app', 'advancedExpMode']) 36 | readonly advancedExpMode$: Observable; 37 | @select(['app', 'tutorialMode']) readonly tutorialMode$: Observable; 38 | private startEventProcessing = new Subject(); 39 | 40 | constructor( 41 | public appActions: AppActions, 42 | private ngRedux: NgRedux, 43 | private dbSrv: DbService, 44 | private editSrv: EditService, 45 | private geocodeSrv: GeocodeService, 46 | private mapSrv: MapService, 47 | private overpassSrv: OverpassService, 48 | private processSrv: ProcessService, 49 | private authSrv: AuthService, 50 | ) { 51 | if (isDevMode()) { 52 | console.log('WARNING: Ang. development mode is ', isDevMode()); 53 | } 54 | } 55 | 56 | ngOnInit() { 57 | this.dbSrv.deleteExpiredDataIDB(); 58 | this.dbSrv 59 | .deleteExpiredPTDataIDB() 60 | .then(() => { 61 | console.log( 62 | 'LOG (app component) Successfully checked and deleted old items from IDB', 63 | ); 64 | }) 65 | .catch((err) => { 66 | console.log('LOG (app component) Error in deleting old items from IDB'); 67 | console.log(err); 68 | }); 69 | this.dbSrv.getCompletelyDownloadedElementsId(); 70 | this.dbSrv.getIdsQueriedRoutesForMaster(); 71 | const map = L.map('map', { 72 | center: L.latLng(49.686, 18.351), 73 | layers: [this.mapSrv.baseMaps.CartoDB_light], 74 | maxZoom: 22, 75 | minZoom: 4, 76 | zoom: 14, 77 | zoomAnimation: false, 78 | zoomControl: false, 79 | }); 80 | 81 | L.control.zoom({ position: 'topright' }).addTo(map); 82 | L.control.layers(this.mapSrv.baseMaps).addTo(map); 83 | L.control.scale().addTo(map); 84 | 85 | this.mapSrv.map = map; 86 | this.mapSrv.map.on('zoomend moveend', (event: L.LeafletEvent) => { 87 | this.startEventProcessing.next(event); 88 | }); 89 | this.startEventProcessing 90 | .pipe(debounceTime(500), distinctUntilChanged()) 91 | .subscribe((event: L.LeafletEvent) => { 92 | this.processSrv.addPositionToUrlHash(); 93 | this.overpassSrv.initDownloader(this.mapSrv.map); 94 | this.processSrv.filterDataInBounds(); 95 | }); 96 | 97 | if (window.location.hash !== '' && this.processSrv.hashIsValidPosition()) { 98 | this.mapSrv.zoomToHashedPosition(); 99 | } else { 100 | this.geocodeSrv.getCurrentLocation(); 101 | } 102 | } 103 | 104 | hideHelpModal(): void { 105 | this.helpModal.hide(); 106 | } 107 | 108 | private showHelpModal(): void { 109 | this.helpModal.show(); 110 | } 111 | 112 | startTutorials(): void { 113 | this.appActions.actToggleTutorialMode(true); 114 | } 115 | 116 | isAuthenticated(): void { 117 | return this.authSrv.oauth.authenticated(); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/components/toolbar/toolbar.component.html: -------------------------------------------------------------------------------- 1 | 8 | 9 | 16 | 17 | 18 | 19 | 20 | 24 | 25 |
26 |
27 | 34 | 41 |
42 |
43 | 44 | 48 | 49 | 50 | 51 | 54 | 55 |
56 |

{{currentElement.tags.name || currentElement.tags.public_transport || ""}} (id: {{currentElement.id || ""}}, type: {{currentElement.type || ""}})

57 | 63 | 64 | 82 | 83 | 90 | 96 | 97 | 98 | 99 |
100 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "osm-pt-ngx-leaflet", 3 | "version": "22.4.29", 4 | "engines": { 5 | "node": "22" 6 | }, 7 | "description": "An OpenStreetMap public transport editor built with Leaflet and Angular.", 8 | "repository": { 9 | "type": "git", 10 | "url": "git+https://github.com/dkocich/osm-pt-ngx-leaflet.git" 11 | }, 12 | "keywords": [ 13 | "angular", 14 | "leaflet", 15 | "openstreetmap", 16 | "public transport" 17 | ], 18 | "author": "David Kocich ", 19 | "license": "MIT", 20 | "bugs": { 21 | "url": "https://github.com/dkocich/osm-pt-ngx-leaflet/issues" 22 | }, 23 | "scripts": { 24 | "build": "ng build", 25 | "e2e": "ng e2e", 26 | "lint": "ng lint", 27 | "lint:all": "eslint src/**/*.ts src/components/**/*.ts src/*.ts", 28 | "lint:all:fix": "eslint --fix src/**/*.ts src/components/**/*.ts src/*.ts", 29 | "lint:fix": "ng lint --fix=true", 30 | "lint:one-rule-fix": "tslint -c tslint-empty.json src/**/*.ts --fix", 31 | "lint:navigableInWebstorm": "ng lint --format=stylish", 32 | "ng": "ng", 33 | "ngbuild": "ng build --progress --verbose --aot=true --build-optimizer", 34 | "nge2e": "ng e2e", 35 | "ngstart-hmr": "ng serve --configuration hmr --hmr --aot=true", 36 | "snyk-protect": "snyk protect", 37 | "start": "ng serve --port=4200", 38 | "stylelint": "stylelint **/*.css **/*.less", 39 | "test": "ng test", 40 | "test:all": "snyk test && NODE_ENV=development karma start" 41 | }, 42 | "homepage": "https://github.com/dkocich/osm-pt-ngx-leaflet#readme", 43 | "dependencies": { 44 | "@angular/animations": "~16.2.8", 45 | "@angular/common": "~16.2.8", 46 | "@angular/compiler": "~16.2.8", 47 | "@angular/core": "~16.2.8", 48 | "@angular/forms": "~16.2.8", 49 | "@angular/platform-browser": "~16.2.8", 50 | "@angular/platform-browser-dynamic": "~16.2.8", 51 | "@angular/router": "~16.2.8", 52 | "@angularclass/hmr": "3.0.0", 53 | "@ngx-translate/core": "15.0.0", 54 | "@ngx-translate/http-loader": "8.0.0", 55 | "@redux-devtools/extension": "3.2.5", 56 | "@sentry/browser": "8.33.0", 57 | "@types/bootstrap": "5.2.6", 58 | "@types/core-js": "2.5.5", 59 | "@types/es6-shim": "0.31.42", 60 | "@types/eslint": "8.44.2", 61 | "@types/express": "4.17.17", 62 | "@types/geojson": "7946.0.10", 63 | "@types/intro.js": "5.1.2", 64 | "@types/leaflet": "1.9.3", 65 | "@types/ramda": "0.29.3", 66 | "@types/redux-logger": "3.0.9", 67 | "angular2-hotkeys": "15.0.0", 68 | "angulartics2": "12.2.1", 69 | "bootstrap": "5.3.2", 70 | "core-js": "3.32.1", 71 | "dexie": "3.2.4", 72 | "express": "4.18.2", 73 | "flux-standard-action": "2.1.2", 74 | "font-awesome": "4.7.0", 75 | "intro.js": "7.2.0", 76 | "leaflet": "1.9.4", 77 | "leaflet-textpath": "1.2.3", 78 | "leaflet.vectorgrid": "1.3.0", 79 | "mobile-detect": "1.4.5", 80 | "ng-http-loader": "14.0.0", 81 | "ng2-dragula": "5.0.1", 82 | "ngx-bootstrap": "~11.0.2", 83 | "ngx-toastr": "17.0.2", 84 | "osm-auth": "2.2.0", 85 | "osmtogeojson": "3.0.0-beta.5", 86 | "ramda": "0.29.1", 87 | "redux": "4.2.1", 88 | "redux-logger": "3.0.6", 89 | "reflect-metadata": "0.1.13", 90 | "rxjs": "~7.8.1", 91 | "rxjs-compat": "~6.6.7", 92 | "stylelint": "16.12.0", 93 | "stylelint-config-standard": "36.0.1", 94 | "tslib": "2.6.2", 95 | "xmlbuilder": "15.1.1", 96 | "zone.js": "~0.13.1" 97 | }, 98 | "devDependencies": { 99 | "@angular-devkit/build-angular": "~16.2.5", 100 | "@angular-eslint/builder": "19.0.2", 101 | "@angular-eslint/eslint-plugin": "19.0.2", 102 | "@angular-eslint/eslint-plugin-template": "19.0.2", 103 | "@angular-eslint/schematics": "19.0.2", 104 | "@angular-eslint/template-parser": "19.0.2", 105 | "@angular/cli": "~16.2.5", 106 | "@angular/compiler-cli": "~16.2.8", 107 | "@angular/language-service": "~16.2.8", 108 | "@types/jasmine": "~4.3.5", 109 | "@types/jasminewd2": "~2.0.10", 110 | "@types/karma": "6.3.4", 111 | "@types/karma-chrome-launcher": "3.1.1", 112 | "@types/karma-coverage-istanbul-reporter": "2.1.1", 113 | "@types/karma-jasmine": "4.0.2", 114 | "@types/karma-jasmine-html-reporter": "1.7.0", 115 | "@types/node": "20.5.1", 116 | "@typescript-eslint/eslint-plugin": "^7.2.0", 117 | "@typescript-eslint/parser": "^7.2.0", 118 | "eslint": "^8.57.0", 119 | "eslint-config-prettier": "9.1.0", 120 | "eslint-plugin-import": "2.28.1", 121 | "eslint-plugin-jsdoc": "46.5.0", 122 | "eslint-plugin-prettier": "5.0.0", 123 | "ts-node": "~10.9.1", 124 | "typescript": "~5.1.6" 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/components/sidebar/stop-browser.component.html: -------------------------------------------------------------------------------- 1 |
2 |
4 |

{{ 'There are no loaded stops (zoom map).' | translate }}

5 | 6 | 11 | 12 | 13 | 18 | 19 |
20 | 21 |
23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 37 | 38 | 39 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 56 | 57 | 58 | 59 |
#{{ 'Role' | translate }}{{ 'atrId' | translate }}{{ 'atrRef' | translate }}
{{i + 1}}.{{stop.role}} 40 | platform 41 | stop 42 | {{stop.tags.name || stop.id}} 43 | {{stop.tags.route_ref}}
{{i + 1}}. 52 | platform 53 | stop 54 | {{stop.tags.name || stop.id}} 55 | {{stop.tags.route_ref}}
60 |
61 | 62 |
63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 82 | 83 | 84 | 85 | 86 |
{{ 'Name/Id' | translate }}PT{{ 'Route ref.' | translate }}
74 | 77 | 80 | {{stop.tags.name || stop.id}} 81 | {{stop.tags.public_transport}}{{stop.tags.route_ref}}
87 |
88 | 89 |
90 |
91 | -------------------------------------------------------------------------------- /angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "newProjectRoot": "projects", 4 | "projects": { 5 | "osm-pt-leaflet-ngx": { 6 | "architect": { 7 | "build": { 8 | "builder": "@angular-devkit/build-angular:browser", 9 | "configurations": { 10 | "production": { 11 | "budgets": [ 12 | { 13 | "maximumError": "5mb", 14 | "maximumWarning": "2mb", 15 | "type": "initial" 16 | }, 17 | { 18 | "maximumError": "10kb", 19 | "maximumWarning": "6kb", 20 | "type": "anyComponentStyle" 21 | } 22 | ], 23 | "buildOptimizer": true, 24 | "extractLicenses": true, 25 | "fileReplacements": [ 26 | { 27 | "replace": "src/environments/environment.ts", 28 | "with": "src/environments/environment.prod.ts" 29 | } 30 | ], 31 | "namedChunks": false, 32 | "optimization": true, 33 | "outputHashing": "all", 34 | "sourceMap": false, 35 | "vendorChunk": false 36 | } 37 | }, 38 | "options": { 39 | "aot": true, 40 | "assets": [ 41 | "src/assets", 42 | "src/land.html", 43 | "src/land_single.html", 44 | "src/favicon.ico" 45 | ], 46 | "index": "src/index.html", 47 | "main": "src/main.ts", 48 | "outputPath": "public/osm-pt-leaflet-ngx", 49 | "polyfills": "src/polyfills.ts", 50 | "scripts": [ 51 | "./node_modules/intro.js/minified/intro.min.js" 52 | ], 53 | "styles": [ 54 | "src/styles.css", 55 | "./node_modules/bootstrap/dist/css/bootstrap.css", 56 | "./node_modules/leaflet/dist/leaflet.css", 57 | "./node_modules/font-awesome/css/font-awesome.css", 58 | "./node_modules/dragula/dist/dragula.css", 59 | "./node_modules/ngx-toastr/toastr.css", 60 | "./node_modules/intro.js/minified/introjs.min.css" 61 | ], 62 | "tsConfig": "src/tsconfig.app.json" 63 | } 64 | }, 65 | "e2e": { 66 | "builder": "@angular-devkit/build-angular:protractor", 67 | "configurations": { 68 | "production": { 69 | "devServerTarget": "osm-pt-ngx-leaflet:serve:production" 70 | } 71 | }, 72 | "options": { 73 | "devServerTarget": "osm-pt-ngx-leaflet:serve", 74 | "protractorConfig": "e2e/protractor.conf.js" 75 | } 76 | }, 77 | "extract-i18n": { 78 | "builder": "@angular-devkit/build-angular:extract-i18n", 79 | "options": { 80 | "browserTarget": "osm-pt-leaflet-ngx:build" 81 | } 82 | }, 83 | "serve": { 84 | "builder": "@angular-devkit/build-angular:dev-server", 85 | "configurations": { 86 | "hmr": { 87 | "hmr": true 88 | }, 89 | "production": { 90 | "browserTarget": "osm-pt-leaflet-ngx:build:production" 91 | } 92 | }, 93 | "options": { 94 | "browserTarget": "osm-pt-leaflet-ngx:build" 95 | } 96 | }, 97 | "test": { 98 | "builder": "@angular-devkit/build-angular:karma", 99 | "options": { 100 | "assets": [ 101 | "src/assets", 102 | "src/land.html", 103 | "src/land_single.html", 104 | "src/favicon.ico" 105 | ], 106 | "karmaConfig": "src/karma.conf.js", 107 | "main": "src/test.ts", 108 | "polyfills": "src/polyfills.ts", 109 | "scripts": [ 110 | "./node_modules/intro.js/minified/intro.min.js" 111 | ], 112 | "styles": [ 113 | "src/styles.css", 114 | "./node_modules/bootstrap/dist/css/bootstrap.css", 115 | "./node_modules/leaflet/dist/leaflet.css", 116 | "./node_modules/font-awesome/css/font-awesome.css", 117 | "./node_modules/dragula/dist/dragula.css", 118 | "./node_modules/ngx-toastr/toastr.css", 119 | "./node_modules/intro.js/minified/introjs.min.css" 120 | ], 121 | "tsConfig": "src/tsconfig.spec.json" 122 | } 123 | } 124 | }, 125 | "prefix": "app", 126 | "projectType": "application", 127 | "root": "", 128 | "schematics": { 129 | "@schematics/angular:component": { 130 | "style": "less" 131 | } 132 | }, 133 | "sourceRoot": "src" 134 | }, 135 | }, 136 | "version": 1, 137 | "cli": { 138 | "analytics": false 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /src/components/sidebar/relation-browser.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, ViewChild } from '@angular/core'; 2 | import { ProcessService } from '../../services/process.service'; 3 | import { StorageService } from '../../services/storage.service'; 4 | import { EditService } from '../../services/edit.service'; 5 | import { ModalDirective } from 'ngx-bootstrap'; 6 | import { IPtRouteMasterNew } from '../../core/ptRouteMasterNew.interface'; 7 | import { IOsmElement } from '../../core/osmElement.interface'; 8 | import { Observable } from 'rxjs'; 9 | import { NgRedux, select } from '@angular-redux/store'; 10 | import { IAppState } from '../../store/model'; 11 | import { Hotkey, HotkeysService } from 'angular2-hotkeys'; 12 | 13 | @Component({ 14 | providers: [], 15 | selector: 'relation-browser', 16 | styleUrls: ['./relation-browser.component.less', '../../styles/main.less'], 17 | templateUrl: './relation-browser.component.html', 18 | }) 19 | export class RelationBrowserComponent implements OnInit { 20 | constructor( 21 | private editSrv: EditService, 22 | private processSrv: ProcessService, 23 | private storageSrv: StorageService, 24 | private hotkeysService: HotkeysService, 25 | private ngRedux: NgRedux, 26 | ) { 27 | this.hotkeysService.add( 28 | new Hotkey( 29 | '3', 30 | (): boolean => { 31 | if ( 32 | this.ngRedux.getState()['app']['editing'] && 33 | this.ngRedux.getState()['app']['advancedExpMode'] 34 | ) { 35 | this.createMaster(); 36 | } 37 | return false; 38 | }, 39 | undefined, 40 | 'Create a new route master', 41 | ), 42 | ); 43 | } 44 | currentElement: IOsmElement | IPtRouteMasterNew; 45 | listOfVariants = this.storageSrv.listOfVariants; 46 | @select(['app', 'editing']) readonly editing$: Observable; 47 | listOfMasters = this.storageSrv.listOfMasters; 48 | 49 | @ViewChild('masterModal') masterModal: ModalDirective; 50 | 51 | ngOnInit(): void { 52 | this.processSrv.refreshSidebarViews$.subscribe((data) => { 53 | if (data === 'tag') { 54 | console.log( 55 | 'LOG (relation-browser) Current selected element changed - ', 56 | data, 57 | ); 58 | if (this.storageSrv.currentElement.tags.type === 'route_master') { 59 | // prevent showing members of everything except route_master 60 | this.currentElement = this.storageSrv.currentElement; 61 | } 62 | } else if (data === 'cancel selection') { 63 | this.currentElement = undefined; 64 | delete this.currentElement; 65 | this.storageSrv.listOfVariants.length = 0; 66 | this.listOfVariants.length = 0; 67 | } 68 | }); 69 | 70 | this.processSrv.refreshSidebarViews$.subscribe((data) => { 71 | if (data === 'relation') { 72 | this.listOfVariants = this.storageSrv.listOfVariants; 73 | console.log( 74 | 'LOG (relation-browser) List of variants ', 75 | this.storageSrv.listOfVariants, 76 | ' currentElement', 77 | this.storageSrv.currentElement, 78 | ); 79 | } 80 | }); 81 | } 82 | 83 | /** 84 | * NgFor track function which helps to re-render rows faster. 85 | */ 86 | trackByFn(index: number, item): number { 87 | return item.id; 88 | } 89 | 90 | hideMasterModal(): void { 91 | this.masterModal.hide(); 92 | } 93 | 94 | isDownloaded(relId: number): boolean { 95 | return this.storageSrv.elementsDownloaded.has(relId) || relId < 0; // show created 96 | } 97 | 98 | exploreRelation($event, relId: number): void { 99 | this.processSrv.exploreRelation( 100 | this.storageSrv.elementsMap.get(relId), 101 | true, 102 | false, 103 | true, 104 | ); 105 | } 106 | 107 | hasMaster(): boolean { 108 | if (this.currentElement) { 109 | return this.storageSrv.idsHaveMaster.has(this.currentElement.id); // create only one route_master for each element 110 | } 111 | return false; // turned on to create route_masters without members 112 | } 113 | 114 | createMaster(): void { 115 | // FIXME - allow to create route_master of route_masters later 116 | if ( 117 | this.currentElement && 118 | this.currentElement.tags.type !== 'route_master' 119 | ) { 120 | this.editSrv.createMaster(this.currentElement.id); 121 | } else { 122 | this.editSrv.createMaster(); 123 | } 124 | } 125 | changeRouteMasterMembers(routeMasterId: number): void { 126 | this.editSrv.changeRouteMasterMembers( 127 | this.currentElement.id, 128 | routeMasterId, 129 | ); 130 | } 131 | 132 | showMasterModal(): void { 133 | this.masterModal.show(); 134 | // this.mapSrv.disableMouseEvent("modalDownload"); 135 | } 136 | 137 | isAlreadyAdded(relId: number): boolean { 138 | if (!this.currentElement) { 139 | return; 140 | } 141 | const rel = this.storageSrv.elementsMap.get(relId); 142 | const found = rel.members.filter((member) => { 143 | return member.ref === this.currentElement.id; 144 | }); 145 | return found.length > 0; 146 | } 147 | 148 | isSelected(relId: number): boolean { 149 | return this.processSrv.haveSameIds(relId, this.currentElement.id); 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/components/transporter/transporter.component.ts: -------------------------------------------------------------------------------- 1 | import { select } from '@angular-redux/store'; 2 | import { Component, OnInit, ViewChild } from '@angular/core'; 3 | import { Hotkey, HotkeysService } from 'angular2-hotkeys'; 4 | import { ModalDirective } from 'ngx-bootstrap'; 5 | import { Observable } from 'rxjs'; 6 | import { AuthService } from '../../services/auth.service'; 7 | import { MapService } from '../../services/map.service'; 8 | import { OverpassService } from '../../services/overpass.service'; 9 | import { StorageService } from '../../services/storage.service'; 10 | 11 | @Component({ 12 | providers: [], 13 | selector: 'transporter', 14 | styleUrls: [ 15 | '../toolbar/toolbar.component.less', 16 | './transporter.component.less', 17 | '../../styles/main.less', 18 | ], 19 | templateUrl: './transporter.component.html', 20 | }) 21 | export class TransporterComponent implements OnInit { 22 | @ViewChild('downloadModal') downloadModal: ModalDirective; 23 | @ViewChild('uploadModal') uploadModal: ModalDirective; 24 | @select(['app', 'tutorialMode']) readonly tutorialMode$: Observable; 25 | 26 | favoriteQueries = [ 27 | { 28 | id: 1, 29 | raw: 30 | '%5Bout%3Ajson%5D%5Btimeout%3A25%5D%3B%0A(%0A%20%20node%5B%22route%22%3D%22bus%22' + 31 | '%5D(%7B%7Bbbox%7D%7D)%3B%0A%20%20way%5B%22route%22%3D%22bus%22%5D(%7B%7Bbbox%7D%7D)%3B' + 32 | '%0A%20%20relation%5B%22route%22%3D%22bus%22%5D(%7B%7Bbbox%7D%7D)%3B%0A)%3B%0Aout%20body' + 33 | '%3B%0A%3E%3B%0Aout%20skel%20qt%3B', 34 | short: 'route=bus', 35 | }, 36 | { 37 | id: 2, 38 | raw: 39 | '%5Bout%3Ajson%5D%5Btimeout%3A25%5D%3B%0A(%0A%20%20node%5B%22route%22%3D%22bus%22%5D(' + 40 | '%7B%7Bbbox%7D%7D)%3B%0A%20%20way%5B%22route%22%3D%22bus%22%5D(%7B%7Bbbox%7D%7D)%3B%0A%20' + 41 | '%20relation%5B%22route%22%3D%22bus%22%5D(%7B%7Bbbox%7D%7D)%3B%0A%20%20node%5B%22highway' + 42 | '%22%5D(%7B%7Bbbox%7D%7D)%3B%0A%20%20way%5B%22highway%22%5D(%7B%7Bbbox%7D%7D)%3B%0A%20' + 43 | '%20relation%5B%22highway%22%5D(%7B%7Bbbox%7D%7D)%3B%0A)%3B%0Aout%20body%3B%0A%3E%3B%0Aout' + 44 | '%20skel%20qt%3B', 45 | short: 'route=bus OR highway=*', 46 | }, 47 | { 48 | id: 3, 49 | raw: 50 | '%5Bout%3Ajson%5D%5Btimeout%3A25%5D%3B%0A%28%0A%20%20node%5B%22public_transport%22%5D' + 51 | '%28%7B%7Bbbox%7D%7D%29%3B%0A%20%20way%5B%22public_transport%22%5D%28%7B%7Bbbox%7D%7D%29' + 52 | '%3B%0A%20%20relation%5B%22public_transport%22%5D%28%7B%7Bbbox%7D%7D%29%3B%0A%29%3B%0Aout' + 53 | '%20body%3B%0A%3E%3B%0Aout%20skel%20qt%3B', 54 | short: 'public_transport=*', 55 | }, 56 | ]; 57 | queryShort = this.favoriteQueries[0].short; 58 | queryRaw = decodeURIComponent(this.favoriteQueries[0].raw); 59 | editsSummary; 60 | 61 | comment = ''; 62 | 63 | source = ''; 64 | constructor( 65 | private authSrv: AuthService, 66 | private mapSrv: MapService, 67 | private overpassSrv: OverpassService, 68 | private storageSrv: StorageService, 69 | private hotkeysService: HotkeysService, 70 | ) { 71 | this.hotkeysService.add([ 72 | new Hotkey( 73 | 's', 74 | (): boolean => { 75 | this.uploadData(); 76 | return false; 77 | }, 78 | undefined, 79 | 'Upload data to OSM', 80 | ), 81 | ]); 82 | } 83 | 84 | ngOnInit(): void { 85 | this.mapSrv.disableMouseEvent('download-data'); 86 | this.mapSrv.disableMouseEvent('upload-data'); 87 | this.storageSrv.editsChanged.subscribe( 88 | /** 89 | * @param data - data event is true when storageSrv.edits change 90 | */ 91 | (data) => { 92 | if (data) { 93 | this.editsSummary = this.storageSrv.edits; 94 | } 95 | }, 96 | ); 97 | } 98 | 99 | showDownloadModal(): void { 100 | this.downloadModal.show(); 101 | this.mapSrv.disableMouseEvent('modalDownload'); 102 | } 103 | 104 | hideDownloadModal(): void { 105 | this.downloadModal.hide(); 106 | } 107 | 108 | showUploadModal(): void { 109 | this.uploadModal.show(); 110 | this.mapSrv.disableMouseEvent('modalUpload'); 111 | } 112 | 113 | hideUploadModal(): void { 114 | this.uploadModal.hide(); 115 | } 116 | 117 | isAuthenticated(): boolean { 118 | return this.authSrv.oauth.authenticated(); 119 | } 120 | 121 | hasEdits(): boolean { 122 | return this.storageSrv.edits.length > 0; 123 | } 124 | 125 | requestData(requestBody: string): void { 126 | this.overpassSrv.requestOverpassData(requestBody); 127 | this.hideDownloadModal(); 128 | } 129 | 130 | verifyUpload(): void { 131 | this.overpassSrv.uploadData( 132 | { source: 'test upload source', comment: 'test upload comment' }, 133 | true, 134 | ); 135 | } 136 | 137 | uploadData(): void { 138 | this.overpassSrv.uploadData({ 139 | comment: this.comment, 140 | source: this.source, 141 | }); 142 | } 143 | 144 | downloading(): boolean { 145 | return true; 146 | } 147 | 148 | setQuery(event): void { 149 | this.queryShort = event.target.textContent; 150 | const filtered = this.favoriteQueries.filter((iter) => { 151 | return iter.short === event.target.textContent; 152 | }); 153 | this.queryRaw = decodeURIComponent(filtered[0].raw); 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/components/transporter/transporter.component.html: -------------------------------------------------------------------------------- 1 | 7 | 8 | 15 | 16 | 68 | 69 | 116 | -------------------------------------------------------------------------------- /src/components/sidebar/route-browser.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

{{ 'There are no loaded routes (zoom map and select a feature).' | translate }}

4 |

{{ 'No available routes for selected feature.' | translate }}

5 | 6 | 12 | 13 | 14 | 20 | 28 | 29 | 30 | 39 | 40 |
41 | 42 |
44 | 45 |
46 |
47 | 49 | {{rel.tags.route || "#TYPE" }} Route {{rel.tags.ref || "#REF"}}: {{rel.tags.from || "#FROM"}} => {{rel.tags.to || "#TO"}}, {{rel.tags.name || "#NAME"}} 50 | 51 |
52 |
53 | 54 |
55 | 56 |
57 | 58 | 60 | {{rel.tags.route_master || "#ROUTE_MASTER"}} route MASTER ({{ rel.members.length }} members) {{rel.tags.ref || "#REF"}}: {{rel.tags.name || "#NAME"}} 61 | 62 |
63 |
64 | 65 | 66 |
67 | 69 | 70 | 73 | 76 | 78 | 79 | 80 | 83 | 86 | 89 | 92 | 93 | !!! 94 | {{rel.tags.route || "#TYPE" }} Route {{rel.tags.ref || "#REF"}}: {{rel.tags.from || "#FROM"}} => {{rel.tags.to || "#TO"}}, {{rel.tags.name || "#NAME"}} 95 | 96 |
97 |
98 |
99 |
100 | 101 |
102 | 103 |
104 | -------------------------------------------------------------------------------- /src/app.module.ts: -------------------------------------------------------------------------------- 1 | import { APP_BASE_HREF } from '@angular/common'; 2 | import { HttpClient, HttpClientModule } from '@angular/common/http'; 3 | import { ErrorHandler, NgModule } from '@angular/core'; 4 | import { FormsModule, ReactiveFormsModule } from '@angular/forms'; 5 | import { BrowserModule } from '@angular/platform-browser'; 6 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; 7 | import { RouterModule, Routes } from '@angular/router'; 8 | import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; 9 | import { TranslateHttpLoader } from '@ngx-translate/http-loader'; 10 | import { HotkeyModule } from 'angular2-hotkeys'; 11 | import { Angulartics2Module } from 'angulartics2'; 12 | import { Angulartics2Piwik } from 'angulartics2/piwik'; 13 | import 'leaflet'; 14 | import 'leaflet.vectorgrid'; 15 | import { NgHttpLoaderModule } from 'ng-http-loader'; 16 | import { DragulaModule } from 'ng2-dragula'; 17 | import { 18 | AccordionModule, 19 | BsDropdownModule, 20 | ButtonsModule, 21 | CarouselModule, 22 | ModalModule, 23 | SortableModule, 24 | TabsModule, 25 | TooltipModule, 26 | TypeaheadModule, 27 | } from 'ngx-bootstrap'; 28 | import { ToastrModule } from 'ngx-toastr'; 29 | import 'reflect-metadata'; 30 | import 'zone.js/dist/long-stack-trace-zone'; 31 | import 'zone.js/dist/zone'; 32 | import { AppComponent } from './components/app/app.component'; 33 | import { AuthComponent } from './components/auth/auth.component'; 34 | import { BeginnerComponent } from './components/beginner/beginner.component'; 35 | import { EditorComponent } from './components/editor/editor.component'; 36 | import { ExpertComponent } from './components/expert/expert.component'; 37 | import { LangComponent } from './components/lang/lang.component'; 38 | import { ModalComponent } from './components/modal/modal.component'; 39 | import { NavigatorComponent } from './components/navigator/navigator.component'; 40 | import { RouteMasterWizardComponent } from './components/route-master-wizard/route-master-wizard.component'; 41 | import { RouteWizardComponent } from './components/route-wizard/route-wizard.component'; 42 | import { SettingsComponent } from './components/settings/settings.component'; 43 | import { RelationBrowserComponent } from './components/sidebar/relation-browser.component'; 44 | import { RouteBrowserComponent } from './components/sidebar/route-browser.component'; 45 | import { StopBrowserComponent } from './components/sidebar/stop-browser.component'; 46 | import { TagBrowserComponent } from './components/sidebar/tag-browser.component'; 47 | import { ValidationBrowserComponent } from './components/sidebar/validation-browser.component'; 48 | import { ToolbarComponent } from './components/toolbar/toolbar.component'; 49 | import { TransporterComponent } from './components/transporter/transporter.component'; 50 | import { TutorialsComponent } from './components/tutorials/tutorials.component'; 51 | import { Utils } from './core/utils.class'; 52 | import { KeysPipe } from './pipes/keys.pipe'; 53 | import { TagsPropPipe } from './pipes/tags-prop.pipe'; 54 | import { RavenErrorHandler } from './raven-error-handler'; 55 | import { AuthService } from './services/auth.service'; 56 | import { ConfService } from './services/conf.service'; 57 | import { DbService } from './services/db.service'; 58 | import { EditService } from './services/edit.service'; 59 | import { ErrorHighlightService } from './services/error-highlight.service'; 60 | import { GeocodeService } from './services/geocode.service'; 61 | import { MapService } from './services/map.service'; 62 | import { OverpassService } from './services/overpass.service'; 63 | import { ProcessService } from './services/process.service'; 64 | import { RouteMasterWizardService } from './services/route-master-wizard.service'; 65 | import { RouteWizardService } from './services/route-wizard.service'; 66 | import { StorageService } from './services/storage.service'; 67 | import { TutorialService } from './services/tutorial.service'; 68 | import { WarnService } from './services/warn.service'; 69 | import { AppActions } from './store/app/actions'; 70 | import { RootEpics } from './store/epics'; 71 | import { StoreModule } from './store/module'; 72 | import { BsDropdownModule } from 'ngx-bootstrap/dropdown'; 73 | 74 | export function HttpLoaderFactory(http: HttpClient) { 75 | return new TranslateHttpLoader(http); 76 | } 77 | 78 | const ROUTES: Routes = [{ path: '', component: AppComponent }]; 79 | 80 | const conditional_providers = [ 81 | Utils.isProductionDeployment() 82 | ? { provide: ErrorHandler, useClass: RavenErrorHandler } 83 | : [], 84 | ]; 85 | 86 | @NgModule({ 87 | bootstrap: [AppComponent], 88 | entryComponents: [ 89 | RouteWizardComponent, 90 | AppComponent, 91 | ModalComponent, 92 | RouteMasterWizardComponent, 93 | ], 94 | declarations: [ 95 | AppComponent, 96 | AuthComponent, 97 | BeginnerComponent, 98 | EditorComponent, 99 | ExpertComponent, 100 | LangComponent, 101 | ModalComponent, 102 | NavigatorComponent, 103 | RelationBrowserComponent, 104 | RouteBrowserComponent, 105 | StopBrowserComponent, 106 | TagBrowserComponent, 107 | ToolbarComponent, 108 | TransporterComponent, 109 | SettingsComponent, 110 | RouteWizardComponent, 111 | ValidationBrowserComponent, 112 | RouteMasterWizardComponent, 113 | TutorialsComponent, 114 | 115 | KeysPipe, 116 | TagsPropPipe, 117 | ], 118 | imports: [ 119 | AccordionModule.forRoot(), 120 | Angulartics2Module.forRoot([Angulartics2Piwik]), 121 | BsDropdownModule.forRoot(), 122 | BrowserAnimationsModule, 123 | BrowserModule, 124 | ButtonsModule.forRoot(), 125 | CarouselModule.forRoot(), 126 | DragulaModule, 127 | FormsModule, 128 | HttpClientModule, 129 | ModalModule.forRoot(), 130 | NgHttpLoaderModule.forRoot(), 131 | TooltipModule.forRoot(), 132 | TranslateModule.forRoot({ 133 | loader: { 134 | provide: TranslateLoader, 135 | useFactory: HttpLoaderFactory, 136 | deps: [HttpClient], 137 | }, 138 | }), 139 | TypeaheadModule.forRoot(), 140 | RouterModule.forRoot(ROUTES), 141 | StoreModule, 142 | ToastrModule.forRoot({ 143 | timeOut: 2000, 144 | positionClass: 'toast-bottom-right', 145 | maxOpened: 1, 146 | }), 147 | SortableModule.forRoot(), 148 | TabsModule.forRoot(), 149 | ReactiveFormsModule, 150 | HotkeyModule.forRoot(), 151 | BsDropdownModule, 152 | ], 153 | providers: [ 154 | ...conditional_providers, 155 | 156 | AuthService, 157 | ConfService, 158 | DbService, 159 | EditService, 160 | ErrorHighlightService, 161 | GeocodeService, 162 | MapService, 163 | OverpassService, 164 | ProcessService, 165 | StorageService, 166 | WarnService, 167 | RouteWizardService, 168 | RouteMasterWizardService, 169 | TutorialService, 170 | 171 | KeysPipe, 172 | 173 | { provide: APP_BASE_HREF, useValue: '/' }, 174 | 175 | AppActions, 176 | RootEpics, 177 | ], 178 | }) 179 | export class AppModule {} 180 | -------------------------------------------------------------------------------- /src/services/tutorials.json: -------------------------------------------------------------------------------- 1 | { 2 | "beginner": { 3 | "Add new platform": [ 4 | { 5 | "element": "#toggle-edit", 6 | "intro": "Click here on the edit button to start editing", 7 | "position": "right" 8 | }, 9 | { 10 | "element": "#platform-btn", 11 | "intro": "Good Job! Now you can start editing. To add a new platform first click here on the platform button.", 12 | "position": "right" 13 | }, 14 | { 15 | "element": "#map", 16 | "intro": "Now click anywhere on the map to specify the location for the new platform.", 17 | "position": "right" 18 | }, 19 | { 20 | "element": "#tag-browser", 21 | "intro": "Now add the name or reference for the platform you just added.", 22 | "position": "right" 23 | }, 24 | { 25 | "element": "last-step", 26 | "intro": "Good job, you have successfully added a platform. Click the escape key to exit." 27 | } 28 | ], 29 | "Quick overview (beginner)" : [ 30 | { 31 | "element": "#map", 32 | "intro": "Try moving around in the map and zooming to download some new map data.", 33 | "position": "right" 34 | }, 35 | { 36 | "element": "#map", 37 | "intro": "Good Job! Click keyboard key to move to the next step.", 38 | "position": "right" 39 | }, 40 | { 41 | "element": "#map", 42 | "intro": "We have moved the map to a new location. Wait till the data is rendered and click on any of the stop which you see in center of the central park to start exploring it.", 43 | "position": "right" 44 | }, 45 | { 46 | "element": "#tag-browser", 47 | "intro": "Here is the tag browser window. You can view all the tags of the selected stop/platform which you just selected here. Click keyboard key to move to the next step.", 48 | "position": "right" 49 | }, 50 | { 51 | "element": "#route-browser", 52 | "intro": "You can also view the parent routes of the selected stop/platform here. Try selecting a route from the given list.", 53 | "position": "right" 54 | }, 55 | { 56 | "element": "#sidebar", 57 | "intro": "Now you can see the information about of route you just selected.Click keyboard key to move to the next step.", 58 | "position": "right" 59 | }, 60 | { 61 | "element": "#map", 62 | "intro": "The route which you selected is also highlighted on the map. Click keyboard key to move to the next step.", 63 | "position": "right" 64 | }, 65 | { 66 | "element": "#back-btn", 67 | "intro": "Click this button to go back to viewing information regarding your selected stop.", 68 | "position": "right" 69 | }, 70 | { 71 | "element": "#sidebar", 72 | "intro": "This way you can toggle between viewing information regarding the stop/platform selected or one of it's parent route. Click keyboard key to move to the next step.", 73 | "position": "right" 74 | }, 75 | { 76 | "element": "last-step", 77 | "intro": "Good Job!The tutorial is now complete. Click escape key to exit." 78 | } 79 | ] 80 | }, 81 | "expert": { 82 | "Add new route": [ 83 | { 84 | "element": "#toggle-edit", 85 | "intro": "Click on the edit button to start editing.", 86 | "position": "right" 87 | }, 88 | { 89 | "element": "#route-browser", 90 | "intro": "This the window where you can view all thr routes which are downloaded and have atleast one memeber in the current map bounds. Click on the panel heading to open it. ", 91 | "position": "right" 92 | }, 93 | { 94 | "element": "#create-route-btn", 95 | "intro": "Now to start creating a route, click on the create route button.", 96 | "position": "right" 97 | }, 98 | { 99 | "element": "#toggle-members-edit-btn", 100 | "intro": "You have now successfully created a route but there are no members added to your route! Click here to start adding/removing members of the route.", 101 | "position": "right" 102 | }, 103 | { 104 | "element": "#map", 105 | "intro": "Click on any marker on the map to add it to the new route.", 106 | "position": "right" 107 | }, 108 | { 109 | "element": "last-step", 110 | "intro": "Good job! You have successfully created a route with members.", 111 | "position": "right" 112 | } 113 | ], 114 | "Quick overview (expert)": [ 115 | { 116 | "element": "#toggle-edit", 117 | "intro": "Edit Button : Enables editing", 118 | "position": "right" 119 | }, 120 | { 121 | "element": "#tag-browser", 122 | "intro": " Tag Browser : Used for displaying/editing tags of the selected element.", 123 | "position": "right" 124 | }, 125 | { 126 | "element": "#route-browser", 127 | "intro": " Route Browser : Contains the list of routes (can be parent routes of a specific element or all the routes in the current map bounds) You can also create a new route, change it's members and download route masters from here.", 128 | "position": "right" 129 | }, 130 | { 131 | "element": "#relation-browser", 132 | "intro": " Relation Browser : Displays all route masters. You can also create a new route master from here.", 133 | "position": "right" 134 | }, 135 | { 136 | "element": "#stop-browser", 137 | "intro": " Stop Browser : Depending on the selection, this window displays a list of all the stops in the current map bounds or for a specific route.", 138 | "position": "right" 139 | }, 140 | { 141 | "element": "#suggestion-browser", 142 | "intro": " Suggestions Browser : Use this browser to highlight the map using certain rules such as missing name tags. You this when you want to make some quick edits.", 143 | "position": "right" 144 | }, 145 | { 146 | "element": "#platform-btn", 147 | "intro": " Add platform button : Use this to start adding new platforms to the map.", 148 | "position": "right" 149 | }, 150 | { 151 | "element": "#wizard-btn", 152 | "intro": " Wizards : Route Creation Wizard and Route Master Creation Wizard are available under this option.", 153 | "position": "right" 154 | }, 155 | { 156 | "element": "#undo-redo-btns", 157 | "intro": "Use these to undo/redo your edits.", 158 | "position": "right" 159 | }, 160 | { 161 | "element": "#edits-count", 162 | "intro": "Here you can see the number of edits you have made without uploading them.", 163 | "position": "right" 164 | }, 165 | { 166 | "element": "last-step", 167 | "intro": "Good job! You have successfully created a route with members.", 168 | "position": "right" 169 | } 170 | ] 171 | } 172 | } 173 | --------------------------------------------------------------------------------