Codebase list gnome-maps / upstream/3.35.1+git20191114.4eb1d51
Import upstream version 3.35.1+git20191114.4eb1d51, md5 e732769835c928ec0d8606294e9d2294 Debian Janitor 4 years ago
42 changed file(s) with 4033 addition(s) and 1676 deletion(s). Raw diff Collapse all Expand all
0 *.orig
1 *~
2 *#*
3
4 ABOUT-NLS
5 Makefile
6 Makefile.in
7 aclocal.m4
8 autom4te.cache/
9 libtool
10 config/
11 config.log
12 config.status
13 configure
14 install-sh
15 missing
16 .deps/
17 .flatpak-builder/
18 .libs/
19 *.o
20 *.lo
21 *.la
22
23 data/org.gnome.Maps.appdata.xml
24 data/org.gnome.Maps.desktop
25 data/org.gnome.Maps.gschema.valid
26
27 m4/*.m4
28
29 po/.intltool-merge-cache
30 po/Makefile.in.in
31 po/Makevars.template
32 po/POTFILES
33 po/Rules-quot
34 po/stamp-it
35 po/*.sed
36 po/*.gmo
37 po/*.sin
38 po/*.header
39
40 src/gnome-maps
41 src/*.gresource
42 src/org.gnome.Maps.service
43
44 /lib/GnomeMaps-1.0.gir
45 /lib/GnomeMaps-1.0.typelib
46 /lib/maps-enum-types.[ch]
47
48 build/
0 stages:
1 - test
2 - review
3
4 variables:
5 # Replace with your preferred file name of the resulting Flatpak bundle
6 BUNDLE: "gnome-maps-git.flatpak"
7
8 flatpak:
9 image: registry.gitlab.gnome.org/gnome/gnome-runtime-images/gnome:master
10 stage: test
11 variables:
12 # Replace with your manifest path
13 MANIFEST_PATH: "org.gnome.Maps.json"
14 RUNTIME_REPO: "https://sdk.gnome.org/gnome-nightly.flatpakrepo"
15 # Replace with your application name, as written in the manifest
16 FLATPAK_MODULE: "gnome-maps"
17 # Make sure to keep this in sync with the Flatpak manifest, all arguments
18 # are passed except the config-args because we build it ourselves
19 MESON_ARGS: ""
20 DBUS_ID: "org.gnome.Maps"
21
22 script:
23 - flatpak-builder --stop-at=${FLATPAK_MODULE} app ${MANIFEST_PATH}
24 # Make sure to keep this in sync with the Flatpak manifest, all arguments
25 # are passed except the config-args because we build it ourselves
26 - flatpak build app meson --prefix=/app ${MESON_ARGS} _build
27 - flatpak build app ninja -C _build install
28 - flatpak-builder --finish-only --repo=repo app ${MANIFEST_PATH}
29 # Generate a Flatpak bundle
30 - flatpak build-bundle repo ${BUNDLE} --runtime-repo=${RUNTIME_REPO} ${DBUS_ID}
31 # Run automatic tests inside the Flatpak env
32 - xvfb-run -a -s "-screen 0 1024x768x24" flatpak build app ninja -C _build test
33 artifacts:
34 paths:
35 - ${BUNDLE}
36 - _build/meson-logs/meson-log.txt
37 - _build/meson-logs/testlog.txt
38 expire_in: 30 days
39 cache:
40 paths:
41 - .flatpak-builder/cache
42
43 review:
44 stage: review
45 dependencies:
46 - flatpak
47 script:
48 - echo "Generating flatpak deployment"
49 artifacts:
50 paths:
51 - ${BUNDLE}
52 expire_in: 30 days
53 environment:
54 name: review/$CI_COMMIT_REF_NAME
55 url: https://gitlab.gnome.org/$CI_PROJECT_PATH/-/jobs/$CI_JOB_ID/artifacts/raw/${BUNDLE}
56 on_stop: stop_review
57 except:
58 - master@GNOME/gnome-maps
59 - tags
60
61 stop_review:
62 stage: review
63 script:
64 - echo "Stopping flatpak deployment"
65 when: manual
66 environment:
67 name: review/$CI_COMMIT_REF_NAME
68 action: stop
69 except:
70 - master@GNOME/gnome-maps
71 - tags
0 3.34.1 - Oct 7, 2019
0 3.35.1 - Oct 12, 2019
11 =========================
22
33 Changes since 3.34.0
4 - Update tile size to 512 px when using --local option
4 - Initial support for public trasit routing/journey planning using third-party
5 service providers
6 - Add nightly app icon (currently not installed, awaiting support for dual
7 installations)
8 - Update default tile size when using local tiles
59
610 Added/updated/fixed translations
11 - Spanish
712 - Danish
813 - Slovak
9 - Persian
1014 - Dutch
1115 - Friulian
1216 - Italian
1317
1418 All contributors to this release
1519 Ask Hjorth Larsen <asklarsen@gmail.com>
16 Danial Behzadi <dani.behzi@ubuntu.com>
20 Daniel Mustieles <daniel.mustieles@gmail.com>
1721 Dušan Kazik <prescott66@gmail.com>
1822 Fabio Tomat <f.t.public@gmail.com>
1923 Gianvito Cavasoli <gianvito@gmx.it>
24 Jakub Steiner <jimmac@gmail.com>
2025 Marcus Lundblad <ml@update.uu.se>
2126 Nathan Follens <nfollens@gnome.org>
2227
0 <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16">
1 <path d="M6.497 1c-.794.001-.781.033-.532 1.031L7.59 7h-4.5L1.872 5.219C1.732 5.009 1.749 5 1.528 5h-.219c-.428 0-.281.438-.281.438L1.309 8l-.281 2.563s-.14.437.25.437h.25c.211 0 .204-.009.344-.219L3.09 9h4.5l-1.625 4.938C5.704 14.983 5.701 15 6.497 15c.432 0 .433-.012.718-.5L10.903 9h3.094c.554 0 1-.446 1-1s-.446-1-1-1h-3.094L7.215 1.5c-.266-.457-.283-.498-.656-.5z" fill="#474747"/>
2 </svg>
0 <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128"><defs><clipPath id="k"><path d="M36 12h68v85H36zm0 0"/></clipPath><clipPath id="E"><path d="M8 28h112v88H8zm0 0"/></clipPath><clipPath id="G"><path d="M8 28h24v69H8zm0 0"/></clipPath><clipPath id="H"><path d="M16 28h16l16-16 16 16h48c4.434 0 8 3.566 8 8v72c0 4.434-3.566 8-8 8H64l-16-16-16 16H16a7.98 7.98 0 01-8-8V36c0-4.434 3.602-8.55 8-8zm0 0"/></clipPath><clipPath id="I"><path d="M36 12h68v85H36zm0 0"/></clipPath><clipPath id="J"><path d="M16 28h16l16-16 16 16h48c4.434 0 8 3.566 8 8v72c0 4.434-3.566 8-8 8H64l-16-16-16 16H16a7.98 7.98 0 01-8-8V36c0-4.434 3.602-8.55 8-8zm0 0"/></clipPath><clipPath id="K"><path d="M8 12h100v89H8zm0 0"/></clipPath><clipPath id="L"><path d="M16 28h16l16-16 16 16h48c4.434 0 8 3.566 8 8v72c0 4.434-3.566 8-8 8H64l-16-16-16 16H16a7.98 7.98 0 01-8-8V36c0-4.434 3.602-8.55 8-8zm0 0"/></clipPath><clipPath id="M"><path d="M8 12h112v104H8zm0 0"/></clipPath><clipPath id="N"><path d="M16 28h16l16-16 16 16h48c4.434 0 8 3.566 8 8v72c0 4.434-3.566 8-8 8H64l-16-16-16 16H16a7.98 7.98 0 01-8-8V36c0-4.434 3.602-8.55 8-8zm0 0"/></clipPath><clipPath id="B"><path d="M0 0h128v128H0z"/></clipPath><clipPath id="z"><path d="M0 0h128v128H0z"/></clipPath><clipPath id="b"><path d="M0 0h192v152H0z"/></clipPath><clipPath id="w"><path d="M0 0h128v128H0z"/></clipPath><clipPath id="f"><path d="M0 0h128v128H0z"/></clipPath><clipPath id="c"><path d="M0 0h192v152H0z"/></clipPath><clipPath id="e"><path d="M0 0h192v152H0z"/></clipPath><clipPath id="d"><path d="M0 0h192v152H0z"/></clipPath><clipPath id="p"><path d="M16 28h16l16-16 16 16h48c4.434 0 8 3.566 8 8v72c0 4.434-3.566 8-8 8H64l-16-16-16 16H16a7.98 7.98 0 01-8-8V36c0-4.434 3.602-8.55 8-8zm0 0"/></clipPath><clipPath id="g"><path d="M8 28h112v88H8zm0 0"/></clipPath><clipPath id="h"><path d="M16 28h16l16-16 16 16h48c4.434 0 8 3.566 8 8v72c0 4.434-3.566 8-8 8H64l-16-16-16 16H16a7.98 7.98 0 01-8-8V36c0-4.434 3.602-8.55 8-8zm0 0"/></clipPath><clipPath id="i"><path d="M8 28h24v69H8zm0 0"/></clipPath><clipPath id="j"><path d="M16 28h16l16-16 16 16h48c4.434 0 8 3.566 8 8v72c0 4.434-3.566 8-8 8H64l-16-16-16 16H16a7.98 7.98 0 01-8-8V36c0-4.434 3.602-8.55 8-8zm0 0"/></clipPath><clipPath id="F"><path d="M16 28h16l16-16 16 16h48c4.434 0 8 3.566 8 8v72c0 4.434-3.566 8-8 8H64l-16-16-16 16H16a7.98 7.98 0 01-8-8V36c0-4.434 3.602-8.55 8-8zm0 0"/></clipPath><clipPath id="l"><path d="M16 28h16l16-16 16 16h48c4.434 0 8 3.566 8 8v72c0 4.434-3.566 8-8 8H64l-16-16-16 16H16a7.98 7.98 0 01-8-8V36c0-4.434 3.602-8.55 8-8zm0 0"/></clipPath><clipPath id="m"><path d="M8 12h100v89H8zm0 0"/></clipPath><clipPath id="n"><path d="M16 28h16l16-16 16 16h48c4.434 0 8 3.566 8 8v72c0 4.434-3.566 8-8 8H64l-16-16-16 16H16a7.98 7.98 0 01-8-8V36c0-4.434 3.602-8.55 8-8zm0 0"/></clipPath><clipPath id="o"><path d="M8 12h112v104H8zm0 0"/></clipPath><mask id="t"><g filter="url(#a)"><path fill-opacity=".15" d="M0 0h128v128H0z"/></g></mask><mask id="O"><g filter="url(#a)"><path fill-opacity=".3" d="M0 0h128v128H0z"/></g></mask><mask id="D"><g filter="url(#a)"><path fill-opacity=".8" d="M0 0h128v128H0z"/></g></mask><mask id="P"><g filter="url(#a)"><path fill-opacity=".15" d="M0 0h128v128H0z"/></g></mask><mask id="r"><g filter="url(#a)"><path fill-opacity=".3" d="M0 0h128v128H0z"/></g></mask><mask id="T"><use xlink:href="#y"/></mask><g id="q" clip-path="url(#b)"><path d="M32 131.992l16 .016V12H32zm0 0" fill="#fff"/></g><g id="s" clip-path="url(#c)"><path d="M48 131.992l16 .016V12H48zm0 0"/></g><g id="x" clip-path="url(#f)"><g clip-path="url(#g)"><g clip-path="url(#h)"><path d="M64 96h40V28h24v96H64l-16-16-16 16H0V96h32l16-16zm0 0" fill="#cdab8f"/></g></g><g clip-path="url(#i)"><g clip-path="url(#j)"><path d="M0 95.992l32 .016V28H0zm0 0" fill="#55a7eb"/></g></g><g clip-path="url(#k)"><g clip-path="url(#l)"><path d="M36 24v68l12-12 16 16.016h36L104 92V28H64L48 12zm0 0" fill="#2ec27e"/></g></g><g clip-path="url(#m)"><g clip-path="url(#n)"><path d="M99.973 28.027v59.5c0 2.508-2.34 4.489-4.235 4.489H64L48 76.148l-9.844 9.883L38 12.016h-6V92l-32 .016v8.023h31.75L48 84l16 16.04h31.738c6.606 0 11.989-5.513 12.262-12.513v-59.5zm0 0" fill="#fff"/></g></g><g clip-path="url(#o)"><g clip-path="url(#p)"><use xlink:href="#q" mask="url(#r)"/><use xlink:href="#s" mask="url(#t)"/></g></g><path d="M92 48c6.629 0 12-5.371 12-12s-5.371-12-12-12-12 5.371-12 12 5.371 12 12 12zm0 0" fill="url(#u)"/><path d="M92 10a27.915 27.915 0 00-19.797 8.2c-10.937 10.937-10.937 28.663 0 39.6L92 77.599s13.82-13.82 19.8-19.797c10.934-10.938 10.934-28.664 0-39.602A27.93 27.93 0 0092 10zm0 18c5.523 0 10 4.477 10 10s-4.477 10-10 10-10-4.477-10-10 4.477-10 10-10zm0 0" fill="#a51d2d"/><path d="M92 8c-7.164 0-14.332 2.734-19.797 8.203-10.937 10.934-10.937 28.66 0 39.594L92 75.597l19.8-19.8c10.934-10.934 10.934-28.66 0-39.594A27.918 27.918 0 0092 8zm0 18c5.523 0 10 4.477 10 10s-4.477 10-10 10-10-4.477-10-10 4.477-10 10-10zm0 0" fill="url(#v)"/></g><g id="C" clip-path="url(#z)"><path d="M128 80.64V128H0V80.64zm0 0" fill="url(#A)"/><path d="M13.309 80.64L60.664 128H81.88l-47.36-47.36zm42.421 0L103.094 128h21.215L76.945 80.64zm42.43 0L128 110.48V89.27l-8.629-8.63zM0 88.548v21.215L18.238 128h21.215zm0 0"/></g><g id="y" clip-path="url(#w)" filter="url(#a)"><use xlink:href="#x"/></g><g id="S" clip-path="url(#B)"><use xlink:href="#C" mask="url(#D)"/></g><linearGradient id="v" gradientUnits="userSpaceOnUse" x1="336" y1="62" x2="336" y2="-180" gradientTransform="matrix(.25 0 0 .25 8 53)"><stop offset="0" stop-color="#d81d25"/><stop offset="1" stop-color="#f66151"/></linearGradient><linearGradient id="A" gradientUnits="userSpaceOnUse" x1="300" y1="235" x2="428" y2="235" gradientTransform="matrix(0 .37 -.98462 0 295.385 -30.36)"><stop offset="0" stop-color="#f9f06b"/><stop offset="1" stop-color="#f5c211"/></linearGradient><linearGradient id="u" gradientUnits="userSpaceOnUse" x1="320.5" y1="-68" x2="415.5" y2="-68" gradientTransform="matrix(0 .25263 .25263 0 109.18 -56.968)"><stop offset="0" stop-color="#a51d2d"/><stop offset="1" stop-color="#ce1921"/></linearGradient><linearGradient id="Q" gradientUnits="userSpaceOnUse" x1="320.5" y1="-68" x2="415.5" y2="-68" gradientTransform="matrix(0 .25263 .25263 0 109.18 -56.968)"><stop offset="0" stop-color="#a51d2d"/><stop offset="1" stop-color="#ce1921"/></linearGradient><linearGradient id="R" gradientUnits="userSpaceOnUse" x1="336" y1="62" x2="336" y2="-180" gradientTransform="matrix(.25 0 0 .25 8 53)"><stop offset="0" stop-color="#d81d25"/><stop offset="1" stop-color="#f66151"/></linearGradient><filter id="a" filterUnits="objectBoundingBox" x="0%" y="0%" width="100%" height="100%"><feColorMatrix in="SourceGraphic" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0"/></filter></defs><g clip-path="url(#E)"><g clip-path="url(#F)"><path d="M64 96h40V28h24v96H64l-16-16-16 16H0V96h32l16-16zm0 0" fill="#cdab8f"/></g></g><g clip-path="url(#G)"><g clip-path="url(#H)"><path d="M0 95.992l32 .016V28H0zm0 0" fill="#55a7eb"/></g></g><g clip-path="url(#I)"><g clip-path="url(#J)"><path d="M36 24v68l12-12 16 16.016h36L104 92V28H64L48 12zm0 0" fill="#2ec27e"/></g></g><g clip-path="url(#K)"><g clip-path="url(#L)"><path d="M99.973 28.027v59.5c0 2.508-2.34 4.489-4.235 4.489H64L48 76.148l-9.844 9.883L38 12.016h-6V92l-32 .016v8.023h31.75L48 84l16 16.04h31.738c6.606 0 11.989-5.513 12.262-12.513v-59.5zm0 0" fill="#fff"/></g></g><g clip-path="url(#M)"><g clip-path="url(#N)"><use xlink:href="#q" mask="url(#O)"/><use xlink:href="#s" mask="url(#P)"/></g></g><path d="M92 48c6.629 0 12-5.371 12-12s-5.371-12-12-12-12 5.371-12 12 5.371 12 12 12zm0 0" fill="url(#Q)"/><path d="M92 10a27.915 27.915 0 00-19.797 8.2c-10.937 10.937-10.937 28.663 0 39.6L92 77.599s13.82-13.82 19.8-19.797c10.934-10.938 10.934-28.664 0-39.602A27.93 27.93 0 0092 10zm0 18c5.523 0 10 4.477 10 10s-4.477 10-10 10-10-4.477-10-10 4.477-10 10-10zm0 0" fill="#a51d2d"/><path d="M92 8c-7.164 0-14.332 2.734-19.797 8.203-10.937 10.934-10.937 28.66 0 39.594L92 75.597l19.8-19.8c10.934-10.934 10.934-28.66 0-39.594A27.918 27.918 0 0092 8zm0 18c5.523 0 10 4.477 10 10s-4.477 10-10 10-10-4.477-10-10 4.477-10 10-10zm0 0" fill="url(#R)"/><use xlink:href="#S" mask="url(#T)"/></svg>
4141 </screenshot>
4242 </screenshots>
4343 <releases>
44 <release date="2019-10-07" version="3.34.1">
45 <description>
46 <ul>
47 <li>Update tile size to 512 px when using --local option</li>
48 </ul>
49 </description>
50 </release>
5144 <release date="2019-09-09" version="3.34.0">
5245 <description>
5346 <ul>
270270 <child>
271271 <object class="GtkStack" id="linkButtonStack">
272272 <child>
273 <object class="GtkLinkButton" id="graphHopperLinkButton">
273 <object class="GtkLinkButton">
274274 <property name="label" translatable="yes">Route search by GraphHopper</property>
275275 <property name="visible">True</property>
276276 <property name="can_focus">True</property>
283283 </style>
284284 </object>
285285 <packing>
286 <property name="name">graphHopper</property>
286 <property name="name">turnByTurn</property>
287287 </packing>
288288 </child>
289289 <child>
291291 <property name="visible">True</property>
292292 <property name="halign">GTK_ALIGN_END</property>
293293 <child>
294 <object class="GtkLinkButton" id="openTripPlannerLinkButton">
295 <property name="label" translatable="yes">Route search by OpenTripPlanner</property>
294 <object class="GtkLabel" id="transitAttributionLabel">
296295 <property name="visible">True</property>
297296 <property name="can_focus">True</property>
298297 <property name="receives_default">True</property>
299 <property name="use_action_appearance">False</property>
300 <property name="relief">none</property>
301 <!-- opentripplanner.org uses an SSL cert only valid for github
302 domains... -->
303 <property name="uri">http://www.opentripplanner.org</property>
298 <property name="use_markup">True</property>
304299 <style>
305300 <class name="small-label"/>
306301 </style>
313308 <child>
314309 <object class="GtkMenuButton">
315310 <property name="visible">True</property>
316 <property name="popover">openTripPlannerDisclaimerPopover</property>
311 <property name="popover">transitDisclaimerPopover</property>
317312 <property name="halign">GTK_ALIGN_END</property>
318313 <property name="margin-top">5</property>
319314 <property name="margin-bottom">5</property>
345340 </child>
346341 </object>
347342 <packing>
348 <property name="name">openTripPlanner</property>
343 <property name="name">transit</property>
349344 </packing>
350345 </child>
351346 </object>
353348 </object>
354349 </child>
355350 </template>
356 <object class="GtkPopover" id="openTripPlannerDisclaimerPopover">
351 <object class="GtkPopover" id="transitDisclaimerPopover">
357352 <property name="visible">False</property>
358353 <child>
359354 <object class="GtkGrid">
365360 <property name="margin-bottom">5</property>
366361 <property name="margin-start">5</property>
367362 <property name="margin-end">5</property>
368 <property name="label" translatable="yes">Routing itineraries for public transit is provided by GNOME
369 using timetable data obtained from transit companies or agencies.
370 The companies and agencies can not be held responsible for the results shown.
363 <property name="label" translatable="yes">Routing itineraries for public transit is provided by third-party
364 services.
371365 GNOME can not guarantee correctness of the itineraries and schedules shown.
366 Note that some providers might not include all available modes of transportation,
367 e.g. a national provider might not include airlines, and a local provider could
368 miss regional trains.
372369 Names and brands shown are to be considered as registered trademarks when applicable.</property>
373370 </object>
374371 </child>
144144 <property name="label" translatable="yes">Ferries</property>
145145 </object>
146146 </child>
147 <child>
148 <object class="GtkCheckButton" id="airplaneCheckButton">
149 <property name="visible">True</property>
150 <property name="active">True</property>
151 <property name="label" translatable="yes">Airplanes</property>
152 </object>
153 </child>
147154 </object>
148155 </child>
149156 </object>
2020
2121 <maintainer>
2222 <foaf:Person>
23 <foaf:name>Jonas Danielsson</foaf:name>
24 <foaf:mbox rdf:resource="mailto:jonas@threetimestwo.org" />
25 <gnome:userid>jonasdn</gnome:userid>
26 </foaf:Person>
27 </maintainer>
28 <maintainer>
29 <foaf:Person>
30 <foaf:name>Amisha Singla</foaf:name>
31 <foaf:mbox rdf:resource="mailto:amishas157@gmail.com" />
32 <gnome:userid>amishasingla</gnome:userid>
33 </foaf:Person>
34 </maintainer>
35 <maintainer>
36 <foaf:Person>
3723 <foaf:name>Marcus Lundblad</foaf:name>
3824 <foaf:mbox rdf:resource="mailto:ml@update.uu.se" />
3925 <gnome:userid>mlundblad</gnome:userid>
00 project('gnome-maps', 'c',
1 version: '3.34.1',
1 version: '3.35.1',
22 license: 'GPL2+'
33 )
44
3838 src/layersPopover.js
3939 src/mainWindow.js
4040 src/mapView.js
41 src/openTripPlanner.js
4241 src/osmConnection.js
4342 src/osmEditDialog.js
4443 src/photonParser.js
5857 src/transitPlan.js
5958 src/translations.js
6059 src/utils.js
60 src/transitplugins/openTripPlanner.js
66 msgstr ""
77 "Project-Id-Version: gnome-maps\n"
88 "Report-Msgid-Bugs-To: https://gitlab.gnome.org/GNOME/gnome-maps/issues\n"
9 "POT-Creation-Date: 2019-09-01 23:55+0000\n"
10 "PO-Revision-Date: 2019-09-02 02:04+0200\n"
9 "POT-Creation-Date: 2019-10-30 21:01+0000\n"
10 "PO-Revision-Date: 2019-11-02 09:41+0100\n"
1111 "Last-Translator: Marek Černocký <marek@manet.cz>\n"
1212 "Language-Team: čeština <gnome-cs-list@gnome.org>\n"
1313 "Language: cs\n"
5555 "Můžete také hledat určité typy míst, jako „restaurace poblíž ulice Kobližná, "
5656 "Brno“ nebo „hotely poblíž Alexanderplatz, Berlín“."
5757
58 #: data/org.gnome.Maps.appdata.xml.in:59
58 #: data/org.gnome.Maps.appdata.xml.in:92
5959 msgid "The GNOME Project"
6060 msgstr "Projekt GNOME"
6161
640640 msgid "Route search by GraphHopper"
641641 msgstr "Hledání trasy pomocí GraphHopper"
642642
643 #: data/ui/sidebar.ui:296
644 msgid "Route search by OpenTripPlanner"
645 msgstr "Hledání trasy pomocí OpenTripPlanner"
646
647 #: data/ui/sidebar.ui:369
648 msgid ""
649 "Routing itineraries for public transit is provided by GNOME\n"
650 "using timetable data obtained from transit companies or agencies.\n"
651 "The companies and agencies can not be held responsible for the results "
652 "shown.\n"
643 #: data/ui/sidebar.ui:364
644 msgid ""
645 "Routing itineraries for public transit is provided by third-party\n"
646 "services.\n"
653647 "GNOME can not guarantee correctness of the itineraries and schedules shown.\n"
648 "Note that some providers might not include all available modes of "
649 "transportation,\n"
650 "e.g. a national provider might not include airlines, and a local provider "
651 "could\n"
652 "miss regional trains.\n"
654653 "Names and brands shown are to be considered as registered trademarks when "
655654 "applicable."
656655 msgstr ""
657 "Jízdní řády pro veřejnou dopravu jsou poskytovány projektem GNOME\n"
658 "díky datům získaným od přepravních společností nebo organizací.\n"
659 "Tyto společnosti a organizace nenesou žádnou zodpovědnost za zobrazené\n"
660 "výsledky. GNOME nemůže ručit za správnost cestovních rozvrhů a plánů.\n"
661 "Kde je to možné, lze názvy a značky považovat za registrované známky."
656 "Jízdní řády pro veřejnou dopravu jsou poskytovány službami třetích stran.\n"
657 "GNOME nemůže nijak zaručit správnost cestovních rozvrhů a plánů.\n"
658 "Může se stát, že poskytovatel nenabízí všechny dostupné způsoby dopravy, "
659 "například národní\n"
660 "poskytovatel nebude nabízet mezinárodní leteckou dopravu a u místního "
661 "poskytovatele mohou\n"
662 "scházet celostátní vlaky apod.\n"
663 "Zobrazené názvy a značky považujte za registrované ochranné známky, všude "
664 "tam, kde tomu tak má být."
662665
663666 #: data/ui/social-place-more-results-row.ui:8
664667 msgid "Show more results"
714717 msgid "Ferries"
715718 msgstr "trajekty"
716719
720 #: data/ui/transit-options-panel.ui:152
721 msgid "Airplanes"
722 msgstr "letadla"
723
717724 #: data/ui/user-location-bubble.ui:13 src/geoclue.js:118
718725 msgid "Current location"
719726 msgstr "Současné místo"
756763 msgid "Show the version of the program"
757764 msgstr "Zobrazit verzi programu"
758765
766 #: src/application.js:104
767 msgid "Ignore network availability"
768 msgstr "Ignorovat dostupnost sítě"
769
759770 #: src/checkInDialog.js:167
760771 msgid "Select an account"
761772 msgstr "Výběr účtu"
886897 msgid "unknown geometry"
887898 msgstr "neznámé geometrické údaje"
888899
889 #: src/graphHopper.js:112 src/openTripPlanner.js:652
900 #: src/graphHopper.js:112 src/transitPlan.js:192
890901 msgid "Route request failed."
891902 msgstr "Požadavek na vyhledání cesty selhal."
892903
893 #: src/graphHopper.js:119 src/openTripPlanner.js:615
904 #: src/graphHopper.js:119 src/transitPlan.js:184
894905 msgid "No route found."
895906 msgstr "Nenalezena žádná cesta."
896907
897 #: src/graphHopper.js:207
908 #: src/graphHopper.js:207 src/transitplugins/openTripPlanner.js:1100
898909 msgid "Start!"
899910 msgstr "Start!"
900911
947958 msgid "Search provided by %s using %s"
948959 msgstr "Hledání zprostředkuje %s pomocí %s"
949960
950 #: src/mapView.js:374
961 #: src/mapView.js:375
951962 msgid "File type is not supported"
952963 msgstr "Typ souboru není podporován"
953964
954 #: src/mapView.js:381
965 #: src/mapView.js:382
955966 msgid "Failed to open layer"
956967 msgstr "Selhalo otevření vrstvy"
957968
958 #: src/mapView.js:417
969 #: src/mapView.js:418
959970 msgid "Failed to open GeoURI"
960971 msgstr "Selhalo otevření adresy GeoURI"
961
962 #: src/openTripPlanner.js:648
963 msgid "No timetable data found for this route."
964 msgstr "Pro tuto trasu nebyly nalezeny žádné časové údaje."
965972
966973 #. setting the status in session.cancel_message still seems
967974 #. to always give status IO_ERROR
13761383 msgstr "selhalo načtení souboru"
13771384
13781385 #. Translators: %s is a time expression with the format "%f h" or "%f min"
1379 #: src/sidebar.js:293
1386 #: src/sidebar.js:296
13801387 #, javascript-format
13811388 msgid "Estimated time: %s"
13821389 msgstr "Odhadovaný čas: %s"
1390
1391 #: src/sidebar.js:352
1392 #, javascript-format
1393 msgid "Itineraries provided by %s"
1394 msgstr "Plán cesty poskytl %s"
13831395
13841396 #. Translators: this is a format string indicating instructions
13851397 #. * starting a journey at the address given as the parameter
14131425 msgid "Arrive at %s"
14141426 msgstr "Příjezd do %s"
14151427
1416 #: src/transit.js:77
1428 #: src/transit.js:77 src/transitplugins/openTripPlanner.js:1113
14171429 msgid "Arrive"
14181430 msgstr "Příjezd"
14191431
14451457 #. * Translators: this is a format string giving the equivalent to
14461458 #. * "may 29" according to the current locale's convensions.
14471459 #.
1448 #: src/transitOptionsPanel.js:141
1460 #: src/transitOptionsPanel.js:143
14491461 msgctxt "month-day-date"
14501462 msgid "%b %e"
14511463 msgstr "%-d. %B"
1464
1465 #: src/transitPlan.js:188
1466 msgid "No timetable data found for this route."
1467 msgstr "Pro tuto trasu nebyly nalezeny žádné časové údaje."
1468
1469 #: src/transitPlan.js:196
1470 msgid "No provider found for this route."
1471 msgstr "Pro tuto trasu nebyl nalezen žádný poskytovatel údajů."
14521472
14531473 #. Translators: this is a format string for showing a departure and
14541474 #. * arrival time, like:
14551475 #. * "12:00 – 13:03" where the placeholder %s are the actual times,
14561476 #. * these could be rearranged if needed.
14571477 #.
1458 #: src/transitPlan.js:254
1478 #: src/transitPlan.js:313
14591479 #, javascript-format
14601480 msgid "%s – %s"
14611481 msgstr "%s – %s"
14641484 #. * less than an hour, with only the minutes part, using plural forms
14651485 #. * as appropriate
14661486 #.
1467 #: src/transitPlan.js:281
1487 #: src/transitPlan.js:340
14681488 #, javascript-format
14691489 msgid "%d minute"
14701490 msgid_plural "%d minutes"
14761496 #. * where the duration is an exact number of hours (i.e. no
14771497 #. * minutes part), using plural forms as appropriate
14781498 #.
1479 #: src/transitPlan.js:292
1499 #: src/transitPlan.js:351
14801500 #, javascript-format
14811501 msgid "%d hour"
14821502 msgid_plural "%d hours"
14881508 #. * where the duration contains an hour and minute part, it's
14891509 #. * pluralized on the hours part
14901510 #.
1491 #: src/transitPlan.js:298
1511 #: src/transitPlan.js:357
14921512 #, javascript-format
14931513 msgid "%d:%02d hour"
14941514 msgid_plural "%d:%02d hours"
15021522 #. * "12:00–13:03" where the placeholder %s are the actual times,
15031523 #. * these could be rearranged if needed.
15041524 #.
1505 #: src/transitPlan.js:651
1525 #: src/transitPlan.js:750
15061526 #, javascript-format
15071527 msgid "%s–%s"
15081528 msgstr "%s – %s"
16561676 msgstr "služba"
16571677
16581678 #. Translators: Accuracy of user location information
1659 #: src/utils.js:220
1679 #: src/utils.js:229
16601680 msgid "Unknown"
16611681 msgstr "neznámá"
16621682
16631683 #. Translators: Accuracy of user location information
1664 #: src/utils.js:223
1684 #: src/utils.js:232
16651685 msgid "Exact"
16661686 msgstr "velmi přesná"
16671687
1668 #: src/utils.js:281
1688 #: src/utils.js:290
16691689 #, javascript-format
16701690 msgid "%f h"
16711691 msgstr "%f h"
16721692
1673 #: src/utils.js:283
1693 #: src/utils.js:292
16741694 #, javascript-format
16751695 msgid "%f min"
16761696 msgstr "%f min"
16771697
1678 #: src/utils.js:285
1698 #: src/utils.js:294
16791699 #, javascript-format
16801700 msgid "%f s"
16811701 msgstr "%f s"
16821702
16831703 #. Translators: This is a distance measured in kilometers
1684 #: src/utils.js:296
1704 #: src/utils.js:305
16851705 #, javascript-format
16861706 msgid "%s km"
16871707 msgstr "%s km"
16881708
16891709 #. Translators: This is a distance measured in meters
1690 #: src/utils.js:299
1710 #: src/utils.js:308
16911711 #, javascript-format
16921712 msgid "%s m"
16931713 msgstr "%s m"
16941714
16951715 #. Translators: This is a distance measured in miles
1696 #: src/utils.js:307
1716 #: src/utils.js:316
16971717 #, javascript-format
16981718 msgid "%s mi"
16991719 msgstr "%s mi"
17001720
17011721 #. Translators: This is a distance measured in feet
1702 #: src/utils.js:310
1722 #: src/utils.js:319
17031723 #, javascript-format
17041724 msgid "%s ft"
17051725 msgstr "%s ft"
1726
1727 #: src/transitplugins/openTripPlanner.js:1174
1728 #, javascript-format
1729 msgid "Continue on %s"
1730 msgstr "Pokračujte na %s"
1731
1732 #: src/transitplugins/openTripPlanner.js:1176
1733 msgid "Continue"
1734 msgstr "Pokračujte"
1735
1736 #: src/transitplugins/openTripPlanner.js:1179
1737 #, javascript-format
1738 msgid "Turn left on %s"
1739 msgstr "Zahněte doleva na %s"
1740
1741 #: src/transitplugins/openTripPlanner.js:1181
1742 msgid "Turn left"
1743 msgstr "Zahněte doleva"
1744
1745 #: src/transitplugins/openTripPlanner.js:1184
1746 #, javascript-format
1747 msgid "Turn slightly left on %s"
1748 msgstr "Zahněte mírně doleva na %s"
1749
1750 #: src/transitplugins/openTripPlanner.js:1186
1751 msgid "Turn slightly left"
1752 msgstr "Zahněte mírně doleva"
1753
1754 #: src/transitplugins/openTripPlanner.js:1189
1755 #, javascript-format
1756 msgid "Turn sharp left on %s"
1757 msgstr "Zahněte ostře doleva na %s"
1758
1759 #: src/transitplugins/openTripPlanner.js:1191
1760 msgid "Turn sharp left"
1761 msgstr "Zahněte ostře doleva"
1762
1763 #: src/transitplugins/openTripPlanner.js:1194
1764 #, javascript-format
1765 msgid "Turn right on %s"
1766 msgstr "Zahněte doprava na %s"
1767
1768 #: src/transitplugins/openTripPlanner.js:1196
1769 msgid "Turn right"
1770 msgstr "Zahněte doprava"
1771
1772 #: src/transitplugins/openTripPlanner.js:1199
1773 #, javascript-format
1774 msgid "Turn slightly right on %s"
1775 msgstr "Zahněte mírně doprava na %s"
1776
1777 #: src/transitplugins/openTripPlanner.js:1201
1778 msgid "Turn slightly right"
1779 msgstr "Zahněte mírně doprava"
1780
1781 #: src/transitplugins/openTripPlanner.js:1204
1782 #, javascript-format
1783 msgid "Turn sharp right on %s"
1784 msgstr "Zahněte ostře doprava na %s"
1785
1786 #: src/transitplugins/openTripPlanner.js:1206
1787 msgid "Turn sharp right"
1788 msgstr "Zahněte ostře doprava"
1789
1790 #: src/transitplugins/openTripPlanner.js:1212
1791 #, javascript-format
1792 msgid "In the roundaboat, take exit %s"
1793 msgstr "Na kruhovém objezdu použijte výjezd %s"
1794
1795 #: src/transitplugins/openTripPlanner.js:1214
1796 #, javascript-format
1797 msgid "In the roundabout, take exit to %s"
1798 msgstr "Na kruhovém objezdu použijte výjezd směr %s"
1799
1800 #: src/transitplugins/openTripPlanner.js:1216
1801 msgid "Take the roundabout"
1802 msgstr "Najeďte na kruhový objezd"
1803
1804 #: src/transitplugins/openTripPlanner.js:1220
1805 #, javascript-format
1806 msgid "Take the elevator and get off at %s"
1807 msgstr "Použijte výtah a vystupte v %s"
1808
1809 #: src/transitplugins/openTripPlanner.js:1222
1810 msgid "Take the elevator"
1811 msgstr "Použijte výtah"
1812
1813 #: src/transitplugins/openTripPlanner.js:1226
1814 #, javascript-format
1815 msgid "Make a left u-turn onto %s"
1816 msgstr "Proveďte otočku doleva na %s"
1817
1818 #: src/transitplugins/openTripPlanner.js:1228
1819 msgid "Make a left u-turn"
1820 msgstr "Proveďte otočku doleva"
1821
1822 #: src/transitplugins/openTripPlanner.js:1231
1823 #, javascript-format
1824 msgid "Make a right u-turn onto %s"
1825 msgstr "Proveďte otočku doprava na %s"
1826
1827 #: src/transitplugins/openTripPlanner.js:1233
1828 msgid "Make a rigth u-turn"
1829 msgstr "Proveďte otočku doprava"
77 msgstr ""
88 "Project-Id-Version: gnome-maps master\n"
99 "Report-Msgid-Bugs-To: https://gitlab.gnome.org/GNOME/gnome-maps/issues\n"
10 "POT-Creation-Date: 2019-08-02 06:11+0000\n"
11 "PO-Revision-Date: 2019-08-02 09:04+0200\n"
10 "POT-Creation-Date: 2019-10-30 21:01+0000\n"
11 "PO-Revision-Date: 2019-11-05 15:07+0100\n"
1212 "Last-Translator: Daniel Mustieles <daniel.mustieles@gmail.com>\n"
1313 "Language-Team: Spanish - Spain <gnome-es-list@gnome.org>\n"
1414 "Language: es_ES\n"
1616 "Content-Type: text/plain; charset=UTF-8\n"
1717 "Content-Transfer-Encoding: 8bit\n"
1818 "Plural-Forms: nplurals=2; plural=(n != 1);\n"
19 "X-Generator: Gtranslator 3.32.1\n"
19 "X-Generator: Gtranslator 3.34.0\n"
2020
2121 #: data/org.gnome.Maps.appdata.xml.in:6
2222 msgid "GNOME Maps"
5555 "También puede buscar por tipos de ubicaciones específicos como «Bares cerca "
5656 "de la Gran Vía, Madrid» u «Hoteles cerca de la Plaza Mayor, Madrid»."
5757
58 #: data/org.gnome.Maps.appdata.xml.in:59
58 #: data/org.gnome.Maps.appdata.xml.in:92
5959 msgid "The GNOME Project"
6060 msgstr "El Proyecto GNOME"
6161
6868 #.
6969 #. Translators: This is the program name.
7070 #: data/org.gnome.Maps.desktop.in:4 data/ui/main-window.ui:26
71 #: src/application.js:81 src/mainWindow.js:139 src/mainWindow.js:509
71 #: src/application.js:81 src/mainWindow.js:139 src/mainWindow.js:513
7272 msgid "Maps"
7373 msgstr "Mapas"
7474
102102 msgstr "Nivel de ampliación"
103103
104104 #: data/org.gnome.Maps.gschema.xml:21
105 #| msgctxt "shortcut window"
106 #| msgid "Map View"
107105 msgid "Map type"
108106 msgstr "Tipo de mapa"
109107
641639 msgid "Route search by GraphHopper"
642640 msgstr "Ruta buscada por GraphHopper"
643641
644 #: data/ui/sidebar.ui:296
645 msgid "Route search by OpenTripPlanner"
646 msgstr "Ruta buscada por OpenTripPlanner"
647
648 #: data/ui/sidebar.ui:369
649 msgid ""
650 "Routing itineraries for public transit is provided by GNOME\n"
651 "using timetable data obtained from transit companies or agencies.\n"
652 "The companies and agencies can not be held responsible for the results "
653 "shown.\n"
642 #: data/ui/sidebar.ui:364
643 msgid ""
644 "Routing itineraries for public transit is provided by third-party\n"
645 "services.\n"
654646 "GNOME can not guarantee correctness of the itineraries and schedules shown.\n"
647 "Note that some providers might not include all available modes of "
648 "transportation,\n"
649 "e.g. a national provider might not include airlines, and a local provider "
650 "could\n"
651 "miss regional trains.\n"
655652 "Names and brands shown are to be considered as registered trademarks when "
656653 "applicable."
657654 msgstr ""
658 "Los itinerarios de las rutas para viajes públicos los proporciona GNOME\n"
659 "usando datos de tablas de tiempos obtenidos de compañías o agencias de "
660 "viajes.\n"
661 "Las compañías y agencias no son responsables de los resultados mostrados.\n"
655 "Los itinerarios de las rutas para viajes públicos los proporciona\n"
656 "un servicio de terceros.\n"
662657 "GNOME no puede garantizar la corrección de los itinerarios y los tiempos "
663658 "mostrados.\n"
664 "Los nombres y las marcas se consideran marcas registradas cuando sea "
665 "aplicable."
659 "Algunos proveedores pueden no incluir todos mos medios de transporte "
660 "disponibles,\n"
661 "ej. un proveedor nacional puede no incluir aerolíneas y un proveedor local "
662 "puede\n"
663 "no incluir trenes regionales.\n"
664 "Los nombres y las marcas mostradas se consideran marcas registradas cuando "
665 "sea aplicable."
666666
667667 #: data/ui/social-place-more-results-row.ui:8
668668 msgid "Show more results"
718718 msgid "Ferries"
719719 msgstr "Ferris"
720720
721 #: data/ui/transit-options-panel.ui:152
722 msgid "Airplanes"
723 msgstr "Aviones"
724
721725 #: data/ui/user-location-bubble.ui:13 src/geoclue.js:118
722726 msgid "Current location"
723727 msgstr "Ubicación actual"
760764 msgid "Show the version of the program"
761765 msgstr "Mostrar la versión del programa"
762766
767 #: src/application.js:104
768 msgid "Ignore network availability"
769 msgstr "Ignorar la disponibilidad de la red"
770
763771 #: src/checkInDialog.js:167
764772 msgid "Select an account"
765773 msgstr "Seleccionar una cuenta"
890898 msgid "unknown geometry"
891899 msgstr "geometría desconocida"
892900
893 #: src/graphHopper.js:112 src/openTripPlanner.js:652
901 #: src/graphHopper.js:112 src/transitPlan.js:192
894902 msgid "Route request failed."
895903 msgstr "Falló al solicitar la ruta."
896904
897 #: src/graphHopper.js:119 src/openTripPlanner.js:615
905 #: src/graphHopper.js:119 src/transitPlan.js:184
898906 msgid "No route found."
899907 msgstr "No se ha encontrado la ruta."
900908
901 #: src/graphHopper.js:207
909 #: src/graphHopper.js:207 src/transitplugins/openTripPlanner.js:1100
902910 msgid "Start!"
903911 msgstr "Empezar"
904912
906914 msgid "All Layer Files"
907915 msgstr "Todos los archivos de capas"
908916
909 #: src/mainWindow.js:442
917 #: src/mainWindow.js:446
910918 msgid "Failed to connect to location service"
911919 msgstr "Falló al conectar al servicio de ubicación"
912920
913 #: src/mainWindow.js:507
921 #: src/mainWindow.js:511
914922 msgid "translator-credits"
915923 msgstr "Daniel Mustieles <daniel.mustieles@gmail.com>, 2013"
916924
917 #: src/mainWindow.js:510
925 #: src/mainWindow.js:514
918926 msgid "A map application for GNOME"
919927 msgstr "Una aplicación de mapas para GNOME"
920928
921 #: src/mainWindow.js:521
929 #: src/mainWindow.js:525
922930 msgid "Copyright © 2011 – 2019 Red Hat, Inc. and The GNOME Maps authors"
923931 msgstr "Copyright © 2011 – 2019 Red Hat, Inc. y los autores de Mapas de GNOME"
924932
925 #: src/mainWindow.js:541
933 #: src/mainWindow.js:545
926934 #, javascript-format
927935 msgid "Map data by %s and contributors"
928936 msgstr "Datos de mapas por %s y colaboradores"
932940 #. * the bare name of the tile provider, or a linkified URL if one
933941 #. * is available
934942 #.
935 #: src/mainWindow.js:557
943 #: src/mainWindow.js:561
936944 #, javascript-format
937945 msgid "Map tiles provided by %s"
938946 msgstr "Cuadrículas de mapas proporcionadas por %s"
946954 #. * (i.e. "%2$s ... %1$s ..." for positioning the project URL
947955 #. * before the provider).
948956 #.
949 #: src/mainWindow.js:586
957 #: src/mainWindow.js:590
950958 #, javascript-format
951959 msgid "Search provided by %s using %s"
952960 msgstr "Búsqueda proporcionada por %s usando %s"
953961
954 #: src/mapView.js:374
962 #: src/mapView.js:375
955963 msgid "File type is not supported"
956964 msgstr "Tipo de archivo no soportado"
957965
958 #: src/mapView.js:381
966 #: src/mapView.js:382
959967 msgid "Failed to open layer"
960968 msgstr "Falló al abrir la capa"
961969
962 #: src/mapView.js:417
970 #: src/mapView.js:418
963971 msgid "Failed to open GeoURI"
964972 msgstr "Falló al abrir el GeoURI"
965
966 #: src/openTripPlanner.js:648
967 msgid "No timetable data found for this route."
968 msgstr "No hay datos en la tabla de tiempos para esta ruta."
969973
970974 #. setting the status in session.cancel_message still seems
971975 #. to always give status IO_ERROR
12681272 msgid "Phone:"
12691273 msgstr "Teléfono:"
12701274
1271 #: src/placeEntry.js:205
1275 #: src/placeEntry.js:209
12721276 msgid "Failed to parse Geo URI"
12731277 msgstr "Falló al analizar el URI de Geo"
12741278
13811385 msgstr "falló al cargar el archivo"
13821386
13831387 #. Translators: %s is a time expression with the format "%f h" or "%f min"
1384 #: src/sidebar.js:293
1388 #: src/sidebar.js:296
13851389 #, javascript-format
13861390 msgid "Estimated time: %s"
13871391 msgstr "Tiempo estimado: %s"
1392
1393 #: src/sidebar.js:352
1394 #, javascript-format
1395 msgid "Itineraries provided by %s"
1396 msgstr "Itinerarios proporcionados por %s"
13881397
13891398 #. Translators: this is a format string indicating instructions
13901399 #. * starting a journey at the address given as the parameter
14181427 msgid "Arrive at %s"
14191428 msgstr "Llegar a %s"
14201429
1421 #: src/transit.js:77
1430 #: src/transit.js:77 src/transitplugins/openTripPlanner.js:1113
14221431 msgid "Arrive"
14231432 msgstr "Llegar"
14241433
14501459 #. * Translators: this is a format string giving the equivalent to
14511460 #. * "may 29" according to the current locale's convensions.
14521461 #.
1453 #: src/transitOptionsPanel.js:141
1462 #: src/transitOptionsPanel.js:143
14541463 msgctxt "month-day-date"
14551464 msgid "%b %e"
14561465 msgstr "%b %e"
1466
1467 #: src/transitPlan.js:188
1468 msgid "No timetable data found for this route."
1469 msgstr "No hay datos en la tabla de tiempos para esta ruta."
1470
1471 #: src/transitPlan.js:196
1472 msgid "No provider found for this route."
1473 msgstr "No se ha encontrado un proveedor para esta ruta."
14571474
14581475 #. Translators: this is a format string for showing a departure and
14591476 #. * arrival time, like:
14601477 #. * "12:00 – 13:03" where the placeholder %s are the actual times,
14611478 #. * these could be rearranged if needed.
14621479 #.
1463 #: src/transitPlan.js:254
1480 #: src/transitPlan.js:313
14641481 #, javascript-format
14651482 msgid "%s – %s"
14661483 msgstr "%s – %s"
14691486 #. * less than an hour, with only the minutes part, using plural forms
14701487 #. * as appropriate
14711488 #.
1472 #: src/transitPlan.js:281
1489 #: src/transitPlan.js:340
14731490 #, javascript-format
14741491 msgid "%d minute"
14751492 msgid_plural "%d minutes"
14801497 #. * where the duration is an exact number of hours (i.e. no
14811498 #. * minutes part), using plural forms as appropriate
14821499 #.
1483 #: src/transitPlan.js:292
1500 #: src/transitPlan.js:351
14841501 #, javascript-format
14851502 msgid "%d hour"
14861503 msgid_plural "%d hours"
14911508 #. * where the duration contains an hour and minute part, it's
14921509 #. * pluralized on the hours part
14931510 #.
1494 #: src/transitPlan.js:298
1511 #: src/transitPlan.js:357
14951512 #, javascript-format
14961513 msgid "%d:%02d hour"
14971514 msgid_plural "%d:%02d hours"
15041521 #. * "12:00–13:03" where the placeholder %s are the actual times,
15051522 #. * these could be rearranged if needed.
15061523 #.
1507 #: src/transitPlan.js:651
1524 #: src/transitPlan.js:750
15081525 #, javascript-format
15091526 msgid "%s–%s"
15101527 msgstr "%s–%s"
16581675 msgstr "servicio"
16591676
16601677 #. Translators: Accuracy of user location information
1661 #: src/utils.js:220
1678 #: src/utils.js:229
16621679 msgid "Unknown"
16631680 msgstr "Desconocida"
16641681
16651682 #. Translators: Accuracy of user location information
1666 #: src/utils.js:223
1683 #: src/utils.js:232
16671684 msgid "Exact"
16681685 msgstr "Exacta"
16691686
1670 #: src/utils.js:281
1687 #: src/utils.js:290
16711688 #, javascript-format
16721689 msgid "%f h"
16731690 msgstr "%f h"
16741691
1675 #: src/utils.js:283
1692 #: src/utils.js:292
16761693 #, javascript-format
16771694 msgid "%f min"
16781695 msgstr "%f min"
16791696
1680 #: src/utils.js:285
1697 #: src/utils.js:294
16811698 #, javascript-format
16821699 msgid "%f s"
16831700 msgstr "%f s"
16841701
16851702 #. Translators: This is a distance measured in kilometers
1686 #: src/utils.js:296
1703 #: src/utils.js:305
16871704 #, javascript-format
16881705 msgid "%s km"
16891706 msgstr "%s km"
16901707
16911708 #. Translators: This is a distance measured in meters
1692 #: src/utils.js:299
1709 #: src/utils.js:308
16931710 #, javascript-format
16941711 msgid "%s m"
16951712 msgstr "%s m"
16961713
16971714 #. Translators: This is a distance measured in miles
1698 #: src/utils.js:307
1715 #: src/utils.js:316
16991716 #, javascript-format
17001717 msgid "%s mi"
17011718 msgstr "%s mi"
17021719
17031720 #. Translators: This is a distance measured in feet
1704 #: src/utils.js:310
1721 #: src/utils.js:319
17051722 #, javascript-format
17061723 msgid "%s ft"
17071724 msgstr "%s ft"
1725
1726 #: src/transitplugins/openTripPlanner.js:1174
1727 #, javascript-format
1728 msgid "Continue on %s"
1729 msgstr "Continúe por %s"
1730
1731 #: src/transitplugins/openTripPlanner.js:1176
1732 msgid "Continue"
1733 msgstr "Continúe"
1734
1735 #: src/transitplugins/openTripPlanner.js:1179
1736 #, javascript-format
1737 msgid "Turn left on %s"
1738 msgstr "Gire a la izquierda en %s"
1739
1740 #: src/transitplugins/openTripPlanner.js:1181
1741 msgid "Turn left"
1742 msgstr "Gire a la izquierda"
1743
1744 #: src/transitplugins/openTripPlanner.js:1184
1745 #, javascript-format
1746 msgid "Turn slightly left on %s"
1747 msgstr "Gire levemente a la izquierda en %s"
1748
1749 #: src/transitplugins/openTripPlanner.js:1186
1750 msgid "Turn slightly left"
1751 msgstr "Gire levemente a la izquierda"
1752
1753 #: src/transitplugins/openTripPlanner.js:1189
1754 #, javascript-format
1755 msgid "Turn sharp left on %s"
1756 msgstr "Gire bruscamente a la izquierda en %s"
1757
1758 #: src/transitplugins/openTripPlanner.js:1191
1759 msgid "Turn sharp left"
1760 msgstr "Gire bruscamente a la izquierda"
1761
1762 #: src/transitplugins/openTripPlanner.js:1194
1763 #, javascript-format
1764 msgid "Turn right on %s"
1765 msgstr "Gire a la derecha en %s"
1766
1767 #: src/transitplugins/openTripPlanner.js:1196
1768 msgid "Turn right"
1769 msgstr "Gire a la derecha"
1770
1771 #: src/transitplugins/openTripPlanner.js:1199
1772 #, javascript-format
1773 msgid "Turn slightly right on %s"
1774 msgstr "Gire levemente a la derecha en %s"
1775
1776 #: src/transitplugins/openTripPlanner.js:1201
1777 msgid "Turn slightly right"
1778 msgstr "Gire levemente a la derecha"
1779
1780 #: src/transitplugins/openTripPlanner.js:1204
1781 #, javascript-format
1782 msgid "Turn sharp right on %s"
1783 msgstr "Gire bruscamente a la derecha en %s"
1784
1785 #: src/transitplugins/openTripPlanner.js:1206
1786 msgid "Turn sharp right"
1787 msgstr "Gire bruscamente a la derecha"
1788
1789 #: src/transitplugins/openTripPlanner.js:1212
1790 #, javascript-format
1791 msgid "In the roundaboat, take exit %s"
1792 msgstr "En la rotonda, coja la salida %s"
1793
1794 #: src/transitplugins/openTripPlanner.js:1214
1795 #, javascript-format
1796 msgid "In the roundabout, take exit to %s"
1797 msgstr "En la rotonda, coja la salida hacia %s"
1798
1799 #: src/transitplugins/openTripPlanner.js:1216
1800 msgid "Take the roundabout"
1801 msgstr "Entre en la rotonda"
1802
1803 #: src/transitplugins/openTripPlanner.js:1220
1804 #, javascript-format
1805 msgid "Take the elevator and get off at %s"
1806 msgstr "Coja el ascensor y salga en %s"
1807
1808 #: src/transitplugins/openTripPlanner.js:1222
1809 msgid "Take the elevator"
1810 msgstr "Coja el ascensor"
1811
1812 #: src/transitplugins/openTripPlanner.js:1226
1813 #, javascript-format
1814 msgid "Make a left u-turn onto %s"
1815 msgstr "Dé la vuelta hacia la izquierda en %s"
1816
1817 #: src/transitplugins/openTripPlanner.js:1228
1818 msgid "Make a left u-turn"
1819 msgstr "Dé la vuelta hacia la izquierda"
1820
1821 #: src/transitplugins/openTripPlanner.js:1231
1822 #, javascript-format
1823 msgid "Make a right u-turn onto %s"
1824 msgstr "Dé la vuelta hacia la derecha en %s"
1825
1826 #: src/transitplugins/openTripPlanner.js:1233
1827 msgid "Make a rigth u-turn"
1828 msgstr "Dé la vuelta hacia la derecha"
1829
1830 #~ msgid "Route search by OpenTripPlanner"
1831 #~ msgstr "Ruta buscada por OpenTripPlanner"
17081832
17091833 #~ msgid "Open with another application"
17101834 #~ msgstr "Abrir con otra aplicación"
88 msgid ""
99 msgstr "Project-Id-Version: gnome-maps master\n"
1010 "Report-Msgid-Bugs-To: https://gitlab.gnome.org/GNOME/gnome-maps/issues\n"
11 "POT-Creation-Date: 2019-08-02 06:11+0000\n"
12 "PO-Revision-Date: 2019-08-11 10:00+0100\n"
11 "POT-Creation-Date: 2019-10-13 19:50+0000\n"
12 "PO-Revision-Date: 2019-10-20 10:00+0100\n"
1313 "Last-Translator: Asier Sarasua Garmendia <asier.sarasua@gmail.com>\n"
1414 "Language-Team: Basque <librezale@librezale.eus>\n"
1515 "Language: eu\n"
2020
2121 #: data/org.gnome.Maps.appdata.xml.in:6
2222 msgid "GNOME Maps"
23 msgstr "GNOMEren Mapak"
23 msgstr "GNOME Mapak"
2424
2525 #: data/org.gnome.Maps.appdata.xml.in:7
2626 msgid "Find places around the world"
4848 "Street, Boston” or “Hotels near Alexanderplatz, Berlin”."
4949 msgstr "Bilatu kokaleku mota zehatz bat, adibidez 'Diru-truke bulegoa, Bilbo' edo 'Hotelak kale nagusia, Bilbo'"
5050
51 #: data/org.gnome.Maps.appdata.xml.in:59
51 #: data/org.gnome.Maps.appdata.xml.in:92
5252 msgid "The GNOME Project"
5353 msgstr "GNOME proiektua"
5454
6161 #.
6262 #. Translators: This is the program name.
6363 #: data/org.gnome.Maps.desktop.in:4 data/ui/main-window.ui:26
64 #: src/application.js:81 src/mainWindow.js:139 src/mainWindow.js:509
64 #: src/application.js:81 src/mainWindow.js:139 src/mainWindow.js:513
6565 msgid "Maps"
6666 msgstr "Mapak"
6767
100100
101101 #: data/org.gnome.Maps.gschema.xml:22
102102 msgid "The type of map to display (street, aerial, etc.)"
103 msgstr "Bistaratuko den mapa mota (kalekoa, airekoa, etab.)"
103 msgstr "Bistaratuko den mapa mota (kalekoa, airekoa eta abar)"
104104
105105 #: data/org.gnome.Maps.gschema.xml:26
106106 msgid "Window size"
467467
468468 #: data/ui/osm-account-dialog.ui:56
469469 msgid "Email"
470 msgstr "Helb. el."
470 msgstr "Posta elektronikoa"
471471
472472 #: data/ui/osm-account-dialog.ui:81
473473 msgid "Password"
615615 msgid "Route search by GraphHopper"
616616 msgstr "ibilbidea GraphHopper-ek bilatuta"
617617
618 #: data/ui/sidebar.ui:296
619 msgid "Route search by OpenTripPlanner"
620 msgstr "ibilbidea OpenTripPlanner-ek bilatuta"
621
622 #: data/ui/sidebar.ui:369
623 msgid ""
624 "Routing itineraries for public transit is provided by GNOME\n"
625 "using timetable data obtained from transit companies or agencies.\n"
626 "The companies and agencies can not be held responsible for the results "
627 "shown.\n"
618 #: data/ui/sidebar.ui:364
619 msgid ""
620 "Routing itineraries for public transit is provided by third-party\n"
621 "services.\n"
628622 "GNOME can not guarantee correctness of the itineraries and schedules shown.\n"
623 "Note that some providers might not include all available modes of "
624 "transportation,\n"
625 "e.g. a national provider might not include airlines, and a local provider "
626 "could\n"
627 "miss regional trains.\n"
629628 "Names and brands shown are to be considered as registered trademarks when "
630629 "applicable."
631 msgstr "Ibilbide publikoen bideak GNOMEk eskaintzen ditu,\n"
632 "konpainia eta agentzien zirkulazioetatik lortutako ordutegiaren datuak erabiliz.\n"
633 "Konpainiak eta agentziak ez dira erakutsitako emaitzen arduradun izango.\n"
634 "GNOMEk ezin du erakutsitako ibilbideen eta antolamenduen zuzentasunaz ziurtatu.\n"
630 msgstr "Ibilbide publikoen bideak hirugarrenek eskaintzen dituzte.\n"
631 "GNOMEk ezin du bermatu erakutsitako ibilbideen eta ordutegien zuzentasuna.\n"
632 "Kontuan izan zenbait hornitzailek ez dituztela garraio modu guztiak kontuan hartzen, adibidez nazio mailako hornitzaile batek beharbada ez ditu hegaldiak kontuan hartzen, eta hornitzaile lokal batek agian ez ditu eskualde mailako trenak erakusten.\n"
635633 "Erakutsitako izenak eta markak erregistratutako marka gisa hartuko dira dagokienenean."
636634
637635 #: data/ui/social-place-more-results-row.ui:8
686684
687685 #: data/ui/transit-options-panel.ui:145
688686 msgid "Ferries"
689 msgstr "Ferriak"
687 msgstr "Ferryak"
688
689 #: data/ui/transit-options-panel.ui:152
690 msgid "Airplanes"
691 msgstr "Hegazkinak"
690692
691693 #: data/ui/user-location-bubble.ui:13 src/geoclue.js:118
692694 msgid "Current location"
776778 #: src/checkIn.js:144
777779 #, javascript-format
778780 msgid "Cannot find “%s” in the social service"
779 msgstr "Ezin da “%s“ aurkitu zerbitzu sozialean"
781 msgstr "Ezin da “%s” aurkitu zerbitzu sozialean"
780782
781783 #: src/checkIn.js:146
782784 msgid "Cannot find a suitable place to check-in in this location"
851853 msgid "unknown geometry"
852854 msgstr "geometria ezezaguna"
853855
854 #: src/graphHopper.js:112 src/openTripPlanner.js:652
856 #: src/graphHopper.js:112 src/transitPlan.js:169
855857 msgid "Route request failed."
856858 msgstr "Huts egin du ibilbidearen eskaerak."
857859
858 #: src/graphHopper.js:119 src/openTripPlanner.js:615
860 #: src/graphHopper.js:119 src/transitPlan.js:161
859861 msgid "No route found."
860862 msgstr "Ez da ibilbiderik aurkitu."
861863
862 #: src/graphHopper.js:207
864 #: src/graphHopper.js:207 src/transitplugins/openTripPlanner.js:1164
863865 msgid "Start!"
864866 msgstr "Hasi!"
865867
867869 msgid "All Layer Files"
868870 msgstr "Geruzen fitxategi guztiak"
869871
870 #: src/mainWindow.js:442
872 #: src/mainWindow.js:446
871873 msgid "Failed to connect to location service"
872874 msgstr "Huts egin du kokapen-zerbitzuarekin konektatzean"
873875
874 #: src/mainWindow.js:507
876 #: src/mainWindow.js:511
875877 msgid "translator-credits"
876878 msgstr "Asier Sarasua Garmendia <asier.sarasua@gmail.com>"
877879
878 #: src/mainWindow.js:510
880 #: src/mainWindow.js:514
879881 msgid "A map application for GNOME"
880882 msgstr "GNOMEren mapen aplikazioa"
881883
882 #: src/mainWindow.js:521
884 #: src/mainWindow.js:525
883885 msgid "Copyright © 2011 – 2019 Red Hat, Inc. and The GNOME Maps authors"
884886 msgstr "Copyright © 2011 – 2019 Red Hat, Inc. eta GNOME Maps egileak"
885887
886 #: src/mainWindow.js:541
888 #: src/mainWindow.js:545
887889 #, javascript-format
888890 msgid "Map data by %s and contributors"
889891 msgstr "Maparen datuen sortzaileak: %s eta laguntzaileak"
893895 #. * the bare name of the tile provider, or a linkified URL if one
894896 #. * is available
895897 #.
896 #: src/mainWindow.js:557
898 #: src/mainWindow.js:561
897899 #, javascript-format
898900 msgid "Map tiles provided by %s"
899901 msgstr "Mapa-lauzen hornitzaileak: %s"
907909 #. * (i.e. "%2$s ... %1$s ..." for positioning the project URL
908910 #. * before the provider).
909911 #.
910 #: src/mainWindow.js:586
912 #: src/mainWindow.js:590
911913 #, javascript-format
912914 msgid "Search provided by %s using %s"
913915 msgstr "%s motorrak hornitutako bilaketa %s bidez"
914916
915 #: src/mapView.js:374
917 #: src/mapView.js:375
916918 msgid "File type is not supported"
917919 msgstr "Fitxategi mota ez dago onartuta"
918920
919 #: src/mapView.js:381
921 #: src/mapView.js:382
920922 msgid "Failed to open layer"
921923 msgstr "Huts egin du geruza irekitzean"
922924
923 #: src/mapView.js:417
925 #: src/mapView.js:418
924926 msgid "Failed to open GeoURI"
925927 msgstr "Huts egin du GeoURIa irekitzean"
926
927 #: src/openTripPlanner.js:648
928 msgid "No timetable data found for this route."
929 msgstr "Ez da ordutegiaren daturik aurkitu ibilbide honetan."
930928
931929 #. setting the status in session.cancel_message still seems
932930 #. to always give status IO_ERROR
12191217 msgid "Phone:"
12201218 msgstr "Telefonoa:"
12211219
1222 #: src/placeEntry.js:205
1220 #: src/placeEntry.js:209
12231221 msgid "Failed to parse Geo URI"
12241222 msgstr "Huts egin du Geo URIa aztertzean"
12251223
12821280 #: src/printLayout.js:312
12831281 #, javascript-format
12841282 msgid "From %s to %s"
1285 msgstr "\"%s\"(e)ndik \"%s\"(e)ra"
1283 msgstr "“%s”(e)ndik “%s”(e)ra"
12861284
12871285 #: src/printOperation.js:46
12881286 msgid "Loading map tiles for printing"
13321330 msgstr "huts egin du fitxategia kargatzean"
13331331
13341332 #. Translators: %s is a time expression with the format "%f h" or "%f min"
1335 #: src/sidebar.js:293
1333 #: src/sidebar.js:296
13361334 #, javascript-format
13371335 msgid "Estimated time: %s"
13381336 msgstr "Aurreikusitako denbora: %s"
1337
1338 #: src/sidebar.js:352
1339 #, javascript-format
1340 msgid "Itineraries provided by %s"
1341 msgstr "Ibilbideen hornitzailea: %s"
13391342
13401343 #. Translators: this is a format string indicating instructions
13411344 #. * starting a journey at the address given as the parameter
13691372 msgid "Arrive at %s"
13701373 msgstr "Iritsi: %s"
13711374
1372 #: src/transit.js:77
1375 #: src/transit.js:77 src/transitplugins/openTripPlanner.js:1177
13731376 msgid "Arrive"
13741377 msgstr "Iritsi"
13751378
14011404 #. * Translators: this is a format string giving the equivalent to
14021405 #. * "may 29" according to the current locale's convensions.
14031406 #.
1404 #: src/transitOptionsPanel.js:141
1407 #: src/transitOptionsPanel.js:143
14051408 msgctxt "month-day-date"
14061409 msgid "%b %e"
14071410 msgstr "%b %e"
1411
1412 #: src/transitPlan.js:165
1413 msgid "No timetable data found for this route."
1414 msgstr "Ez da ordutegiaren daturik aurkitu ibilbide honetan."
1415
1416 #: src/transitPlan.js:173
1417 msgid "No provider found for this route."
1418 msgstr "Ez da hornitzailerik aurkitu ibilbide honetarako."
14081419
14091420 #. Translators: this is a format string for showing a departure and
14101421 #. * arrival time, like:
14111422 #. * "12:00 – 13:03" where the placeholder %s are the actual times,
14121423 #. * these could be rearranged if needed.
14131424 #.
1414 #: src/transitPlan.js:254
1425 #: src/transitPlan.js:290
14151426 #, javascript-format
14161427 msgid "%s – %s"
14171428 msgstr "%s – %s"
14201431 #. * less than an hour, with only the minutes part, using plural forms
14211432 #. * as appropriate
14221433 #.
1423 #: src/transitPlan.js:281
1434 #: src/transitPlan.js:317
14241435 #, javascript-format
14251436 msgid "%d minute"
14261437 msgid_plural "%d minutes"
14311442 #. * where the duration is an exact number of hours (i.e. no
14321443 #. * minutes part), using plural forms as appropriate
14331444 #.
1434 #: src/transitPlan.js:292
1445 #: src/transitPlan.js:328
14351446 #, javascript-format
14361447 msgid "%d hour"
14371448 msgid_plural "%d hours"
14421453 #. * where the duration contains an hour and minute part, it's
14431454 #. * pluralized on the hours part
14441455 #.
1445 #: src/transitPlan.js:298
1456 #: src/transitPlan.js:334
14461457 #, javascript-format
14471458 msgid "%d:%02d hour"
14481459 msgid_plural "%d:%02d hours"
14551466 #. * "12:00–13:03" where the placeholder %s are the actual times,
14561467 #. * these could be rearranged if needed.
14571468 #.
1458 #: src/transitPlan.js:651
1469 #: src/transitPlan.js:699
14591470 #, javascript-format
14601471 msgid "%s–%s"
14611472 msgstr "%s–%s"
16091620 msgstr "zerbitzua"
16101621
16111622 #. Translators: Accuracy of user location information
1612 #: src/utils.js:220
1623 #: src/utils.js:229
16131624 msgid "Unknown"
16141625 msgstr "Ezezaguna"
16151626
16161627 #. Translators: Accuracy of user location information
1617 #: src/utils.js:223
1628 #: src/utils.js:232
16181629 msgid "Exact"
16191630 msgstr "Zehatza"
16201631
1621 #: src/utils.js:281
1632 #: src/utils.js:290
16221633 #, javascript-format
16231634 msgid "%f h"
16241635 msgstr "%f o"
16251636
1626 #: src/utils.js:283
1637 #: src/utils.js:292
16271638 #, javascript-format
16281639 msgid "%f min"
16291640 msgstr "%f min"
16301641
1631 #: src/utils.js:285
1642 #: src/utils.js:294
16321643 #, javascript-format
16331644 msgid "%f s"
16341645 msgstr "%f s"
16351646
16361647 #. Translators: This is a distance measured in kilometers
1637 #: src/utils.js:296
1648 #: src/utils.js:305
16381649 #, javascript-format
16391650 msgid "%s km"
16401651 msgstr "%s km"
16411652
16421653 #. Translators: This is a distance measured in meters
1643 #: src/utils.js:299
1654 #: src/utils.js:308
16441655 #, javascript-format
16451656 msgid "%s m"
16461657 msgstr "%s m"
16471658
16481659 #. Translators: This is a distance measured in miles
1649 #: src/utils.js:307
1660 #: src/utils.js:316
16501661 #, javascript-format
16511662 msgid "%s mi"
16521663 msgstr "%s mi"
16531664
16541665 #. Translators: This is a distance measured in feet
1655 #: src/utils.js:310
1666 #: src/utils.js:319
16561667 #, javascript-format
16571668 msgid "%s ft"
16581669 msgstr "%s ft"
1670
1671 #: src/transitplugins/openTripPlanner.js:1238
1672 #, javascript-format
1673 msgid "Continue on %s"
1674 msgstr "Jarraitu hemen: %s"
1675
1676 #: src/transitplugins/openTripPlanner.js:1240
1677 msgid "Continue"
1678 msgstr "Jarraitu"
1679
1680 #: src/transitplugins/openTripPlanner.js:1243
1681 #, javascript-format
1682 msgid "Turn left on %s"
1683 msgstr "Biratu ezkerrera hemen: %s"
1684
1685 #: src/transitplugins/openTripPlanner.js:1245
1686 msgid "Turn left"
1687 msgstr "Biratu ezkerrera"
1688
1689 #: src/transitplugins/openTripPlanner.js:1248
1690 #, javascript-format
1691 msgid "Turn slightly left on %s"
1692 msgstr "Biratu pixka bat ezkerrera hemen: %s"
1693
1694 #: src/transitplugins/openTripPlanner.js:1250
1695 msgid "Turn slightly left"
1696 msgstr "Biratu pixka bat ezkerrera"
1697
1698 #: src/transitplugins/openTripPlanner.js:1253
1699 #, javascript-format
1700 msgid "Turn sharp left on %s"
1701 msgstr "Biratu erabat ezkerrera hemen: %s"
1702
1703 #: src/transitplugins/openTripPlanner.js:1255
1704 msgid "Turn sharp left"
1705 msgstr "Biratu erabat ezkerrera"
1706
1707 #: src/transitplugins/openTripPlanner.js:1258
1708 #, javascript-format
1709 msgid "Turn right on %s"
1710 msgstr "Biratu eskuinera hemen: %s"
1711
1712 #: src/transitplugins/openTripPlanner.js:1260
1713 msgid "Turn right"
1714 msgstr "Biratu eskuinera"
1715
1716 #: src/transitplugins/openTripPlanner.js:1263
1717 #, javascript-format
1718 msgid "Turn slightly right on %s"
1719 msgstr "Biratu pixka bat eskuinera hemen: %s"
1720
1721 #: src/transitplugins/openTripPlanner.js:1265
1722 msgid "Turn slightly right"
1723 msgstr "Biratu pixka bat eskuinera"
1724
1725 #: src/transitplugins/openTripPlanner.js:1268
1726 #, javascript-format
1727 msgid "Turn sharp right on %s"
1728 msgstr "Biratu erabat eskuinera hemen: %s"
1729
1730 #: src/transitplugins/openTripPlanner.js:1270
1731 msgid "Turn sharp right"
1732 msgstr "Biratu erabat eskuinera"
1733
1734 #: src/transitplugins/openTripPlanner.js:1276
1735 #, javascript-format
1736 msgid "In the roundaboat, take exit %s"
1737 msgstr "Biribilgunean, hartu %s irteera"
1738
1739 #: src/transitplugins/openTripPlanner.js:1278
1740 #, javascript-format
1741 msgid "In the roundabout, take exit to %s"
1742 msgstr "Biribilgunean, hartu %s helbidera joateko irteera"
1743
1744 #: src/transitplugins/openTripPlanner.js:1280
1745 msgid "Take the roundabout"
1746 msgstr "Hartu biribilgunea"
1747
1748 #: src/transitplugins/openTripPlanner.js:1284
1749 #, javascript-format
1750 msgid "Take the elevator and get off at %s"
1751 msgstr "Hartu igogailua eta utzi hemen: %s"
1752
1753 #: src/transitplugins/openTripPlanner.js:1286
1754 msgid "Take the elevator"
1755 msgstr "Hartu igogailua"
1756
1757 #: src/transitplugins/openTripPlanner.js:1290
1758 #, javascript-format
1759 msgid "Make a left u-turn onto %s"
1760 msgstr "Aldatu noranzkoa ezkerrerantz hemen: %s"
1761
1762 #: src/transitplugins/openTripPlanner.js:1292
1763 msgid "Make a left u-turn"
1764 msgstr "Aldatu noranzkoa ezkerrerantz"
1765
1766 #: src/transitplugins/openTripPlanner.js:1295
1767 #, javascript-format
1768 msgid "Make a right u-turn onto %s"
1769 msgstr "Aldatu noranzkoa eskuinerantz hemen: %s"
1770
1771 #: src/transitplugins/openTripPlanner.js:1297
1772 msgid "Make a rigth u-turn"
1773 msgstr "Aldatu noranzkoa eskuinerantz"
1774
1775 #~ msgid "Route search by OpenTripPlanner"
1776 #~ msgstr "ibilbidea OpenTripPlanner-ek bilatuta"
16591777
16601778 #~ msgid "org.gnome.Maps"
16611779 #~ msgstr "org.gnome.Maps"
+219
-102
po/fa.po less more
66 msgstr ""
77 "Project-Id-Version: gnome-maps gnome-3-10\n"
88 "Report-Msgid-Bugs-To: https://gitlab.gnome.org/GNOME/gnome-maps/issues\n"
9 "POT-Creation-Date: 2019-09-22 06:02+0000\n"
10 "PO-Revision-Date: 2019-09-25 12:05+0000\n"
9 "POT-Creation-Date: 2019-10-19 13:44+0000\n"
10 "PO-Revision-Date: 2019-10-19 16:36+0000\n"
1111 "Last-Translator: Danial Behzadi <dani.behzi@ubuntu.com>\n"
1212 "Language-Team: Persian\n"
1313 "Language: fa\n"
1515 "Content-Type: text/plain; charset=UTF-8\n"
1616 "Content-Transfer-Encoding: 8bit\n"
1717 "Plural-Forms: nplurals=1; plural=0;\n"
18 "X-Generator: Poedit 2.2.1\n"
18 "X-Generator: Poedit 2.2.4\n"
1919
2020 #: data/org.gnome.Maps.appdata.xml.in:6
2121 msgid "GNOME Maps"
2828 #: data/org.gnome.Maps.appdata.xml.in:9
2929 msgid ""
3030 "Maps gives you quick access to maps all across the world. It allows you to "
31 "quickly find the place you’re looking for by searching for a city or street, "
32 "or locate a place to meet a friend."
33 msgstr ""
34 "نقشه‌ها دسترسی سریعی به نقشه‌ها در تمام جهان را می‌دهد. این برنامه می‌گذارد با "
35 "جست‌وجو برای یک شهر یا خیابان، مکانی را که به دنبالشید یافته یا مکانی را برای "
36 "قرار مشخّص کنید."
31 "quickly find the place you’re looking for by searching for a city or street, or "
32 "locate a place to meet a friend."
33 msgstr ""
34 "نقشه‌ها دسترسی سریعی به نقشه‌ها در تمام جهان را می‌دهد. این برنامه می‌گذارد با جست‌وجو "
35 "برای یک شهر یا خیابان، مکانی را که به دنبالشید یافته یا مکانی را برای قرار مشخّص "
36 "کنید."
3737
3838 #: data/org.gnome.Maps.appdata.xml.in:14
3939 msgid ""
40 "Maps uses the collaborative OpenStreetMap database, made by hundreds of "
41 "thousands of people across the globe."
42 msgstr ""
43 "نقشه‌ها از پایگاه دادهٔ مشارکتی OpenStreetMap استفاده می‌کند که به دست صدها هزار "
44 "نفر در سراسر جهان درست شده است."
40 "Maps uses the collaborative OpenStreetMap database, made by hundreds of thousands "
41 "of people across the globe."
42 msgstr ""
43 "نقشه‌ها از پایگاه دادهٔ مشارکتی OpenStreetMap استفاده می‌کند که به دست صدها هزار نفر "
44 "در سراسر جهان درست شده است."
4545
4646 #. Translators: Search is carried out on OpenStreetMap data using Nominatim.
4747 #. Visit http://wiki.openstreetmap.org/wiki/Nominatim/Special_Phrases and click
5151 "You can even search for specific types of locations, such as “Pubs near Main "
5252 "Street, Boston” or “Hotels near Alexanderplatz, Berlin”."
5353 msgstr ""
54 "حتا می‌توانید گونه‌های خاصی از موقعیت‌ها را، مثل «Pubs near Main Street, Boston» "
55 "یا «WiFi Access near Alexanderplatz, Berlin» جست‌وجو کنید."
54 "حتا می‌توانید گونه‌های خاصی از موقعیت‌ها را، مثل «Pubs near Main Street, Boston» یا "
55 "«WiFi Access near Alexanderplatz, Berlin» جست‌وجو کنید."
5656
5757 #: data/org.gnome.Maps.appdata.xml.in:92
5858 msgid "The GNOME Project"
6666 #. * overview.
6767 #.
6868 #. Translators: This is the program name.
69 #: data/org.gnome.Maps.desktop.in:4 data/ui/main-window.ui:26
70 #: src/application.js:81 src/mainWindow.js:139 src/mainWindow.js:513
69 #: data/org.gnome.Maps.desktop.in:4 data/ui/main-window.ui:26 src/application.js:81
70 #: src/mainWindow.js:139 src/mainWindow.js:513
7171 msgid "Maps"
7272 msgstr "نقشه‌ها"
7373
178178 "Latest used Foursquare check-in privacy setting. Possible values are: public, "
179179 "followers or private."
180180 msgstr ""
181 "آخرین تنظیمات استفاده شدهٔ محرمانگی اعلام حضور فوراسکور. مقادیر مجاز عبارتند "
182 "از: public، followers یا private."
181 "آخرین تنظیمات استفاده شدهٔ محرمانگی اعلام حضور فوراسکور. مقادیر مجاز عبارتند از: "
182 "public، followers یا private."
183183
184184 #: data/org.gnome.Maps.gschema.xml:67
185185 msgid "Foursquare check-in Facebook broadcasting"
187187
188188 #: data/org.gnome.Maps.gschema.xml:68
189189 msgid ""
190 "Indicates if Foursquare should broadcast the check-in as a post in the "
191 "Facebook account associated with the Foursquare account."
190 "Indicates if Foursquare should broadcast the check-in as a post in the Facebook "
191 "account associated with the Foursquare account."
192192 msgstr ""
193193 "مشخّص می‌کند که آیا فوراسکور باید اعلام حضور را به عنوان یک فرسته در حساب فیس‌بوک "
194194 "مرتبط با حساب فوراسکور منتشر کند یا نه."
199199
200200 #: data/org.gnome.Maps.gschema.xml:73
201201 msgid ""
202 "Indicates if Foursquare should broadcast the check-in as a tweet in the "
203 "Twitter account associated with the Foursquare account."
202 "Indicates if Foursquare should broadcast the check-in as a tweet in the Twitter "
203 "account associated with the Foursquare account."
204204 msgstr ""
205205 "مشخّص می‌کند که آیا فوراسکور باید اعلام حضور را به عنوان یک توییت در حساب توییتر "
206206 "مرتبط با حساب فوراسکور منتشر کند یا نه."
211211
212212 #: data/org.gnome.Maps.gschema.xml:78
213213 msgid "Indicates if the user has signed in to edit OpenStreetMap data."
214 msgstr ""
215 "مشخّص می‌کند که آیا کاربر برای ویرایش داده‌های OpenStreetMap وارد شده یا نه."
214 msgstr "مشخّص می‌کند که آیا کاربر برای ویرایش داده‌های OpenStreetMap وارد شده یا نه."
216215
217216 #: data/org.gnome.Maps.gschema.xml:82
218217 msgid "Last used transportation type for routing"
501500 #: data/ui/osm-account-dialog.ui:159
502501 msgid ""
503502 "Sorry, that didn’t work. Please try again, or visit\n"
504 "<a href=\"https://www.openstreetmap.org/user/forgot-password\">OpenStreetMap</"
505 "a> to reset your password."
503 "<a href=\"https://www.openstreetmap.org/user/forgot-password\">OpenStreetMap</a> "
504 "to reset your password."
506505 msgstr ""
507506 "متأسّفانه تأثیری نداشت. لطفا دوباره تلاش کرده یا برای\n"
508507 "باز نشانی گذرواژه‌تان به <a href=\"https://www.openstreetmap.org/user/forgot-"
571570 "OpenStreetMap data."
572571 msgstr ""
573572 "تغییرات نقشه در تمام نقشه‌هایی که از داده‌های\n"
574 " OpenStreetMap استفاده می‌کنند قابل مشاهده است."
573 "اوپن‌استریت‌مپ استفاده می‌کنند قابل مشاهده است."
575574
576575 #: data/ui/osm-edit-dialog.ui:241
577576 msgid "Recently Used"
634633 msgid "Route search by GraphHopper"
635634 msgstr "جست‌وجوی مسیر به دست GraphHopper"
636635
637 #: data/ui/sidebar.ui:296
638 msgid "Route search by OpenTripPlanner"
639 msgstr "جست‌وجوی مسیر به دست OpenTripPlanner"
640
641 #: data/ui/sidebar.ui:369
642 msgid ""
643 "Routing itineraries for public transit is provided by GNOME\n"
644 "using timetable data obtained from transit companies or agencies.\n"
645 "The companies and agencies can not be held responsible for the results shown.\n"
636 #: data/ui/sidebar.ui:364
637 msgid ""
638 "Routing itineraries for public transit is provided by third-party\n"
639 "services.\n"
646640 "GNOME can not guarantee correctness of the itineraries and schedules shown.\n"
641 "Note that some providers might not include all available modes of "
642 "transportation,\n"
643 "e.g. a national provider might not include airlines, and a local provider could\n"
644 "miss regional trains.\n"
647645 "Names and brands shown are to be considered as registered trademarks when "
648646 "applicable."
649647 msgstr ""
650 "مسیریابی برنامهٔ سفر برای حمل و نقل عمومی با استفاده از داده‌های جدول زمانی\n"
651 "گرفته شده از آژانس‌ها یا شرکت‌های مسافرتی به دست گنوم فراهم شده است.\n"
652 "آژانس‌ها و شرکت‌ها نمی‌توانند در قبال نتایج نمایش‌داده شده مسيول باشند.\n"
653 "گنوم نمی‌تواند درستی رمان‌بندی برنامه‌های سفر نمایش‌داده شده را تضمین کند.\n"
654 "نام‌ها و مارک‌های نمایش‌داده شده، در صورت امکان باید علامت‌های تجاری ثبت‌شده در نظر "
655 "گرفته شوند."
648 "مسیریابی برنامهٔ سفر برای حمل‌ونقل عمومی به دست خدمات ثالث فراهم شده است.\n"
649 "گنوم نمی‌تواند درستی برنامهٔ سفر و زمان‌بندی‌های نمایش داده‌شده را تصمین کند.\n"
650 "به خاطر داشته باشید که ممکن است برخی فراهم‌کنندگان، تمام حالت‌های موجود برای\n"
651 "حمل‌ونقل را نداشته باشند. مثلاً ممکن است یک فراهم‌کنندهٔ ملّی، خطوط هوایی را نداشته\n"
652 "باشد و یک فراهم‌کنندهٔ محلّی می‌تواند قطارهای منطقه‌ای را نشان ندهد.\n"
653 "نام‌ها و برندهای نمایش‌داده‌شده باید هنگام امکان به عنوان علایم تجاری در نظر گرفته "
654 "شوند."
656655
657656 #: data/ui/social-place-more-results-row.ui:8
658657 msgid "Show more results"
708707 msgid "Ferries"
709708 msgstr "قایق‌ها"
710709
710 #: data/ui/transit-options-panel.ui:152
711 msgid "Airplanes"
712 msgstr "هواپیماها"
713
711714 #: data/ui/user-location-bubble.ui:13 src/geoclue.js:118
712715 msgid "Current location"
713716 msgstr "موقعیت کنونی"
764767
765768 #: src/checkInDialog.js:201
766769 msgid ""
767 "Maps cannot find the place to check in to with Facebook. Please select one "
768 "from this list."
769 msgstr ""
770 "نقشه‌ها نمی‌تواند مکان را برای اعلام حضور در فیس‌بوک بیابد. لطفاً موقعیتی را از "
771 "فهرست برگزینید."
770 "Maps cannot find the place to check in to with Facebook. Please select one from "
771 "this list."
772 msgstr ""
773 "نقشه‌ها نمی‌تواند مکان را برای اعلام حضور در فیس‌بوک بیابد. لطفاً موقعیتی را از فهرست "
774 "برگزینید."
772775
773776 #: src/checkInDialog.js:203
774777 msgid ""
775 "Maps cannot find the place to check in to with Foursquare. Please select one "
776 "from this list."
778 "Maps cannot find the place to check in to with Foursquare. Please select one from "
779 "this list."
777780 msgstr ""
778781 "نقشه‌ها نمی‌تواند مکان را برای اعلام حضور در فوراسکور بیابد. لطفاً موقعیتی را از "
779782 "فهرست برگزینید."
808811
809812 #: src/checkIn.js:150
810813 msgid ""
811 "Credentials have expired, please open Online Accounts to sign in and enable "
812 "this account"
813 msgstr ""
814 "اعتبارنامه‌ها منقضی شده‌اند. لطفاً برای ورود و به کار انداختن این حساب، حساب‌های "
815 "برخط را بگشایید"
814 "Credentials have expired, please open Online Accounts to sign in and enable this "
815 "account"
816 msgstr ""
817 "اعتبارنامه‌ها منقضی شده‌اند. لطفاً برای ورود و به کار انداختن این حساب، حساب‌های برخط "
818 "را بگشایید"
816819
817820 #: src/contextMenu.js:99
818821 msgid "Route from here"
832835
833836 #: src/contextMenu.js:190
834837 msgid ""
835 "Location was added to the map, note that it may take a while before it shows "
836 "on the map and in search results."
837 msgstr ""
838 "موقعیت به نقشه اضافه شد. خواستان باشد که نمایشش در نقشه و نتایج جست‌وجو، ممکن "
839 "است کمی طول بکشد."
838 "Location was added to the map, note that it may take a while before it shows on "
839 "the map and in search results."
840 msgstr ""
841 "موقعیت به نقشه اضافه شد. خواستان باشد که نمایشش در نقشه و نتایج جست‌وجو، ممکن است "
842 "کمی طول بکشد."
840843
841844 #. Translators: This is a format string for a PNG filename for an
842845 #. * exported image with coordinates. The .png extension should be kept
879882 msgid "unknown geometry"
880883 msgstr "هندسهٔ ناشناخته"
881884
882 #: src/graphHopper.js:112 src/openTripPlanner.js:652
885 #: src/graphHopper.js:112 src/transitPlan.js:192
883886 msgid "Route request failed."
884887 msgstr "درخواست مسیر شکست خورد."
885888
886 #: src/graphHopper.js:119 src/openTripPlanner.js:615
889 #: src/graphHopper.js:119 src/transitPlan.js:184
887890 msgid "No route found."
888891 msgstr "هیچ مسیری پیدا نشد."
889892
890 #: src/graphHopper.js:207
893 #: src/graphHopper.js:207 src/transitplugins/openTripPlanner.js:1100
891894 msgid "Start!"
892895 msgstr "شروع!"
893896
942945 msgid "Search provided by %s using %s"
943946 msgstr "جست‌وجو به دست %s با استفاده از %s فراهم شده"
944947
945 #: src/mapView.js:374
948 #: src/mapView.js:375
946949 msgid "File type is not supported"
947950 msgstr "گونهٔ پرونده پشتیبانی نمی‌شود"
948951
949 #: src/mapView.js:381
952 #: src/mapView.js:382
950953 msgid "Failed to open layer"
951954 msgstr "شکست در گشودن لایه"
952955
953 #: src/mapView.js:417
956 #: src/mapView.js:418
954957 msgid "Failed to open GeoURI"
955958 msgstr "شکست در گشودن نشانی جغرافیایی"
956
957 #: src/openTripPlanner.js:648
958 msgid "No timetable data found for this route."
959 msgstr "هیچ دادهٔٔ زمانی‌ای برای این مسیر پیدا نشد."
960959
961960 #. setting the status in session.cancel_message still seems
962961 #. to always give status IO_ERROR
10061005
10071006 #: src/osmEditDialog.js:122
10081007 msgid ""
1009 "The official website. Try to use the most basic form of a URL i.e. http://"
1010 "example.com instead of http://example.com/index.html."
1008 "The official website. Try to use the most basic form of a URL i.e. http://example."
1009 "com instead of http://example.com/index.html."
10111010 msgstr ""
10121011 "پایگاه وب رسمی. سعی کنید از ساده‌ترین شکل آدرس استفاده کنید مثلا http://example."
10131012 "com به جای http://example.com/index.html."
10301029
10311030 #: src/osmEditDialog.js:140
10321031 msgid ""
1033 "The format used should include the language code and the article title like "
1034 "“en:Article title”."
1035 msgstr ""
1036 "قالب استفاده شده باید شامل کد زبان و عنوان مقاله باید مثل «en:Article title» "
1037 "باشد."
1032 "The format used should include the language code and the article title like “en:"
1033 "Article title”."
1034 msgstr ""
1035 "قالب استفاده شده باید شامل کد زبان و عنوان مقاله باید مثل «en:Article title» باشد."
10381036
10391037 #: src/osmEditDialog.js:144
10401038 msgid "Opening hours"
12011199
12021200 #: src/osmEditDialog.js:220
12031201 msgid ""
1204 "Information used to inform other mappers about non-obvious information about "
1205 "an element, the author’s intent when creating it, or hints for further "
1206 "improvement."
1207 msgstr ""
1208 "اطّلاعات برای آگاه کردن دیگر نقشه‌کش‌ها دربارهٔ اطّلاعات نابدیهی دربارهٔ یک عنصر، "
1209 "قصد نگارنده هنگام ایجادش یا راهنمایی برای بهبودهای بعدی."
1202 "Information used to inform other mappers about non-obvious information about an "
1203 "element, the author’s intent when creating it, or hints for further improvement."
1204 msgstr ""
1205 "اطّلاعات برای آگاه کردن دیگر نقشه‌کش‌ها دربارهٔ اطّلاعات نابدیهی دربارهٔ یک عنصر، قصد "
1206 "نگارنده هنگام ایجادش یا راهنمایی برای بهبودهای بعدی."
12101207
12111208 #: src/osmEditDialog.js:325
12121209 msgctxt "dialog title"
13031300
13041301 #: src/place.js:475
13051302 msgid "Place not found in OpenStreetMap"
1306 msgstr "مکان در OpenStreetMap پیدا نشد"
1303 msgstr "مکان در اوپن استریت مپ پیدا نشد"
13071304
13081305 #: src/place.js:495
13091306 msgid "OpenStreetMap URL is not valid"
13701367 msgstr "شکست در بارگیری پرونده"
13711368
13721369 #. Translators: %s is a time expression with the format "%f h" or "%f min"
1373 #: src/sidebar.js:293
1370 #: src/sidebar.js:296
13741371 #, javascript-format
13751372 msgid "Estimated time: %s"
13761373 msgstr "زمان تخمینی: %s"
1374
1375 #: src/sidebar.js:352
1376 #, javascript-format
1377 msgid "Itineraries provided by %s"
1378 msgstr "برنامه سفر فراهم شده به دست %s"
13771379
13781380 #. Translators: this is a format string indicating instructions
13791381 #. * starting a journey at the address given as the parameter
14071409 msgid "Arrive at %s"
14081410 msgstr "رسیدن به مقصد در %s"
14091411
1410 #: src/transit.js:77
1412 #: src/transit.js:77 src/transitplugins/openTripPlanner.js:1113
14111413 msgid "Arrive"
14121414 msgstr "رسیدن"
14131415
14391441 #. * Translators: this is a format string giving the equivalent to
14401442 #. * "may 29" according to the current locale's convensions.
14411443 #.
1442 #: src/transitOptionsPanel.js:141
1444 #: src/transitOptionsPanel.js:143
14431445 msgctxt "month-day-date"
14441446 msgid "%b %e"
14451447 msgstr "%b %Oe"
1448
1449 #: src/transitPlan.js:188
1450 msgid "No timetable data found for this route."
1451 msgstr "برای این مسیر هیچ دادهٔ جدول زمانی‌ای پیدا نشد."
1452
1453 #: src/transitPlan.js:196
1454 msgid "No provider found for this route."
1455 msgstr "برای این مسیر هیچ فراهم کننده‌ای پیدا نشد."
14461456
14471457 #. Translators: this is a format string for showing a departure and
14481458 #. * arrival time, like:
14491459 #. * "12:00 – 13:03" where the placeholder %s are the actual times,
14501460 #. * these could be rearranged if needed.
14511461 #.
1452 #: src/transitPlan.js:254
1462 #: src/transitPlan.js:313
14531463 #, javascript-format
14541464 msgid "%s – %s"
14551465 msgstr "%s – %s"
14581468 #. * less than an hour, with only the minutes part, using plural forms
14591469 #. * as appropriate
14601470 #.
1461 #: src/transitPlan.js:281
1471 #: src/transitPlan.js:340
14621472 #, javascript-format
14631473 msgid "%d minute"
14641474 msgid_plural "%d minutes"
14681478 #. * where the duration is an exact number of hours (i.e. no
14691479 #. * minutes part), using plural forms as appropriate
14701480 #.
1471 #: src/transitPlan.js:292
1481 #: src/transitPlan.js:351
14721482 #, javascript-format
14731483 msgid "%d hour"
14741484 msgid_plural "%d hours"
14781488 #. * where the duration contains an hour and minute part, it's
14791489 #. * pluralized on the hours part
14801490 #.
1481 #: src/transitPlan.js:298
1491 #: src/transitPlan.js:357
14821492 #, javascript-format
14831493 msgid "%d:%02d hour"
14841494 msgid_plural "%d:%02d hours"
14901500 #. * "12:00–13:03" where the placeholder %s are the actual times,
14911501 #. * these could be rearranged if needed.
14921502 #.
1493 #: src/transitPlan.js:651
1503 #: src/transitPlan.js:734
14941504 #, javascript-format
14951505 msgid "%s–%s"
14961506 msgstr "%s–%s"
16441654 msgstr "خدمت"
16451655
16461656 #. Translators: Accuracy of user location information
1647 #: src/utils.js:220
1657 #: src/utils.js:229
16481658 msgid "Unknown"
16491659 msgstr "ناشناخته"
16501660
16511661 #. Translators: Accuracy of user location information
1652 #: src/utils.js:223
1662 #: src/utils.js:232
16531663 msgid "Exact"
16541664 msgstr "دقیقاً"
16551665
1656 #: src/utils.js:281
1666 #: src/utils.js:290
16571667 #, javascript-format
16581668 msgid "%f h"
16591669 msgstr "%If ساعت"
16601670
1661 #: src/utils.js:283
1671 #: src/utils.js:292
16621672 #, javascript-format
16631673 msgid "%f min"
16641674 msgstr "%If دقیقه"
16651675
1666 #: src/utils.js:285
1676 #: src/utils.js:294
16671677 #, javascript-format
16681678 msgid "%f s"
16691679 msgstr "%f s"
16701680
16711681 #. Translators: This is a distance measured in kilometers
1672 #: src/utils.js:296
1682 #: src/utils.js:305
16731683 #, javascript-format
16741684 msgid "%s km"
16751685 msgstr "%s ک‌م"
16761686
16771687 #. Translators: This is a distance measured in meters
1678 #: src/utils.js:299
1688 #: src/utils.js:308
16791689 #, javascript-format
16801690 msgid "%s m"
16811691 msgstr "%s م"
16821692
16831693 #. Translators: This is a distance measured in miles
1684 #: src/utils.js:307
1694 #: src/utils.js:316
16851695 #, javascript-format
16861696 msgid "%s mi"
16871697 msgstr "%s ما"
16881698
16891699 #. Translators: This is a distance measured in feet
1690 #: src/utils.js:310
1700 #: src/utils.js:319
16911701 #, javascript-format
16921702 msgid "%s ft"
16931703 msgstr "%s پا"
1704
1705 #: src/transitplugins/openTripPlanner.js:1174
1706 #, javascript-format
1707 msgid "Continue on %s"
1708 msgstr "در %s ادامه دهید"
1709
1710 #: src/transitplugins/openTripPlanner.js:1176
1711 msgid "Continue"
1712 msgstr "ادامه"
1713
1714 #: src/transitplugins/openTripPlanner.js:1179
1715 #, javascript-format
1716 msgid "Turn left on %s"
1717 msgstr "در %s به چپ بپیچید"
1718
1719 #: src/transitplugins/openTripPlanner.js:1181
1720 msgid "Turn left"
1721 msgstr "به چپ بپیچید"
1722
1723 #: src/transitplugins/openTripPlanner.js:1184
1724 #, javascript-format
1725 msgid "Turn slightly left on %s"
1726 msgstr "در %s آرام به چپ بپیچید"
1727
1728 #: src/transitplugins/openTripPlanner.js:1186
1729 msgid "Turn slightly left"
1730 msgstr "آرام به چپ بپیچید"
1731
1732 #: src/transitplugins/openTripPlanner.js:1189
1733 #, javascript-format
1734 msgid "Turn sharp left on %s"
1735 msgstr "در %s تند به چپ بپیچید"
1736
1737 #: src/transitplugins/openTripPlanner.js:1191
1738 msgid "Turn sharp left"
1739 msgstr "تند به چپ بپیچید"
1740
1741 #: src/transitplugins/openTripPlanner.js:1194
1742 #, javascript-format
1743 msgid "Turn right on %s"
1744 msgstr "در %s به راست بپیچید"
1745
1746 #: src/transitplugins/openTripPlanner.js:1196
1747 msgid "Turn right"
1748 msgstr "به راست بپیچید"
1749
1750 #: src/transitplugins/openTripPlanner.js:1199
1751 #, javascript-format
1752 msgid "Turn slightly right on %s"
1753 msgstr "در %s آرام به راست بپیچید"
1754
1755 #: src/transitplugins/openTripPlanner.js:1201
1756 msgid "Turn slightly right"
1757 msgstr "آرام به راست بپیچید"
1758
1759 #: src/transitplugins/openTripPlanner.js:1204
1760 #, javascript-format
1761 msgid "Turn sharp right on %s"
1762 msgstr "در %s تند به راست بپیچید"
1763
1764 #: src/transitplugins/openTripPlanner.js:1206
1765 msgid "Turn sharp right"
1766 msgstr "تند به رایت بپیچید"
1767
1768 #: src/transitplugins/openTripPlanner.js:1212
1769 #, javascript-format
1770 msgid "In the roundaboat, take exit %s"
1771 msgstr "در میدان از %s خارج شوید"
1772
1773 #: src/transitplugins/openTripPlanner.js:1214
1774 #, javascript-format
1775 msgid "In the roundabout, take exit to %s"
1776 msgstr "در میدان به %s خارج شوید"
1777
1778 #: src/transitplugins/openTripPlanner.js:1216
1779 msgid "Take the roundabout"
1780 msgstr "وارد میدان شوید"
1781
1782 #: src/transitplugins/openTripPlanner.js:1220
1783 #, javascript-format
1784 msgid "Take the elevator and get off at %s"
1785 msgstr "وارد آسانسور شده و در %s حارج شوید"
1786
1787 #: src/transitplugins/openTripPlanner.js:1222
1788 msgid "Take the elevator"
1789 msgstr "وارد آسانسور شوید"
1790
1791 #: src/transitplugins/openTripPlanner.js:1226
1792 #, javascript-format
1793 msgid "Make a left u-turn onto %s"
1794 msgstr "به %s گردش به چپ کنید"
1795
1796 #: src/transitplugins/openTripPlanner.js:1228
1797 msgid "Make a left u-turn"
1798 msgstr "گردش به چپ کنید"
1799
1800 #: src/transitplugins/openTripPlanner.js:1231
1801 #, javascript-format
1802 msgid "Make a right u-turn onto %s"
1803 msgstr "به %s گردش به راست کنید"
1804
1805 #: src/transitplugins/openTripPlanner.js:1233
1806 msgid "Make a rigth u-turn"
1807 msgstr "گردش به راست کنید"
1808
1809 #~ msgid "Route search by OpenTripPlanner"
1810 #~ msgstr "جست‌وجوی مسیر به دست OpenTripPlanner"
16941811
16951812 #~ msgid "Quit"
16961813 #~ msgstr "خروج"
66 msgstr ""
77 "Project-Id-Version: gnome-maps master\n"
88 "Report-Msgid-Bugs-To: https://gitlab.gnome.org/GNOME/gnome-maps/issues\n"
9 "POT-Creation-Date: 2019-09-10 19:50+0000\n"
10 "PO-Revision-Date: 2019-09-22 07:59+0200\n"
9 "POT-Creation-Date: 2019-10-01 20:20+0000\n"
10 "PO-Revision-Date: 2019-10-17 21:32+0200\n"
1111 "Last-Translator: Fabio Tomat <f.t.public@gmail.com>\n"
1212 "Language-Team: Friulian <fur@li.org>\n"
1313 "Language: fur\n"
1414 "MIME-Version: 1.0\n"
1515 "Content-Type: text/plain; charset=UTF-8\n"
1616 "Content-Transfer-Encoding: 8bit\n"
17 "X-Generator: Poedit 2.2.3\n"
17 "X-Generator: Poedit 2.2.4\n"
1818 "Plural-Forms: nplurals=2; plural=(n != 1);\n"
1919
2020 #: data/org.gnome.Maps.appdata.xml.in:6
138138
139139 #: data/org.gnome.Maps.gschema.xml:42
140140 msgid "Maximum number of search results from geocode search."
141 msgstr "Massim numar di risultâts de ricercje da codifiche gjeografiche. "
141 msgstr "Massim numar di risultâts de ricercje da codifiche gjeografiche."
142142
143143 #: data/org.gnome.Maps.gschema.xml:46
144144 msgid "Number of recent places to store"
639639 msgid "Route search by GraphHopper"
640640 msgstr "Ricercje dal percors cun GraphHopper"
641641
642 #: data/ui/sidebar.ui:296
643 msgid "Route search by OpenTripPlanner"
644 msgstr "Ricercje dal percors cun OpenTripPlanner"
645
646 #: data/ui/sidebar.ui:369
647 msgid ""
648 "Routing itineraries for public transit is provided by GNOME\n"
649 "using timetable data obtained from transit companies or agencies.\n"
650 "The companies and agencies can not be held responsible for the results "
651 "shown.\n"
642 #: data/ui/sidebar.ui:364
643 msgid ""
644 "Routing itineraries for public transit is provided by third-party\n"
645 "services.\n"
652646 "GNOME can not guarantee correctness of the itineraries and schedules shown.\n"
647 "Note that some providers might not include all available modes of "
648 "transportation,\n"
649 "e.g. a national provider might not include airlines, and a local provider "
650 "could\n"
651 "miss regional trains.\n"
653652 "Names and brands shown are to be considered as registered trademarks when "
654653 "applicable."
655654 msgstr ""
656 "I itineraris di percors pai mieçs publics a son furnîts di GNOME\n"
657 "doprant i dâts dai oraris otignûts des aziendis di traspuart o agjenziis.\n"
658 "Lis aziendis di traspuart e lis agjenziis no puedin jessi tignudis "
659 "responsabilis pai risultâts mostrâts.\n"
660 "GNOME nol pues garantî la justece dai itineraris e dai plans mostrâts.\n"
655 "I itineraris di percors pai mieçs publics a son furnîts di servizis di\n"
656 "tiercis parts.\n"
657 "GNOME nol pues garantî la justece dai itineraris e dai oraris mostrâts.\n"
658 "Viôt che cualchi furnidôr al podarès no includi dutis lis modalitâts di "
659 "traspuart disponibilis,\n"
660 "p.e. un furnidôr nazionâl al podarès no includi lis tratis aeronautichis e "
661 "un furnidôr locâl\n"
662 "al pues no includi i trens regjionâi.\n"
661663 "I nons e lis marchis mostradis si àn di considerâ come marchis regjistradis "
662664 "cuant che si puedin aplicâ."
663665
715717 msgid "Ferries"
716718 msgstr "Traghets"
717719
720 #: data/ui/transit-options-panel.ui:152
721 msgid "Airplanes"
722 msgstr "Avions"
723
718724 #: data/ui/user-location-bubble.ui:13 src/geoclue.js:118
719725 msgid "Current location"
720726 msgstr "Posizion atuâl"
884890
885891 #: src/geoJSONSource.js:180
886892 msgid "unknown geometry"
887 msgstr "Gjeometrie no valide"
888
889 #: src/graphHopper.js:112 src/openTripPlanner.js:652
893 msgstr "gjeometrie no valide"
894
895 #: src/graphHopper.js:112 src/transitPlan.js:169
890896 msgid "Route request failed."
891897 msgstr "Richieste dal percors falide."
892898
893 #: src/graphHopper.js:119 src/openTripPlanner.js:615
899 #: src/graphHopper.js:119 src/transitPlan.js:161
894900 msgid "No route found."
895901 msgstr "Percors no cjatât."
896902
897 #: src/graphHopper.js:207
903 #: src/graphHopper.js:207 src/transitplugins/openTripPlanner.js:1154
898904 msgid "Start!"
899905 msgstr "Invie!"
900906
908914
909915 #: src/mainWindow.js:511
910916 msgid "translator-credits"
911 msgstr "Fabio Tomat"
917 msgstr "Fabio Tomat, <f.t.public@gmail.com>"
912918
913919 #: src/mainWindow.js:514
914920 msgid "A map application for GNOME"
947953 msgid "Search provided by %s using %s"
948954 msgstr "Ricercje furnide di %s doprant %s"
949955
950 #: src/mapView.js:374
956 #: src/mapView.js:375
951957 msgid "File type is not supported"
952958 msgstr "Gjenar di file no supuartât"
953959
954 #: src/mapView.js:381
960 #: src/mapView.js:382
955961 msgid "Failed to open layer"
956962 msgstr "Impussibil vierzi il strât"
957963
958 #: src/mapView.js:417
964 #: src/mapView.js:418
959965 msgid "Failed to open GeoURI"
960966 msgstr "Impussibil vierzi il GeoURI"
961
962 #: src/openTripPlanner.js:648
963 msgid "No timetable data found for this route."
964 msgstr "Nissun dât di oraris cjatât par chest percors."
965967
966968 #. setting the status in session.cancel_message still seems
967969 #. to always give status IO_ERROR
13771379 msgstr "impussibil cjariâ il file"
13781380
13791381 #. Translators: %s is a time expression with the format "%f h" or "%f min"
1380 #: src/sidebar.js:293
1382 #: src/sidebar.js:296
13811383 #, javascript-format
13821384 msgid "Estimated time: %s"
13831385 msgstr "Timp stimât: %s"
1386
1387 #: src/sidebar.js:352
1388 #, javascript-format
1389 msgid "Itineraries provided by %s"
1390 msgstr "Itineraris furnîts di %s"
13841391
13851392 #. Translators: this is a format string indicating instructions
13861393 #. * starting a journey at the address given as the parameter
14141421 msgid "Arrive at %s"
14151422 msgstr "Rive a %s"
14161423
1417 #: src/transit.js:77
1424 #: src/transit.js:77 src/transitplugins/openTripPlanner.js:1167
14181425 msgid "Arrive"
14191426 msgstr "Rivade"
14201427
14461453 #. * Translators: this is a format string giving the equivalent to
14471454 #. * "may 29" according to the current locale's convensions.
14481455 #.
1449 #: src/transitOptionsPanel.js:141
1456 #: src/transitOptionsPanel.js:143
14501457 msgctxt "month-day-date"
14511458 msgid "%b %e"
14521459 msgstr "%e di %b"
1460
1461 #: src/transitPlan.js:165
1462 msgid "No timetable data found for this route."
1463 msgstr "Nissun dât di oraris cjatât par chest percors."
1464
1465 #: src/transitPlan.js:173
1466 msgid "No provider found for this route."
1467 msgstr "Nissun furnidôr cjatât par chest percors."
14531468
14541469 #. Translators: this is a format string for showing a departure and
14551470 #. * arrival time, like:
14561471 #. * "12:00 – 13:03" where the placeholder %s are the actual times,
14571472 #. * these could be rearranged if needed.
14581473 #.
1459 #: src/transitPlan.js:254
1474 #: src/transitPlan.js:290
14601475 #, javascript-format
14611476 msgid "%s – %s"
14621477 msgstr "%s – %s"
14651480 #. * less than an hour, with only the minutes part, using plural forms
14661481 #. * as appropriate
14671482 #.
1468 #: src/transitPlan.js:281
1483 #: src/transitPlan.js:317
14691484 #, javascript-format
14701485 msgid "%d minute"
14711486 msgid_plural "%d minutes"
14761491 #. * where the duration is an exact number of hours (i.e. no
14771492 #. * minutes part), using plural forms as appropriate
14781493 #.
1479 #: src/transitPlan.js:292
1494 #: src/transitPlan.js:328
14801495 #, javascript-format
14811496 msgid "%d hour"
14821497 msgid_plural "%d hours"
14871502 #. * where the duration contains an hour and minute part, it's
14881503 #. * pluralized on the hours part
14891504 #.
1490 #: src/transitPlan.js:298
1505 #: src/transitPlan.js:334
14911506 #, javascript-format
14921507 msgid "%d:%02d hour"
14931508 msgid_plural "%d:%02d hours"
15001515 #. * "12:00–13:03" where the placeholder %s are the actual times,
15011516 #. * these could be rearranged if needed.
15021517 #.
1503 #: src/transitPlan.js:651
1518 #: src/transitPlan.js:699
15041519 #, javascript-format
15051520 msgid "%s–%s"
15061521 msgstr "%s–%s"
15111526
15121527 #: src/translations.js:58
15131528 msgid "from sunrise to sunset"
1514 msgstr "Di un scûr a chel altri"
1529 msgstr "di un scûr a chel altri"
15151530
15161531 #. Translators:
15171532 #. * This is a format string with two separate time ranges
15821597
15831598 #: src/translations.js:185
15841599 msgid "every day"
1585 msgstr "Ogni dì"
1600 msgstr "ogni dì"
15861601
15871602 #. Translators:
15881603 #. * This represents a range of days with a starting and ending day.
15951610
15961611 #: src/translations.js:208
15971612 msgid "public holidays"
1598 msgstr "Feriis"
1613 msgstr "festîfs"
15991614
16001615 #: src/translations.js:210
16011616 msgid "school holidays"
1602 msgstr "Vacancis di scuele"
1617 msgstr "vacancis di scuele"
16031618
16041619 #. Translators:
16051620 #. * This is a list with two time intervals, such as:
16171632
16181633 #: src/translations.js:264
16191634 msgid "not open"
1620 msgstr "No viert"
1635 msgstr "no viert"
16211636
16221637 #. Translators:
16231638 #. * This is a time interval with a starting and an ending time.
16541669 msgstr "servizi"
16551670
16561671 #. Translators: Accuracy of user location information
1657 #: src/utils.js:220
1672 #: src/utils.js:229
16581673 msgid "Unknown"
16591674 msgstr "No cognossût"
16601675
16611676 #. Translators: Accuracy of user location information
1662 #: src/utils.js:223
1677 #: src/utils.js:232
16631678 msgid "Exact"
16641679 msgstr "Di precîs"
16651680
1666 #: src/utils.js:281
1681 #: src/utils.js:290
16671682 #, javascript-format
16681683 msgid "%f h"
16691684 msgstr "%f h"
16701685
1671 #: src/utils.js:283
1686 #: src/utils.js:292
16721687 #, javascript-format
16731688 msgid "%f min"
16741689 msgstr "%f min"
16751690
1676 #: src/utils.js:285
1691 #: src/utils.js:294
16771692 #, javascript-format
16781693 msgid "%f s"
16791694 msgstr "%f s"
16801695
16811696 #. Translators: This is a distance measured in kilometers
1682 #: src/utils.js:296
1697 #: src/utils.js:305
16831698 #, javascript-format
16841699 msgid "%s km"
16851700 msgstr "%s km"
16861701
16871702 #. Translators: This is a distance measured in meters
1688 #: src/utils.js:299
1703 #: src/utils.js:308
16891704 #, javascript-format
16901705 msgid "%s m"
16911706 msgstr "%s m"
16921707
16931708 #. Translators: This is a distance measured in miles
1694 #: src/utils.js:307
1709 #: src/utils.js:316
16951710 #, javascript-format
16961711 msgid "%s mi"
16971712 msgstr "%s mi"
16981713
16991714 #. Translators: This is a distance measured in feet
1700 #: src/utils.js:310
1715 #: src/utils.js:319
17011716 #, javascript-format
17021717 msgid "%s ft"
17031718 msgstr "%s ft"
1719
1720 #: src/transitplugins/openTripPlanner.js:1228
1721 #, javascript-format
1722 msgid "Continue on %s"
1723 msgstr "Continue su %s"
1724
1725 #: src/transitplugins/openTripPlanner.js:1230
1726 msgid "Continue"
1727 msgstr "Continue"
1728
1729 #: src/transitplugins/openTripPlanner.js:1233
1730 #, javascript-format
1731 msgid "Turn left on %s"
1732 msgstr "Svolte a çampe su %s"
1733
1734 #: src/transitplugins/openTripPlanner.js:1235
1735 msgid "Turn left"
1736 msgstr "Svolte a çampe"
1737
1738 #: src/transitplugins/openTripPlanner.js:1238
1739 #, javascript-format
1740 msgid "Turn slightly left on %s"
1741 msgstr "Svolte di pôc a çampe su %s"
1742
1743 #: src/transitplugins/openTripPlanner.js:1240
1744 msgid "Turn slightly left"
1745 msgstr "Svolte di pôc a çampe"
1746
1747 #: src/transitplugins/openTripPlanner.js:1243
1748 #, javascript-format
1749 msgid "Turn sharp left on %s"
1750 msgstr "Svolte di bot a çampe su %s"
1751
1752 #: src/transitplugins/openTripPlanner.js:1245
1753 msgid "Turn sharp left"
1754 msgstr "Svolte di bot a çampe"
1755
1756 #: src/transitplugins/openTripPlanner.js:1248
1757 #, javascript-format
1758 msgid "Turn right on %s"
1759 msgstr "Svolte a diestre su %s"
1760
1761 #: src/transitplugins/openTripPlanner.js:1250
1762 msgid "Turn right"
1763 msgstr "Svolte a diestre"
1764
1765 #: src/transitplugins/openTripPlanner.js:1253
1766 #, javascript-format
1767 msgid "Turn slightly right on %s"
1768 msgstr "Svolte di pôc a diestre su %s"
1769
1770 #: src/transitplugins/openTripPlanner.js:1255
1771 msgid "Turn slightly right"
1772 msgstr "Svolte di pôc a diestre"
1773
1774 #: src/transitplugins/openTripPlanner.js:1258
1775 #, javascript-format
1776 msgid "Turn sharp right on %s"
1777 msgstr "Svolte di bot a diestre su %s"
1778
1779 #: src/transitplugins/openTripPlanner.js:1260
1780 msgid "Turn sharp right"
1781 msgstr "Svolte di bot a diestre"
1782
1783 #: src/transitplugins/openTripPlanner.js:1266
1784 #, javascript-format
1785 msgid "In the roundaboat, take exit %s"
1786 msgstr "Inte rotatorie, cjape la jessude %s"
1787
1788 #: src/transitplugins/openTripPlanner.js:1268
1789 #, javascript-format
1790 msgid "In the roundabout, take exit to %s"
1791 msgstr "Inte rotatorie, cjape la jessude par %s"
1792
1793 #: src/transitplugins/openTripPlanner.js:1270
1794 msgid "Take the roundabout"
1795 msgstr "Cjape la rotatorie"
1796
1797 #: src/transitplugins/openTripPlanner.js:1274
1798 #, javascript-format
1799 msgid "Take the elevator and get off at %s"
1800 msgstr "Cjape l'assensôr e jes a %s"
1801
1802 #: src/transitplugins/openTripPlanner.js:1276
1803 msgid "Take the elevator"
1804 msgstr "Cjape l'assensôr"
1805
1806 #: src/transitplugins/openTripPlanner.js:1280
1807 #, javascript-format
1808 msgid "Make a left u-turn onto %s"
1809 msgstr "Fâs une inversion a U a çampe su %s"
1810
1811 #: src/transitplugins/openTripPlanner.js:1282
1812 msgid "Make a left u-turn"
1813 msgstr "Fâs une inversion a U a çampe"
1814
1815 #: src/transitplugins/openTripPlanner.js:1285
1816 #, javascript-format
1817 msgid "Make a right u-turn onto %s"
1818 msgstr "Fâs une inversion a U a diestre su %s"
1819
1820 #: src/transitplugins/openTripPlanner.js:1287
1821 msgid "Make a rigth u-turn"
1822 msgstr "Fâs une inversion a U a diestre"
1823
1824 #~ msgid "Route search by OpenTripPlanner"
1825 #~ msgstr "Ricercje dal percors cun OpenTripPlanner"
17041826
17051827 #~ msgid "Open with another application"
17061828 #~ msgstr "Vierç cuntune altre aplicazion"
77 msgstr ""
88 "Project-Id-Version: gnome-maps gnome-3-28\n"
99 "Report-Msgid-Bugs-To: https://gitlab.gnome.org/GNOME/gnome-maps/issues\n"
10 "POT-Creation-Date: 2019-08-02 06:11+0000\n"
11 "PO-Revision-Date: 2019-08-06 17:30+0700\n"
12 "Last-Translator: Kukuh Syafaat <kukuhsyafaat@gnome.org>\n"
10 "POT-Creation-Date: 2019-10-30 21:01+0000\n"
11 "PO-Revision-Date: 2019-11-10 09:18+0700\n"
12 "Last-Translator: Andika Triwidada <andika@gmail.com>\n"
1313 "Language-Team: Indonesian <gnome@i15n.org>\n"
1414 "Language: id\n"
1515 "MIME-Version: 1.0\n"
1616 "Content-Type: text/plain; charset=UTF-8\n"
1717 "Content-Transfer-Encoding: 8bit\n"
18 "X-Generator: Poedit 2.2.3\n"
19 "Plural-Forms: nplurals=2; plural= n!=1;\n"
18 "X-Generator: Poedit 2.2.4\n"
19 "Plural-Forms: nplurals=2; plural=(n != 1);\n"
2020
2121 #: data/org.gnome.Maps.appdata.xml.in:6
2222 msgid "GNOME Maps"
5757 "\"Pub dekat Main Street, Boston\" atau \"Hotel dekat Alexanderplatz, Berlin"
5858 "\"."
5959
60 #: data/org.gnome.Maps.appdata.xml.in:59
60 #: data/org.gnome.Maps.appdata.xml.in:92
6161 msgid "The GNOME Project"
6262 msgstr "Proyek GNOME"
6363
7070 #.
7171 #. Translators: This is the program name.
7272 #: data/org.gnome.Maps.desktop.in:4 data/ui/main-window.ui:26
73 #: src/application.js:81 src/mainWindow.js:139 src/mainWindow.js:509
73 #: src/application.js:81 src/mainWindow.js:139 src/mainWindow.js:513
7474 msgid "Maps"
7575 msgstr "Peta"
7676
641641 msgid "Route search by GraphHopper"
642642 msgstr "Pencarian rute oleh GraphHopper"
643643
644 #: data/ui/sidebar.ui:296
645 msgid "Route search by OpenTripPlanner"
646 msgstr "Pencarian rute oleh OpenTripPlanner"
647
648 #: data/ui/sidebar.ui:369
649 msgid ""
650 "Routing itineraries for public transit is provided by GNOME\n"
651 "using timetable data obtained from transit companies or agencies.\n"
652 "The companies and agencies can not be held responsible for the results "
653 "shown.\n"
644 #: data/ui/sidebar.ui:364
645 msgid ""
646 "Routing itineraries for public transit is provided by third-party\n"
647 "services.\n"
654648 "GNOME can not guarantee correctness of the itineraries and schedules shown.\n"
649 "Note that some providers might not include all available modes of "
650 "transportation,\n"
651 "e.g. a national provider might not include airlines, and a local provider "
652 "could\n"
653 "miss regional trains.\n"
655654 "Names and brands shown are to be considered as registered trademarks when "
656655 "applicable."
657656 msgstr ""
658 "Rute perjalanan untuk angkutan umum disediakan oleh GNOME \n"
659 "menggunakan data jadwal yang diperoleh dari perusahaan angkutan atau agen.\n"
660 "Perusahaan dan agen tidak dapat bertanggung jawab atas hasil yang "
661 "ditampilkan.\n"
657 "Rute perjalanan untuk angkutan umum disediakan oleh layanan pihak ketiga.\n"
662658 "GNOME tidak dapat menjamin kebenaran dari perjalanan dan jadwal yang "
663659 "ditampilkan.\n"
660 "Perhatikan bawa beberapa penyedia mungkin tidak menyertakan semua mode "
661 "transportasi\n"
662 "yang tersedia, mis. sebuah penyedia nasional mungkin tidak menyertakan "
663 "penerbangan,\n"
664 "dan suatu penyedia lokal mungkin tidak punya jadwal kereta regional.\n"
664665 "Nama dan merek yang ditampilkan dianggap sebagai merek dagang terdaftar "
665666 "ketika berlaku."
666667
718719 msgid "Ferries"
719720 msgstr "Feri"
720721
722 #: data/ui/transit-options-panel.ui:152
723 msgid "Airplanes"
724 msgstr "Penerbangan"
725
721726 #: data/ui/user-location-bubble.ui:13 src/geoclue.js:118
722727 msgid "Current location"
723728 msgstr "Lokasi saat ini"
760765 msgid "Show the version of the program"
761766 msgstr "Tampilkan versi program"
762767
768 #: src/application.js:104
769 msgid "Ignore network availability"
770 msgstr "Abaikan ketersediaan jaringan"
771
763772 #: src/checkInDialog.js:167
764773 msgid "Select an account"
765774 msgstr "Pilih sebuah akun"
890899 msgid "unknown geometry"
891900 msgstr "geometri tak dikenal"
892901
893 #: src/graphHopper.js:112 src/openTripPlanner.js:652
902 #: src/graphHopper.js:112 src/transitPlan.js:192
894903 msgid "Route request failed."
895904 msgstr "Permintaan rute gagal."
896905
897 #: src/graphHopper.js:119 src/openTripPlanner.js:615
906 #: src/graphHopper.js:119 src/transitPlan.js:184
898907 msgid "No route found."
899908 msgstr "Rute tak ditemukan."
900909
901 #: src/graphHopper.js:207
910 #: src/graphHopper.js:207 src/transitplugins/openTripPlanner.js:1100
902911 msgid "Start!"
903912 msgstr "Mulai!"
904913
906915 msgid "All Layer Files"
907916 msgstr "Semua Berkas Lapisan"
908917
909 #: src/mainWindow.js:442
918 #: src/mainWindow.js:446
910919 msgid "Failed to connect to location service"
911920 msgstr "Gagal menyambung ke layanan lokasi"
912921
913 #: src/mainWindow.js:507
922 #: src/mainWindow.js:511
914923 msgid "translator-credits"
915924 msgstr ""
916 "Andika Triwidada <andika@gmail.com>, 2013-2016, 2018.\n"
925 "Andika Triwidada <andika@gmail.com>, 2013-2016, 2018, 2019.\n"
917926 "Kukuh Syafaat <kukuhsyafaat@gnome.org>, 2017, 2018, 2019."
918927
919 #: src/mainWindow.js:510
928 #: src/mainWindow.js:514
920929 msgid "A map application for GNOME"
921930 msgstr "Aplikasi peta bagi GNOME"
922931
923 #: src/mainWindow.js:521
932 #: src/mainWindow.js:525
924933 msgid "Copyright © 2011 – 2019 Red Hat, Inc. and The GNOME Maps authors"
925934 msgstr "Hak Cipta © 2011 – 2019 Red Hat, Inc. dan Penulis GNOME Peta"
926935
927 #: src/mainWindow.js:541
936 #: src/mainWindow.js:545
928937 #, javascript-format
929938 msgid "Map data by %s and contributors"
930939 msgstr "Data peta oleh %s dan kontributor"
934943 #. * the bare name of the tile provider, or a linkified URL if one
935944 #. * is available
936945 #.
937 #: src/mainWindow.js:557
946 #: src/mainWindow.js:561
938947 #, javascript-format
939948 msgid "Map tiles provided by %s"
940949 msgstr "Ubin peta disediakan oleh %s"
948957 #. * (i.e. "%2$s ... %1$s ..." for positioning the project URL
949958 #. * before the provider).
950959 #.
951 #: src/mainWindow.js:586
960 #: src/mainWindow.js:590
952961 #, javascript-format
953962 msgid "Search provided by %s using %s"
954963 msgstr "Pencarian disediakan oleh %s menggunakan %s"
955964
956 #: src/mapView.js:374
965 #: src/mapView.js:375
957966 msgid "File type is not supported"
958967 msgstr "Jenis berkas tak didukung"
959968
960 #: src/mapView.js:381
969 #: src/mapView.js:382
961970 msgid "Failed to open layer"
962971 msgstr "Gagal membuka layer"
963972
964 #: src/mapView.js:417
973 #: src/mapView.js:418
965974 msgid "Failed to open GeoURI"
966975 msgstr "Gagal membuka GeoURI"
967
968 #: src/openTripPlanner.js:648
969 msgid "No timetable data found for this route."
970 msgstr "Tidak ada data jadwal yang ditemukan untuk rute ini."
971976
972977 #. setting the status in session.cancel_message still seems
973978 #. to always give status IO_ERROR
12691274 msgid "Phone:"
12701275 msgstr "Telepon:"
12711276
1272 #: src/placeEntry.js:205
1277 #: src/placeEntry.js:209
12731278 msgid "Failed to parse Geo URI"
12741279 msgstr "Gagal mengurai URI Geo"
12751280
13821387 msgstr "gagal memuat berkas"
13831388
13841389 #. Translators: %s is a time expression with the format "%f h" or "%f min"
1385 #: src/sidebar.js:293
1390 #: src/sidebar.js:296
13861391 #, javascript-format
13871392 msgid "Estimated time: %s"
13881393 msgstr "Perkiraan waktu: %s"
1394
1395 #: src/sidebar.js:352
1396 #, javascript-format
1397 msgid "Itineraries provided by %s"
1398 msgstr "Jadwal disediakan oleh %s"
13891399
13901400 #. Translators: this is a format string indicating instructions
13911401 #. * starting a journey at the address given as the parameter
14191429 msgid "Arrive at %s"
14201430 msgstr "Tiba di %s"
14211431
1422 #: src/transit.js:77
1432 #: src/transit.js:77 src/transitplugins/openTripPlanner.js:1113
14231433 msgid "Arrive"
14241434 msgstr "Tiba"
14251435
14511461 #. * Translators: this is a format string giving the equivalent to
14521462 #. * "may 29" according to the current locale's convensions.
14531463 #.
1454 #: src/transitOptionsPanel.js:141
1464 #: src/transitOptionsPanel.js:143
14551465 msgctxt "month-day-date"
14561466 msgid "%b %e"
14571467 msgstr "%e %b"
1468
1469 #: src/transitPlan.js:188
1470 msgid "No timetable data found for this route."
1471 msgstr "Tidak ada data jadwal yang ditemukan untuk rute ini."
1472
1473 #: src/transitPlan.js:196
1474 msgid "No provider found for this route."
1475 msgstr "Tidak ada penyedia yang ditemukan untuk rute ini."
14581476
14591477 #. Translators: this is a format string for showing a departure and
14601478 #. * arrival time, like:
14611479 #. * "12:00 – 13:03" where the placeholder %s are the actual times,
14621480 #. * these could be rearranged if needed.
14631481 #.
1464 #: src/transitPlan.js:254
1482 #: src/transitPlan.js:313
14651483 #, javascript-format
14661484 msgid "%s – %s"
14671485 msgstr "%s – %s"
14701488 #. * less than an hour, with only the minutes part, using plural forms
14711489 #. * as appropriate
14721490 #.
1473 #: src/transitPlan.js:281
1491 #: src/transitPlan.js:340
14741492 #, javascript-format
14751493 msgid "%d minute"
14761494 msgid_plural "%d minutes"
14811499 #. * where the duration is an exact number of hours (i.e. no
14821500 #. * minutes part), using plural forms as appropriate
14831501 #.
1484 #: src/transitPlan.js:292
1502 #: src/transitPlan.js:351
14851503 #, javascript-format
14861504 msgid "%d hour"
14871505 msgid_plural "%d hours"
14921510 #. * where the duration contains an hour and minute part, it's
14931511 #. * pluralized on the hours part
14941512 #.
1495 #: src/transitPlan.js:298
1513 #: src/transitPlan.js:357
14961514 #, javascript-format
14971515 msgid "%d:%02d hour"
14981516 msgid_plural "%d:%02d hours"
15051523 #. * "12:00–13:03" where the placeholder %s are the actual times,
15061524 #. * these could be rearranged if needed.
15071525 #.
1508 #: src/transitPlan.js:651
1526 #: src/transitPlan.js:750
15091527 #, javascript-format
15101528 msgid "%s–%s"
15111529 msgstr "%s–%s"
16591677 msgstr "pelayanan"
16601678
16611679 #. Translators: Accuracy of user location information
1662 #: src/utils.js:220
1680 #: src/utils.js:229
16631681 msgid "Unknown"
16641682 msgstr "Tak diketahui"
16651683
16661684 #. Translators: Accuracy of user location information
1667 #: src/utils.js:223
1685 #: src/utils.js:232
16681686 msgid "Exact"
16691687 msgstr "Eksak"
16701688
1671 #: src/utils.js:281
1689 #: src/utils.js:290
16721690 #, javascript-format
16731691 msgid "%f h"
16741692 msgstr "%f j"
16751693
1676 #: src/utils.js:283
1694 #: src/utils.js:292
16771695 #, javascript-format
16781696 msgid "%f min"
16791697 msgstr "%f men"
16801698
1681 #: src/utils.js:285
1699 #: src/utils.js:294
16821700 #, javascript-format
16831701 msgid "%f s"
16841702 msgstr "%f s"
16851703
16861704 #. Translators: This is a distance measured in kilometers
1687 #: src/utils.js:296
1705 #: src/utils.js:305
16881706 #, javascript-format
16891707 msgid "%s km"
16901708 msgstr "%s km"
16911709
16921710 #. Translators: This is a distance measured in meters
1693 #: src/utils.js:299
1711 #: src/utils.js:308
16941712 #, javascript-format
16951713 msgid "%s m"
16961714 msgstr "%s m"
16971715
16981716 #. Translators: This is a distance measured in miles
1699 #: src/utils.js:307
1717 #: src/utils.js:316
17001718 #, javascript-format
17011719 msgid "%s mi"
17021720 msgstr "%s mil"
17031721
17041722 #. Translators: This is a distance measured in feet
1705 #: src/utils.js:310
1723 #: src/utils.js:319
17061724 #, javascript-format
17071725 msgid "%s ft"
17081726 msgstr "%s kaki"
17091727
1710 #~ msgid ""
1711 #~ "Show live-updated thumbnails for the street/aerial layer switcher, "
1712 #~ "instead of (outdated) hard-code thumbnails"
1713 #~ msgstr ""
1714 #~ "Tampilkan gambar mini yang diperbarui secara langsung untuk pengalih "
1715 #~ "lapisan jalan/udara, bukan gambar mini kode lama"
1716
1717 #~ msgid "Move app menu to the headerbar"
1718 #~ msgstr "Pindahkan menu aplikasi ke bilah tajuk"
1719
1720 #~ msgid "Updated application icon"
1721 #~ msgstr "Ikon aplikasi yang dimutakhirkan"
1722
1723 #~ msgid "Press enter to search"
1724 #~ msgstr "Tekan enter untuk mencari"
1725
1726 #~ msgid "org.gnome.Maps"
1727 #~ msgstr "org.gnome.Maps"
1728
1729 #~ msgid "Quit"
1730 #~ msgstr "Keluar"
1731
1732 #~ msgid "OK"
1733 #~ msgstr "OK"
1728 #: src/transitplugins/openTripPlanner.js:1174
1729 #, javascript-format
1730 msgid "Continue on %s"
1731 msgstr "Lanjutkan pada %s"
1732
1733 #: src/transitplugins/openTripPlanner.js:1176
1734 msgid "Continue"
1735 msgstr "Lanjutkan"
1736
1737 #: src/transitplugins/openTripPlanner.js:1179
1738 #, javascript-format
1739 msgid "Turn left on %s"
1740 msgstr "Belok kiri di %s"
1741
1742 #: src/transitplugins/openTripPlanner.js:1181
1743 msgid "Turn left"
1744 msgstr "Belok kiri"
1745
1746 #: src/transitplugins/openTripPlanner.js:1184
1747 #, javascript-format
1748 msgid "Turn slightly left on %s"
1749 msgstr "Belok sedikit ke kiri pada %s"
1750
1751 #: src/transitplugins/openTripPlanner.js:1186
1752 msgid "Turn slightly left"
1753 msgstr "Belok sedikit ke kiri"
1754
1755 #: src/transitplugins/openTripPlanner.js:1189
1756 #, javascript-format
1757 msgid "Turn sharp left on %s"
1758 msgstr "Belok kiri tajam pada %s"
1759
1760 #: src/transitplugins/openTripPlanner.js:1191
1761 msgid "Turn sharp left"
1762 msgstr "Belok kiri tajam"
1763
1764 #: src/transitplugins/openTripPlanner.js:1194
1765 #, javascript-format
1766 msgid "Turn right on %s"
1767 msgstr "Belok kanan pada %s"
1768
1769 #: src/transitplugins/openTripPlanner.js:1196
1770 msgid "Turn right"
1771 msgstr "Belok kanan"
1772
1773 #: src/transitplugins/openTripPlanner.js:1199
1774 #, javascript-format
1775 msgid "Turn slightly right on %s"
1776 msgstr "Belok sedikit ke kanan pada %s"
1777
1778 #: src/transitplugins/openTripPlanner.js:1201
1779 msgid "Turn slightly right"
1780 msgstr "Belok sedikit ke kanan"
1781
1782 #: src/transitplugins/openTripPlanner.js:1204
1783 #, javascript-format
1784 msgid "Turn sharp right on %s"
1785 msgstr "Belok kanan tajam pada %s"
1786
1787 #: src/transitplugins/openTripPlanner.js:1206
1788 msgid "Turn sharp right"
1789 msgstr "Belok kanan tajam"
1790
1791 #: src/transitplugins/openTripPlanner.js:1212
1792 #, javascript-format
1793 msgid "In the roundaboat, take exit %s"
1794 msgstr "Di bundaran, ambil jalur keluar %s"
1795
1796 #: src/transitplugins/openTripPlanner.js:1214
1797 #, javascript-format
1798 msgid "In the roundabout, take exit to %s"
1799 msgstr "Di bundaran, ambil jalur ke %s"
1800
1801 #: src/transitplugins/openTripPlanner.js:1216
1802 msgid "Take the roundabout"
1803 msgstr "Ambil bundaran"
1804
1805 #: src/transitplugins/openTripPlanner.js:1220
1806 #, javascript-format
1807 msgid "Take the elevator and get off at %s"
1808 msgstr "Naik lift dan turun di %s"
1809
1810 #: src/transitplugins/openTripPlanner.js:1222
1811 msgid "Take the elevator"
1812 msgstr "Naik lift"
1813
1814 #: src/transitplugins/openTripPlanner.js:1226
1815 #, javascript-format
1816 msgid "Make a left u-turn onto %s"
1817 msgstr "Putar balik kiri ke %s"
1818
1819 #: src/transitplugins/openTripPlanner.js:1228
1820 msgid "Make a left u-turn"
1821 msgstr "Putar balik kiri"
1822
1823 #: src/transitplugins/openTripPlanner.js:1231
1824 #, javascript-format
1825 msgid "Make a right u-turn onto %s"
1826 msgstr "Putar balik kanan ke %s"
1827
1828 #: src/transitplugins/openTripPlanner.js:1233
1829 msgid "Make a rigth u-turn"
1830 msgstr "Putar balik kanan"
9696 this.add_main_option('version', 'v'.charCodeAt(0), GLib.OptionFlags.NONE, GLib.OptionArg.NONE,
9797 _("Show the version of the program"), null);
9898
99 this.add_main_option('force-online',
100 0,
101 GLib.OptionFlags.NONE,
102 GLib.OptionArg.NONE,
103 _("Ignore network availability"),
104 null);
105
99106 this.connect('handle-local-options', (app, options) => {
100107 if (options.contains('local')) {
101108 let variant = options.lookup_value('local', null);
107114 * leaving the running instance unaffected
108115 */
109116 return 0;
117 } else if (options.contains('force-online')) {
118 this._forceOnline = true;
110119 }
111120
112121 return -1;
114123 }
115124
116125 _checkNetwork() {
117 this.connected = networkMonitor.connectivity === Gio.NetworkConnectivity.FULL;
126 this.connected =
127 this._forceOnline ||
128 networkMonitor.connectivity === Gio.NetworkConnectivity.FULL;
118129 }
119130
120131 _showContact(id) {
1919 */
2020
2121 /* Minimum contrast ratio for foreground/background color for i.e. route labels */
22 const MIN_CONTRAST_RATIO = 3.0;
22 const MIN_CONTRAST_RATIO = 2.0;
2323
2424 /**
2525 * Parses a given color component index (0: red, 1: green, 2: blue)
143143
144144 _readService() {
145145 let graphHopperGeocode = Service.getService().graphHopperGeocode;
146 let locale = GLib.get_language_names()[0];
147 // the last item returned is the "bare" language
148 this._language = GLib.get_locale_variants(locale).slice(-1)[0];
146 this._language = Utils.getLanguage();
149147
150148 if (graphHopperGeocode) {
151149 this._baseUrl = graphHopperGeocode.baseUrl;
0 /* -*- Mode: JS2; indent-tabs-mode: nil; js2-basic-offset: 4 -*- */
1 /* vim: set et ts=4 sw=4: */
2 /*
3 * Copyright (c) 2019 Marcus Lundblad
4 *
5 * GNOME Maps is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by the
7 * Free Software Foundation; either version 2 of the License, or (at your
8 * option) any later version.
9 *
10 * GNOME Maps is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13 * for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with GNOME Maps; if not, see <http://www.gnu.org/licenses/>.
17 *
18 * Author: Marcus Lundblad <ml@update.uu.se>
19 */
20
21 /**
22 * Utilities to use GraphHopper to perform walking routes for use in
23 * transit itineraries, for plugins not natively supporting turn-by-turn
24 * routing for walking legs
25 */
26
27 const Champlain = imports.gi.Champlain;
28
29 const Application = imports.application;
30 const Location = imports.location;
31 const Place = imports.place;
32 const RouteQuery = imports.routeQuery;
33 const TransitPlan = imports.transitPlan;
34
35 /* Creates a new walking leg given start and end places, and a route
36 * obtained from GraphHopper. If the route is undefined (which happens if
37 * GraphHopper failed to obtain a walking route, approximate it with a
38 * straight line. */
39 function createWalkingLeg(from, to, fromName, toName, route) {
40 let fromLocation = from.place.location;
41 let toLocation = to.place.location;
42 let fromCoordinate = [fromLocation.latitude, fromLocation.longitude];
43 let toCoordinate = [toLocation.latitude, toLocation.longitude];
44 let polyline = route ? route.path :
45 createStraightPolyline(fromLocation, toLocation);
46 let distance = route ? route.distance :
47 fromLocation.get_distance_from(toLocation) * 1000;
48 /* as an estimate for approximated straight-line walking legs,
49 * assume a speed of 1 m/s to allow some extra time */
50 let duration = route ? route.time / 1000 : distance;
51 let walkingInstructions = route ? route.turnPoints : null;
52
53 return new TransitPlan.Leg({ fromCoordinate: fromCoordinate,
54 toCoordinate: toCoordinate,
55 from: fromName,
56 to: toName,
57 isTransit: false,
58 polyline: polyline,
59 duration: duration,
60 distance: distance,
61 walkingInstructions: walkingInstructions });
62 }
63
64 // create a straight-line "as the crow flies" polyline between two places
65 function createStraightPolyline(fromLoc, toLoc) {
66 return [new Champlain.Coordinate({ latitude: fromLoc.latitude,
67 longitude: fromLoc.longitude }),
68 new Champlain.Coordinate({ latitude: toLoc.latitude,
69 longitude: toLoc.longitude })];
70 }
71
72 var _walkingRoutes = [];
73
74 /* fetches walking route and stores the route for the given coordinate
75 * pair to avoid requesting the same route over and over from GraphHopper
76 */
77 function fetchWalkingRoute(points, callback) {
78 let index = points[0].place.location.latitude + ',' +
79 points[0].place.location.longitude + ';' +
80 points[1].place.location.latitude + ',' +
81 points[1].place.location.longitude;
82 let route = _walkingRoutes[index];
83
84 if (!route) {
85 Application.routingDelegator.graphHopper.fetchRouteAsync(points,
86 RouteQuery.Transportation.PEDESTRIAN,
87 (newRoute) => {
88 _walkingRoutes[index] = newRoute;
89 callback(newRoute);
90 });
91 } else {
92 callback(route);
93 }
94 }
95
96 // create a query point from a bare coordinate (lat, lon pair)
97 function createQueryPointForCoord(coord) {
98 let location = new Location.Location({ latitude: coord[0],
99 longitude: coord[1],
100 accuracy: 0 });
101 let place = new Place.Place({ location: location });
102 let point = new RouteQuery.QueryPoint();
103
104 point.place = place;
105 return point;
106 }
107
108 /**
109 * Refine itineraries with walking legs retrieved from GraphHopper.
110 * Intended for use by transit plugins where the source API doesn't give
111 * full walking turn-by-turn routing
112 */
113 function addWalkingToItineraries(itineraries, callback) {
114 _addWalkingToItinerariesRecursive(itineraries, 0, callback);
115 }
116
117 function _addWalkingToItinerariesRecursive(itineraries, index, callback) {
118 if (index === itineraries.length) {
119 callback();
120 } else {
121 let itinerary = itineraries[index];
122
123 _addWalkingToLegsRecursive(itinerary.legs, 0, () => {
124 _addWalkingToItinerariesRecursive(itineraries, index + 1, callback);
125 });
126 }
127 }
128
129 function _addWalkingToLegsRecursive(legs, index, callback) {
130 if (index === legs.length) {
131 callback();
132 } else {
133 let leg = legs[index];
134
135 if (!leg.transit) {
136 let from = createQueryPointForCoord(leg.fromCoordinate);
137 let to = createQueryPointForCoord(leg.toCoordinate);
138
139 fetchWalkingRoute([from, to], (route) => {
140 if (route) {
141 let duration = route.time / 1000;
142
143 /* for walking legs not in the start or end
144 * only replace with the retrieved one if it's not
145 * longer in duration that the previous (straight-line)
146 * one.
147 */
148 if (index === 0 || index === legs.length - 1 ||
149 duration <= leg.duration) {
150 leg.distance = route.distance;
151 leg.walkingInstructions = route.turnPoints;
152 leg.polyline = route.path;
153 }
154 }
155
156 _addWalkingToLegsRecursive(legs, index + 1, callback);
157 });
158 } else {
159 _addWalkingToLegsRecursive(legs, index + 1, callback);
160 }
161 }
162 }
261261
262262 _connectRouteSignals() {
263263 let route = Application.routingDelegator.graphHopper.route;
264 let transitPlan = Application.routingDelegator.openTripPlanner.plan;
264 let transitPlan = Application.routingDelegator.transitRouter.plan;
265265 let query = Application.routeQuery;
266266
267267 route.connect('update', () => {
+0
-1189
src/openTripPlanner.js less more
0 /* -*- Mode: JS2; indent-tabs-mode: nil; js2-basic-offset: 4 -*- */
1 /* vim: set et ts=4 sw=4: */
2 /*
3 * Copyright (c) 2017 Marcus Lundblad
4 *
5 * GNOME Maps is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by the
7 * Free Software Foundation; either version 2 of the License, or (at your
8 * option) any later version.
9 *
10 * GNOME Maps is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13 * for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with GNOME Maps; if not, see <http://www.gnu.org/licenses/>.
17 *
18 * Author: Marcus Lundblad <ml@update.uu.se>
19 */
20
21 const Champlain = imports.gi.Champlain;
22 const GLib = imports.gi.GLib;
23 const Soup = imports.gi.Soup;
24
25 const EPAF = imports.epaf;
26 const HTTP = imports.http;
27 const Location = imports.location;
28 const Place = imports.place;
29 const RouteQuery = imports.routeQuery;
30 const Service = imports.service;
31 const TransitPlan = imports.transitPlan;
32 const Utils = imports.utils;
33
34 /**
35 * This module implements the interface for communicating with an OpenTripPlanner
36 * server instance.
37 * The code is somwhat intricate, because it assumes running OpenTripPlanner with
38 * only transit data and relies on calling out to GraphHopper to do turn-by-turn-
39 * based routing for the walking portions, thus it's based on an asynchronous
40 * recursive pattern. The reason for running OpenTripPlanner with only transit
41 * data is that prior experiments has shown that OpenTripPlanner with full OSM
42 * data doesn't scale well beyong single cities, and GraphHopper has already
43 * given us good results before.
44 *
45 * There is two entry points for obtaining routes, one which is called by the
46 * routing delegator when the query is being modified (fetchFirstResults()),
47 * and the other being called when requesting additional results (later or
48 * earlier alternatives depending on search criteria) (fetchMoreResults())
49 * These call into an entry point function "_fatchRoute()" which first calls
50 * out to the function "_fetchRouters()" which calls out to the server to update
51 * the cached router list if needed (routers are the OpenTripPlanner terminology
52 * for an isolated graph, routing can not occur between graphs).
53 * In the callback from _fetchRouters, an array of suitable routers (covering
54 * start and end coordinates for the desired route) is processed.
55 * "_fetchRoutes()" is called, which will do asynchronous recursive call for
56 * each router obtained earlier.
57 * "_fetchRoutesForRouter()" is called on each router, which in turn
58 * asyncronously calls "_fetchTransitStops()" to get closest transit stop for
59 * each of the query point, this function will involve OpenTripPlanner calls to
60 * find stops within a search circle around the coordinate and then calls out
61 * to GraphHopper to find the actual walking distance and selects the closest
62 * stop.
63 * In the callback for the results of _fetchTransitStops() the actual call to
64 * OpenTripPlanner is made.
65 * The callback for "_fetchRoutes()" will in turn reformat the resulting
66 * itineraries obtained from OpenTripPlanner into our internal format specified
67 * in the TransitPlan module and calls "_recalculateItineraries()" which will
68 * traverse these and do yet another recursive asyncrous pass over these results
69 * to refine the results with actual walking routes obtained from GraphHopper,
70 * since the results from our OpenTripPlanner instance with only transit data
71 * will result in walking legs which use "as the crow flies" routes for walking.
72 * It will also clean up the results by pruning out pointless transit legs, such
73 * as taking transit very short distances when almost reaching the target.
74 *
75 * API docs for OpenTripPlanner can be found at: http://dev.opentripplanner.org/apidoc/1.0.0/
76 */
77
78 /* timeout after which the routers data is considered stale and we will force
79 * a reload (24 hours)
80 */
81 const ROUTERS_TIMEOUT = 24 * 60 * 60 * 1000;
82
83 /* minimum distance when an explicit walk route will be requested to suppliment
84 * the transit route
85 */
86 const MIN_WALK_ROUTING_DISTANCE = 100;
87
88 /* minimum distance of a transit leg, below which we would replace the leg with
89 * walking if the leg is the first or last
90 */
91 const MIN_TRANSIT_LEG_DISTANCE = 300;
92
93 /* maximum walking distance for a potential replacement of a beginning or ending
94 * transit leg */
95 const MAX_WALK_OPTIMIZATION_DISTANCE = 1000;
96
97 /* maximum distance difference for performing a replacement of a beginning or
98 * ending transit leg with a walking leg
99 */
100 const MAX_WALK_OPTIMIZATION_DISTANCE_DIFFERENCE = 500;
101
102 /* minimum acceptable time margin when recalculating walking legs in the middle
103 * of an itinerary
104 */
105 const MIN_INTERMEDIATE_WALKING_SLACK = 60;
106
107 /* maximum walking distance, filter out itineraries containing walking legs
108 * whith longer walking after refined by GraphHopper
109 */
110 const MAX_WALKING_DISTANCE = 2000;
111
112 // maximum radius to search for stops
113 const STOP_SEARCH_RADIUS = 2000;
114
115 // maximum number of transit stops to consider as candidates for start/end points
116 const NUM_STOPS_TO_TRY = 5;
117
118 // gap to use when fetching additional routes
119 const GAP_BEFORE_MORE_RESULTS = 120;
120
121 var OpenTripPlanner = class OpenTripPlanner {
122
123 constructor(params) {
124 this._session = new Soup.Session();
125 /* initially set routers as updated far back in the past to force
126 * a download when first request
127 */
128 this._routersUpdatedTimestamp = 0;
129 this._query = params.query;
130 this._graphHopper = params.graphHopper;
131 this._plan = new TransitPlan.Plan();
132 this._baseUrl = this._getBaseUrl();
133 this._walkingRoutes = [];
134 this._extendPrevious = false;
135 }
136
137 get plan() {
138 return this._plan;
139 }
140
141 get enabled() {
142 return this._baseUrl !== null;
143 }
144
145 fetchFirstResults() {
146 this._extendPrevious = false;
147 this._fetchRoute();
148 }
149
150 fetchMoreResults() {
151 this._extendPrevious = true;
152 this._fetchRoute();
153 }
154
155 _getBaseUrl() {
156 let debugUrl = GLib.getenv('OTP_BASE_URL');
157
158 if (debugUrl) {
159 return debugUrl;
160 } else {
161 let otp = Service.getService().openTripPlanner
162
163 if (otp && otp.baseUrl) {
164 return otp.baseUrl;
165 } else {
166 Utils.debug('No OpenTripPlanner URL defined in service file');
167 return null;
168 }
169 }
170 }
171
172 _getRouterUrl(router) {
173 if (!router || router.length === 0)
174 router = 'default';
175
176 return this._baseUrl + '/routers/' + router;
177 }
178
179 _fetchRouters(callback) {
180 let currentTime = (new Date()).getTime();
181
182 if (currentTime - this._routersUpdatedTimestamp < ROUTERS_TIMEOUT) {
183 callback(true);
184 } else {
185 let uri = new Soup.URI(this._baseUrl + '/routers');
186 let request = new Soup.Message({ method: 'GET', uri: uri });
187
188 request.request_headers.append('Accept', 'application/json');
189 this._session.queue_message(request, (obj, message) => {
190 if (message.status_code !== Soup.Status.OK) {
191 callback(false);
192 return;
193 }
194
195 try {
196 this._routers = JSON.parse(message.response_body.data);
197 this._routersUpdatedTimestamp = (new Date()).getTime();
198 callback(true);
199 } catch (e) {
200 Utils.debug('Failed to parse router information');
201 callback(false);
202 }
203 });
204 }
205 }
206
207 _getRoutersForPlace(place) {
208 let routers = [];
209
210 this._routers.routerInfo.forEach((routerInfo) => {
211 /* TODO: only check bounding rectangle for now
212 * should we try to do a finer-grained check using the bounding
213 * polygon (if OTP gives one for the routers).
214 * And should we add some margins to allow routing from just outside
215 * a network (walking distance)?
216 */
217 if (place.location.latitude >= routerInfo.lowerLeftLatitude &&
218 place.location.latitude <= routerInfo.upperRightLatitude &&
219 place.location.longitude >= routerInfo.lowerLeftLongitude &&
220 place.location.longitude <= routerInfo.upperRightLongitude)
221 routers.push(routerInfo.routerId);
222 });
223
224 return routers;
225 }
226
227 /* Note: this is theoretically slow (O(n*m)), but we will have filtered
228 * possible routers for the starting and ending query point, so they should
229 * be short (in many cases just one element)
230 */
231 _routerIntersection(routers1, routers2) {
232 return routers1.filter(function(n) {
233 return routers2.indexOf(n) != -1;
234 });
235 }
236
237 _getMode(routeType) {
238 switch (routeType) {
239 case TransitPlan.RouteType.TRAM:
240 return 'TRAM';
241 case TransitPlan.RouteType.TRAIN:
242 return 'RAIL';
243 case TransitPlan.RouteType.SUBWAY:
244 return 'SUBWAY';
245 case TransitPlan.RouteType.BUS:
246 return 'BUS';
247 case TransitPlan.RouteType.FERRY:
248 return 'FERRY';
249 default:
250 throw new Error('unhandled route type');
251 }
252 }
253
254 _getModes(options) {
255 let modes = options.transitTypes.map((transitType) => {
256 return this._getMode(transitType);
257 });
258
259 return modes.join(',');
260 }
261
262 _selectBestStopRecursive(stops, index, stopIndex, callback) {
263 if (index < stops.length) {
264 let points = this._query.filledPoints;
265 let stop = stops[index];
266 let stopPoint =
267 this._createQueryPointForCoord([stop.lat, stop.lon]);
268
269 if (stops[0].dist < 100) {
270 /* if the stop is close enough to the intended point, just
271 * return the top most from the the original query */
272 this._selectBestStopRecursive(stops, index + 1, stopIndex,
273 callback);
274 } else if (stopIndex === 0) {
275 this._fetchWalkingRoute([points[0], stopPoint],
276 (route) => {
277 /* if we couldn't find an exact walking route, go with the
278 * "as the crow flies" distance */
279 if (route)
280 stop.dist = route.distance;
281 this._selectBestStopRecursive(stops, index + 1, stopIndex,
282 callback);
283 });
284 } else if (stopIndex === points.length - 1) {
285 this._fetchWalkingRoute([stopPoint, points.last()], (route) => {
286 if (route)
287 stop.dist = route.distance;
288 this._selectBestStopRecursive(stops, index + 1, stopIndex,
289 callback);
290 });
291 } else {
292 /* for intermediate stops just return the one geographically
293 * closest */
294 this._selectBestStopRecursive(stops, index + 1, stopIndex,
295 callback);
296 }
297 } else {
298 /* re-sort stops by distance and select the closest after refining
299 * distances */
300 stops.sort(this._sortTransitStops);
301 Utils.debug('refined stops: ');
302 stops.forEach((stop) => Utils.debug(JSON.stringify(stop, '', 2)));
303 callback(stops[0]);
304 }
305 }
306
307 /* stopIndex here is the index of stop (i.e. starting point, intermediate
308 * stop, final stop
309 */
310 _selectBestStop(stops, stopIndex, callback) {
311 this._selectBestStopRecursive(stops, 0, stopIndex, callback);
312 }
313
314 _sortTransitStops(s1, s2) {
315 return s1.dist > s2.dist;
316 }
317
318 _fetchRoutesForStop(router, stop, callback) {
319 let query = new HTTP.Query();
320 let uri = new Soup.URI(this._getRouterUrl(router) +
321 '/index/stops/' + stop.id + '/routes');
322 let request = new Soup.Message({ method: 'GET', uri: uri });
323
324 request.request_headers.append('Accept', 'application/json');
325 this._session.queue_message(request, (obj, message) => {
326 if (message.status_code !== Soup.Status.OK) {
327 Utils.debug('Failed to get routes for stop');
328 this._reset();
329 } else {
330 let routes = JSON.parse(message.response_body.data);
331
332 Utils.debug('Routes for stop: ' + stop + ': ' + JSON.stringify(routes));
333 callback(routes);
334 }
335 });
336 }
337
338 _routeMatchesSelectedModes(route) {
339 let desiredTransitTypes = this._query.transitOptions.transitTypes;
340
341 for (let i = 0; i < desiredTransitTypes.length; i++) {
342 let type = desiredTransitTypes[i];
343
344 if (type === TransitPlan.RouteType.TRAM && route.mode === 'TRAM')
345 return true;
346 else if (type === TransitPlan.RouteType.SUBWAY && route.mode === 'SUBWAY')
347 return true;
348 else if (type === TransitPlan.RouteType.TRAIN && route.mode === 'RAIL')
349 return true;
350 else if (type === TransitPlan.RouteType.BUS &&
351 (route.mode === 'BUS' || route.mode === 'TAXI'))
352 return true;
353 else if (type === TransitPlan.RouteType.FERRY && route.mode === 'FERRY')
354 return true;
355 }
356
357 return false;
358 }
359
360 _filterStopsRecursive(router, stops, index, filteredStops, callback) {
361 if (index < stops.length) {
362 let stop = stops[index];
363
364 this._fetchRoutesForStop(router, stop, (routes) => {
365 for (let i = 0; i < routes.length; i++) {
366 let route = routes[i];
367
368 if (this._routeMatchesSelectedModes(route)) {
369 filteredStops.push(stop);
370 break;
371 }
372 }
373 this._filterStopsRecursive(router, stops, index + 1,
374 filteredStops, callback);
375 });
376 } else {
377 callback(filteredStops);
378 }
379 }
380
381 _filterStops(router, stops, callback) {
382 this._filterStopsRecursive(router, stops, 0, [], callback);
383 }
384
385 _fetchTransitStopsRecursive(router, index, result, callback) {
386 let points = this._query.filledPoints;
387
388 if (index < points.length) {
389 let point = points[index];
390 let params = { lat: point.place.location.latitude,
391 lon: point.place.location.longitude,
392 radius: STOP_SEARCH_RADIUS };
393 let query = new HTTP.Query(params);
394 let uri = new Soup.URI(this._getRouterUrl(router) +
395 '/index/stops?' + query.toString());
396 let request = new Soup.Message({ method: 'GET', uri: uri });
397
398 request.request_headers.append('Accept', 'application/json');
399 this._session.queue_message(request, (obj, message) => {
400 if (message.status_code !== Soup.Status.OK) {
401 Utils.debug('Failed to get stop for search point ' + point);
402 this._reset();
403 } else {
404 let stops = JSON.parse(message.response_body.data);
405
406 if (stops.length === 0) {
407 Utils.debug('No suitable stop found from router');
408 callback(null);
409 return;
410 }
411
412 if (this._query.transitOptions.showAllTransitTypes) {
413 stops.sort(this._sortTransitStops);
414 stops = stops.splice(0, NUM_STOPS_TO_TRY);
415
416 Utils.debug('stops: ' + JSON.stringify(stops, '', 2));
417 this._selectBestStop(stops, index, (stop) => {
418 result.push(stop);
419 this._fetchTransitStopsRecursive(router, index + 1,
420 result, callback);
421 });
422 } else {
423 this._filterStops(router, stops, (filteredStops) => {
424 filteredStops.sort(this._sortTransitStops);
425 filteredStops = filteredStops.splice(0, NUM_STOPS_TO_TRY);
426
427 if (filteredStops.length === 0) {
428 Utils.debug('No suitable stop found using selected transit modes');
429 callback(null);
430 return;
431 }
432
433 this._selectBestStop(filteredStops, index, (stop) => {
434 result.push(stop);
435 this._fetchTransitStopsRecursive(router, index + 1,
436 result, callback);
437 });
438 });
439 }
440 }
441 });
442 } else {
443 callback(result);
444 }
445 }
446
447 _fetchTransitStops(router, callback) {
448 this._fetchTransitStopsRecursive(router, 0, [], callback);
449 }
450
451 // get a time suitably formatted for the OpenTripPlanner query param
452 _formatTime(time, offset) {
453 let utcTimeWithOffset = (time + offset) / 1000;
454 let date = GLib.DateTime.new_from_unix_utc(utcTimeWithOffset);
455
456 return date.format('%R');
457 }
458
459 // get a date suitably formatted for the OpenTripPlanner query param
460 _formatDate(time, offset) {
461 let utcTimeWithOffset = (time + offset) / 1000;
462 let date = GLib.DateTime.new_from_unix_utc(utcTimeWithOffset);
463
464 return date.format('%F');
465 }
466
467 // create parameter map for the request, given query and options
468 _createParams(stops) {
469 let params = { fromPlace: stops[0].id,
470 toPlace: stops.last().id };
471 let intermediatePlaces = [];
472
473 for (let i = 1; i < stops.length - 1; i++) {
474 intermediatePlaces.push(stops[i].id);
475 }
476 if (intermediatePlaces.length > 0)
477 params.intermediatePlaces = intermediatePlaces;
478
479 params.numItineraries = 5;
480 params.showIntermediateStops = true;
481 /* set walking speed for transfers to a slightly lower value to
482 * compensate for running OTP with only transit data, giving straight-
483 * line walking paths
484 */
485 params.walkSpeed = 1.0;
486
487 let time = this._query.time;
488 let date = this._query.date;
489
490 if (this._extendPrevious) {
491 let itineraries = this.plan.itineraries;
492 let lastItinerary = itineraries.last();
493 let time;
494 let offset;
495
496 if (this._query.arriveBy) {
497 time = lastItinerary.transitArrivalTime -
498 GAP_BEFORE_MORE_RESULTS * 1000;
499 offset = lastItinerary.transitArrivalTimezoneOffset;
500 } else {
501 time = lastItinerary.transitDepartureTime +
502 GAP_BEFORE_MORE_RESULTS * 1000;
503 offset = lastItinerary.transitDepartureTimezoneOffset;
504 }
505
506 params.time = this._formatTime(time, offset);
507 params.date = this._formatDate(time, offset);
508 } else {
509 if (time) {
510 params.time = time;
511 /* it seems OTP doesn't like just setting a time, so if the query
512 * doesn't specify a date, go with today's date
513 */
514 if (!date) {
515 let dateTime = GLib.DateTime.new_now_local();
516
517 params.date = dateTime.format('%F');
518 }
519 }
520
521 if (date)
522 params.date = date;
523 }
524
525 if (this._query.arriveBy)
526 params.arriveBy = true;
527
528 let options = this._query.transitOptions;
529 if (options && !options.showAllTransitTypes)
530 params.mode = this._getModes(options);
531
532 return params;
533 }
534
535 _fetchRoutesForRouter(router, callback) {
536 this._fetchTransitStops(router, (stops) => {
537 let points = this._query.filledPoints;
538
539 if (!stops) {
540 callback(null);
541 return;
542 }
543
544 /* if there's only a start and end stop (no intermediate stops)
545 * and those stops are identical, reject the routing, since this
546 * means there would be no point in transit, and OTP would give
547 * some bizarre option like boarding transit, go one stop and then
548 * transfer to go back the same route
549 */
550 if (stops.length === 2 && stops[0].id === stops[1].id) {
551 callback(null);
552 return;
553 }
554
555 let params = this._createParams(stops);
556 let query = new HTTP.Query(params);
557 let uri = new Soup.URI(this._getRouterUrl(router) + '/plan?' +
558 query.toString());
559 let request = new Soup.Message({ method: 'GET', uri: uri });
560
561 request.request_headers.append('Accept', 'application/json');
562 this._session.queue_message(request, (obj, message) => {
563 if (message.status_code !== Soup.Status.OK) {
564 Utils.debug('Failed to get route plan from router ' +
565 routers[index] + ' ' + message);
566 callback(null);
567 } else {
568 callback(JSON.parse(message.response_body.data));
569 }
570 });
571 });
572 }
573
574 _fetchRoutesRecursive(routers, index, result, callback) {
575 if (index < routers.length) {
576 let router = routers[index];
577
578 this._fetchRoutesForRouter(router, (response) => {
579 if (response) {
580 Utils.debug('plan: ' + JSON.stringify(response, '', 2));
581 result.push(response);
582 }
583
584 this._fetchRoutesRecursive(routers, index + 1, result, callback);
585 });
586 } else {
587 callback(result);
588 }
589 }
590
591 _fetchRoutes(routers, callback) {
592 this._fetchRoutesRecursive(routers, 0, [], callback);
593 }
594
595 _reset() {
596 this._extendPrevious = false;
597 if (this._query.latest)
598 this._query.latest.place = null;
599 else
600 this.plan.reset();
601 }
602
603 /* Indicate that no routes where found, either shows the "No route found"
604 * message, or in case of loading additional (later/earlier) results,
605 * indicate no such where found, so that the sidebar can disable the
606 * "load more" functionallity as appropriate.
607 */
608 _noRouteFound() {
609 if (this._extendPrevious) {
610 this._extendPrevious = false;
611 this.plan.noMoreResults();
612 } else {
613 this._reset();
614 this.plan.error(_("No route found."));
615 }
616 }
617
618 _fetchRoute() {
619 this._fetchRouters((success) => {
620 if (success) {
621 let points = this._query.filledPoints;
622 let routers = this._getRoutersForPoints(points);
623
624 if (routers.length > 0) {
625 this._fetchRoutes(routers, (routes) => {
626 let itineraries = [];
627 routes.forEach((plan) => {
628 if (plan.plan && plan.plan.itineraries) {
629 itineraries =
630 itineraries.concat(
631 this._createItineraries(plan.plan.itineraries));
632 }
633 });
634
635 if (itineraries.length === 0) {
636 /* don't reset query points, unlike for turn-based
637 * routing, since options and timeing might influence
638 * results */
639 this._noRouteFound();
640 } else {
641 this._recalculateItineraries(itineraries);
642 }
643 });
644
645 } else {
646 this._reset();
647 this.plan.error(_("No timetable data found for this route."));
648 }
649 } else {
650 this._reset();
651 this.plan.error(_("Route request failed."));
652 }
653 });
654 }
655
656 _isOnlyWalkingItinerary(itinerary) {
657 return itinerary.legs.length === 1 && !itinerary.legs[0].transit;
658 }
659
660 _recalculateItineraries(itineraries) {
661 // filter out itineraries with only walking
662 let newItineraries = [];
663
664 itineraries.forEach((itinerary) => {
665 if (!this._isOnlyWalkingItinerary(itinerary))
666 newItineraries.push(itinerary);
667 });
668
669 /* TODO: should we always calculate a walking itinerary to put at the
670 * top if the total distance is below some threashhold?
671 */
672 this._recalculateItinerariesRecursive(newItineraries, 0);
673 }
674
675 _isItineraryRealistic(itinerary) {
676 for (let i = 0; i < itinerary.legs.length; i++) {
677 let leg = itinerary.legs[i];
678
679 if (!leg.transit) {
680 /* if a walking leg exceeds the maximum desired walking
681 * distance, or for a leg "in-between" two transit legs, if
682 * there's insufficent switch time
683 */
684 if (leg.distance > MAX_WALKING_DISTANCE) {
685 return false;
686 } else if (i >= 1 && i < itinerary.legs.length - 1) {
687 let previousLeg = itinerary.legs[i - 1];
688 let nextLeg = itinerary.legs[i + 1];
689
690 let availableTime =
691 (nextLeg.departure - previousLeg.arrival) / 1000;
692
693 if (availableTime <
694 leg.duration + MIN_INTERMEDIATE_WALKING_SLACK)
695 return false;
696 }
697 }
698 }
699
700 return true;
701 }
702
703 _recalculateItinerariesRecursive(itineraries, index) {
704 if (index < itineraries.length) {
705 this._recalculateItinerary(itineraries[index], (itinerary) => {
706 itineraries[index] = itinerary;
707 this._recalculateItinerariesRecursive(itineraries, index + 1);
708 });
709 } else {
710 /* filter out itineraries where there are intermediate walking legs
711 * that are too narrow time-wise, this is nessesary since running
712 * OTP with only transit data can result in some over-optimistic
713 * walking itinerary legs, since it will use "line-of-sight"
714 * distances.
715 * also filter out itineraries where recalculation process ended
716 * up with just walking
717 */
718 let filteredItineraries = [];
719
720 itineraries.forEach((itinerary) => {
721 if (this._isItineraryRealistic(itinerary) &&
722 !this._isOnlyWalkingItinerary(itinerary))
723 filteredItineraries.push(itinerary);
724 });
725
726 if (filteredItineraries.length > 0) {
727 filteredItineraries.forEach((itinerary) => itinerary.adjustTimings());
728
729 /* sort itineraries, by departure time ascending if querying
730 * by leaving time, by arrival time descending when querying
731 * by arriving time
732 */
733 if (this._query.arriveBy)
734 filteredItineraries.sort(TransitPlan.sortItinerariesByArrivalDesc);
735 else
736 filteredItineraries.sort(TransitPlan.sortItinerariesByDepartureAsc);
737
738 let newItineraries = this._extendPrevious ?
739 this.plan.itineraries.concat(filteredItineraries) :
740 filteredItineraries;
741
742 // reset the "load more results" flag
743 this._extendPrevious = false;
744 this.plan.update(newItineraries);
745 } else {
746 this._noRouteFound();
747 }
748 }
749 }
750
751 // create a straight-line "as the crow flies" polyline between two places
752 _createStraightPolyline(fromLoc, toLoc) {
753 return [new Champlain.Coordinate({ latitude: fromLoc.latitude,
754 longitude: fromLoc.longitude }),
755 new Champlain.Coordinate({ latitude: toLoc.latitude,
756 longitude: toLoc.longitude })];
757 }
758
759 /* Creates a new walking leg given start and end places, and a route
760 * obtained from GraphHopper. If the route is undefined (which happens if
761 * GraphHopper failed to obtain a walking route, approximate it with a
762 * straight line. */
763 _createWalkingLeg(from, to, fromName, toName, route) {
764 let fromLocation = from.place.location;
765 let toLocation = to.place.location;
766 let fromCoordinate = [fromLocation.latitude, fromLocation.longitude];
767 let toCoordinate = [toLocation.latitude, toLocation.longitude];
768 let polyline = route ? route.path :
769 this._createStraightPolyline(fromLocation, toLocation);
770 let distance = route ? route.distance :
771 fromLocation.get_distance_from(toLocation) * 1000;
772 /* as an estimate for approximated straight-line walking legs,
773 * assume a speed of 1 m/s to allow some extra time */
774 let duration = route ? route.time / 1000 : distance;
775 let walkingInstructions = route ? route.turnPoints : null;
776
777 return new TransitPlan.Leg({ fromCoordinate: fromCoordinate,
778 toCoordinate: toCoordinate,
779 from: fromName,
780 to: toName,
781 isTransit: false,
782 polyline: polyline,
783 duration: duration,
784 distance: distance,
785 walkingInstructions: walkingInstructions });
786 }
787
788 /* fetches walking route and stores the route for the given coordinate
789 * pair to avoid requesting the same route over and over from GraphHopper
790 */
791 _fetchWalkingRoute(points, callback) {
792 let index = points[0].place.location.latitude + ',' +
793 points[0].place.location.longitude + ';' +
794 points[1].place.location.latitude + ',' +
795 points[1].place.location.longitude;
796 let route = this._walkingRoutes[index];
797
798 if (!route) {
799 this._graphHopper.fetchRouteAsync(points,
800 RouteQuery.Transportation.PEDESTRIAN,
801 (newRoute) => {
802 this._walkingRoutes[index] = newRoute;
803 callback(newRoute);
804 });
805 } else {
806 callback(route);
807 }
808 }
809
810 _recalculateItinerary(itinerary, callback) {
811 let from = this._query.filledPoints[0];
812 let to = this._query.filledPoints.last();
813
814 if (itinerary.legs.length === 1 && !itinerary.legs[0].transit) {
815 /* special case, if there's just one leg of an itinerary, and that leg
816 * leg is a non-transit (walking), recalculate the route in its entire
817 * using walking
818 */
819 this._fetchWalkingRoute(this._query.filledPoints, (route) => {
820 let leg = this._createWalkingLeg(from, to, from.place.name,
821 to.place.name, route);
822 let newItinerary =
823 new TransitPlan.Itinerary({departure: itinerary.departure,
824 duration: route.time / 1000,
825 legs: [leg]});
826 callback(newItinerary);
827 });
828 } else if (itinerary.legs.length === 1 && itinerary.legs[0].transit) {
829 // special case if there is extactly one transit leg
830 let leg = itinerary.legs[0];
831 let startLeg = this._createQueryPointForCoord(leg.fromCoordinate);
832 let endLeg = this._createQueryPointForCoord(leg.toCoordinate);
833 let fromLoc = from.place.location;
834 let startLoc = startLeg.place.location;
835 let endLoc = endLeg.place.location;
836 let toLoc = to.place.location;
837 let startWalkDistance = fromLoc.get_distance_from(startLoc) * 1000;
838 let endWalkDistance = endLoc.get_distance_from(toLoc) * 1000;
839
840 if (startWalkDistance >= MIN_WALK_ROUTING_DISTANCE &&
841 endWalkDistance >= MIN_WALK_ROUTING_DISTANCE) {
842 /* add an extra walking leg to both the beginning and end of the
843 * itinerary
844 */
845 this._fetchWalkingRoute([from, startLeg], (firstRoute) => {
846 let firstLeg =
847 this._createWalkingLeg(from, startLeg, from.place.name,
848 leg.from, firstRoute);
849 this._fetchWalkingRoute([endLeg, to], (lastRoute) => {
850 let lastLeg = this._createWalkingLeg(endLeg, to, leg.to,
851 to.place.name,
852 lastRoute);
853 itinerary.legs.unshift(firstLeg);
854 itinerary.legs.push(lastLeg);
855 callback(itinerary);
856 });
857 });
858 } else if (endWalkDistance >= MIN_WALK_ROUTING_DISTANCE) {
859 // add an extra walking leg to the end of the itinerary
860 this._fetchWalkingRoute([endLeg, to], (lastRoute) => {
861 let lastLeg =
862 this._createWalkingLeg(endLeg, to, leg.to,
863 to.place.name, lastRoute);
864 itinerary.legs.push(lastLeg);
865 callback(itinerary);
866 });
867 } else {
868 /* if only there's only a walking leg to be added to the start
869 * let the recursive routine dealing with multi-leg itineraries
870 * handle it
871 */
872 this._recalculateItineraryRecursive(itinerary, 0, callback);
873 }
874 } else {
875 /* replace walk legs with GraphHopper-generated paths (hence the
876 * callback nature of this. Filter out unrealistic itineraries (having
877 * walking segments not possible in reasonable time, due to our running
878 * of OTP with only transit data).
879 */
880 this._recalculateItineraryRecursive(itinerary, 0, callback);
881 }
882 }
883
884 _createQueryPointForCoord(coord) {
885 let location = new Location.Location({ latitude: coord[0],
886 longitude: coord[1],
887 accuracy: 0 });
888 let place = new Place.Place({ location: location });
889 let point = new RouteQuery.QueryPoint();
890
891 point.place = place;
892 return point;
893 }
894
895 _recalculateItineraryRecursive(itinerary, index, callback) {
896 if (index < itinerary.legs.length) {
897 let leg = itinerary.legs[index];
898 if (index === 0) {
899 let from = this._query.filledPoints[0];
900 let startLeg =
901 this._createQueryPointForCoord(leg.fromCoordinate);
902 let endLeg =
903 this._createQueryPointForCoord(leg.toCoordinate);
904 let fromLoc = from.place.location;
905 let startLegLoc = startLeg.place.location;
906 let endLegLoc = endLeg.place.location;
907 let distanceToEndLeg =
908 fromLoc.get_distance_from(endLegLoc) * 1000;
909 let distanceToStartLeg =
910 fromLoc.get_distance_from(startLegLoc) * 1000;
911 let nextLeg = itinerary.legs[index + 1];
912
913 if (!leg.transit ||
914 ((leg.distance <= MIN_TRANSIT_LEG_DISTANCE ||
915 (distanceToEndLeg <= MAX_WALK_OPTIMIZATION_DISTANCE &&
916 distanceToEndLeg - distanceToStartLeg <=
917 MAX_WALK_OPTIMIZATION_DISTANCE_DIFFERENCE)) &&
918 itinerary.legs.length > 1)) {
919 /* if the first leg of the intinerary returned by OTP is a
920 * walking one, recalculate it with GH using the actual
921 * starting coordinate from the input query,
922 * also replace a transit leg at the start with walking if
923 * its distance is below a threashhold, to avoid suboptimal
924 * routes due to only running OTP with transit data,
925 * also optimize away cases where the routing would make one
926 * "pass by" a stop at the next step in the itinerary due to
927 * similar reasons
928 */
929 let to = this._createQueryPointForCoord(leg.toCoordinate);
930 let toName = leg.to;
931
932 /* if the next leg is a walking one, "fold" it into the one
933 * we create here */
934 if (nextLeg && !nextLeg.transit) {
935 to = this._createQueryPointForCoord(nextLeg.toCoordinate);
936 toName = nextLeg.to;
937 itinerary.legs.splice(index + 1, index + 1);
938 }
939
940 this._fetchWalkingRoute([from, to], (route) => {
941 let newLeg =
942 this._createWalkingLeg(from, to, from.place.name,
943 toName, route);
944 itinerary.legs[index] = newLeg;
945 this._recalculateItineraryRecursive(itinerary, index + 1,
946 callback);
947 });
948 } else {
949 /* introduce an additional walking leg calculated
950 * by GH in case the OTP starting point as far enough from
951 * the original starting point
952 */
953 let to = this._createQueryPointForCoord(leg.fromCoordinate);
954 let fromLoc = from.place.location;
955 let toLoc = to.place.location;
956 let distance = fromLoc.get_distance_from(toLoc) * 1000;
957
958 if (distance >= MIN_WALK_ROUTING_DISTANCE) {
959 this._fetchWalkingRoute([from, to], (route) => {
960 let newLeg =
961 this._createWalkingLeg(from, to, from.place.name,
962 leg.from, route);
963 itinerary.legs.unshift(newLeg);
964 /* now, next index will be two steps up, since we
965 * inserted a new leg
966 */
967 this._recalculateItineraryRecursive(itinerary,
968 index + 2,
969 callback);
970 });
971 } else {
972 this._recalculateItineraryRecursive(itinerary, index + 1,
973 callback);
974 }
975 }
976 } else if (index === itinerary.legs.length - 1) {
977 let to = this._query.filledPoints.last();
978 let startLeg =
979 this._createQueryPointForCoord(leg.fromCoordinate);
980 let endLeg = this._createQueryPointForCoord(leg.toCoordinate);
981 let toLoc = to.place.location;
982 let startLegLoc = startLeg.place.location;
983 let endLegLoc = endLeg.place.location;
984 let distanceFromEndLeg =
985 toLoc.get_distance_from(endLegLoc) * 1000;
986 let distanceFromStartLeg =
987 toLoc.get_distance_from(startLegLoc) * 1000;
988 let previousLeg = itinerary.legs[itinerary.legs.length - 2];
989
990 if (!leg.transit ||
991 ((leg.distance <= MIN_TRANSIT_LEG_DISTANCE ||
992 (distanceFromStartLeg <= MAX_WALK_OPTIMIZATION_DISTANCE &&
993 distanceFromStartLeg - distanceFromEndLeg <=
994 MAX_WALK_OPTIMIZATION_DISTANCE_DIFFERENCE)) &&
995 itinerary.legs.length > 1)) {
996 /* if the final leg of the itinerary returned by OTP is a
997 * walking one, recalculate it with GH using the actual
998 * ending coordinate from the input query
999 * also replace a transit leg at the end with walking if
1000 * its distance is below a threashhold, to avoid suboptimal
1001 * routes due to only running OTP with transit data,
1002 * also optimize away cases where the routing would make one
1003 * "pass by" a stop at the previous step in the itinerary
1004 * due to similar reasons
1005 */
1006 let finalTransitLeg;
1007 let insertIndex;
1008 if (leg.transit && previousLeg && !previousLeg.transit) {
1009 /* if we optimize away the final transit leg, and the
1010 * previous leg is a walking one, "fold" both into a
1011 * single walking leg */
1012 finalTransitLeg = previousLeg;
1013 insertIndex = index -1;
1014 itinerary.legs.pop();
1015 } else {
1016 finalTransitLeg = leg;
1017 insertIndex = index;
1018 }
1019 let from = this._createQueryPointForCoord(finalTransitLeg.fromCoordinate);
1020 this._fetchWalkingRoute([from, to], (route) => {
1021 let newLeg =
1022 this._createWalkingLeg(from, to,
1023 finalTransitLeg.from,
1024 to.place.name, route);
1025 itinerary.legs[insertIndex] = newLeg;
1026 this._recalculateItineraryRecursive(itinerary,
1027 insertIndex + 1,
1028 callback);
1029 });
1030 } else {
1031 /* introduce an additional walking leg calculated by GH in
1032 * case the OTP end point as far enough from the original
1033 * end point
1034 */
1035 let from = this._createQueryPointForCoord(leg.toCoordinate);
1036 let fromLoc = from.place.location;
1037 let toLoc = to.place.location;
1038 let distance = fromLoc.get_distance_from(toLoc) * 1000;
1039
1040 if (distance >= MIN_WALK_ROUTING_DISTANCE) {
1041 this._fetchWalkingRoute([from, to], (route) => {
1042 let newLeg =
1043 this._createWalkingLeg(from, to, leg.to,
1044 to.place.name, route);
1045 itinerary.legs.push(newLeg);
1046 /* now, next index will be two steps up, since we
1047 * inserted a new leg
1048 */
1049 this._recalculateItineraryRecursive(itinerary,
1050 index + 2,
1051 callback);
1052 });
1053 } else {
1054 this._recalculateItineraryRecursive(itinerary, index + 1,
1055 callback);
1056 }
1057 }
1058 } else {
1059 /* if an intermediate leg is a walking one, and it's distance is
1060 * above the threashhold distance, calculate an exact route
1061 */
1062 if (!leg.transit && leg.distance >= MIN_WALK_ROUTING_DISTANCE) {
1063 let from = this._createQueryPointForCoord(leg.fromCoordinate);
1064 let to = this._createQueryPointForCoord(leg.toCoordinate);
1065
1066 /* if the next leg is the final one of the itinerary,
1067 * and it's shorter than the "optimize away" distance,
1068 * create a walking leg all the way to the final destination
1069 */
1070 let nextLeg = itinerary.legs[index + 1];
1071 if (index === itinerary.legs.length - 2 &&
1072 nextLeg.distance <= MIN_TRANSIT_LEG_DISTANCE) {
1073 to = this._query.filledPoints.last();
1074 itinerary.legs.splice(index + 1, index + 1);
1075 }
1076
1077 this._fetchWalkingRoute([from, to], (route) => {
1078 let newLeg = this._createWalkingLeg(from, to, leg.from,
1079 leg.to, route);
1080 itinerary.legs[index] = newLeg;
1081 this._recalculateItineraryRecursive(itinerary,
1082 index + 1,
1083 callback);
1084 });
1085 } else {
1086 this._recalculateItineraryRecursive(itinerary, index + 1,
1087 callback);
1088 }
1089 }
1090 } else {
1091 callback(itinerary);
1092 }
1093 }
1094
1095 _getRoutersForPoints(points) {
1096 let startRouters = this._getRoutersForPlace(points[0].place);
1097 let endRouters =
1098 this._getRoutersForPlace(points.last().place);
1099
1100 let intersectingRouters =
1101 this._routerIntersection(startRouters, endRouters);
1102
1103 return intersectingRouters;
1104 }
1105
1106 _createItineraries(itineraries) {
1107 return itineraries.map((itinerary) => this._createItinerary(itinerary));
1108 }
1109
1110 _createItinerary(itinerary) {
1111 let legs = this._createLegs(itinerary.legs);
1112 return new TransitPlan.Itinerary({ duration: itinerary.duration,
1113 transfers: itinerary.transfers,
1114 departure: itinerary.startTime,
1115 arrival: itinerary.endTime,
1116 legs: legs});
1117 }
1118
1119 _createLegs(legs) {
1120 return legs.map((leg) => this._createLeg(leg));
1121 }
1122
1123 /* check if a string is a valid hex RGB string */
1124 _isValidHexColor(string) {
1125 if (string && string.length === 6) {
1126 let regex = /^[A-Fa-f0-9]/;
1127
1128 return string.match(regex);
1129 }
1130
1131 return false;
1132 }
1133
1134 _createLeg(leg) {
1135 let polyline = EPAF.decode(leg.legGeometry.points);
1136 let intermediateStops =
1137 this._createIntermediateStops(leg);
1138 let color = leg.routeColor && this._isValidHexColor(leg.routeColor) ?
1139 leg.routeColor : null;
1140 let textColor = leg.routeTextColor && this._isValidHexColor(leg.routeTextColor) ?
1141 leg.routeTextColor : null;
1142
1143 /* instroduce an extra stop at the end (in additional to the
1144 * intermediate stops we get from OTP
1145 */
1146 intermediateStops.push(new TransitPlan.Stop({ name: leg.to.name,
1147 arrival: leg.to.arrival,
1148 agencyTimezoneOffset: leg.agencyTimeZoneOffset,
1149 coordinate: [leg.to.lat,
1150 leg.to.lon] }));
1151
1152 return new TransitPlan.Leg({ departure: leg.from.departure,
1153 arrival: leg.to.arrival,
1154 from: leg.from.name,
1155 to: leg.to.name,
1156 headsign: leg.headsign,
1157 intermediateStops: intermediateStops,
1158 fromCoordinate: [leg.from.lat,
1159 leg.from.lon],
1160 toCoordinate: [leg.to.lat,
1161 leg.to.lon],
1162 route: leg.route,
1163 routeType: leg.routeType,
1164 polyline: polyline,
1165 isTransit: leg.transitLeg,
1166 distance: leg.distance,
1167 duration: leg.duration,
1168 agencyName: leg.agencyName,
1169 agencyUrl: leg.agencyUrl,
1170 agencyTimezoneOffset: leg.agencyTimeZoneOffset,
1171 color: color,
1172 textColor: textColor,
1173 tripShortName: leg.tripShortName });
1174 }
1175
1176 _createIntermediateStops(leg) {
1177 let stops = leg.intermediateStops;
1178 return stops.map((stop) => this._createIntermediateStop(stop, leg));
1179 }
1180
1181 _createIntermediateStop(stop, leg) {
1182 return new TransitPlan.Stop({ name: stop.name,
1183 arrival: stop.arrival,
1184 departure: stop.departure,
1185 agencyTimezoneOffset: leg.agencyTimeZoneOffset,
1186 coordinate: [stop.lat, stop.lon] });
1187 }
1188 };
2323 <file>gpxShapeLayer.js</file>
2424 <file>graphHopper.js</file>
2525 <file>graphHopperGeocode.js</file>
26 <file>graphHopperTransit.js</file>
2627 <file>hvt.js</file>
2728 <file>http.js</file>
2829 <file>instructionRow.js</file>
3839 <file>mapSource.js</file>
3940 <file>mapView.js</file>
4041 <file>mapWalker.js</file>
41 <file>openTripPlanner.js</file>
4242 <file>osmAccountDialog.js</file>
4343 <file>osmConnection.js</file>
4444 <file>osmEdit.js</file>
9090 <file>transitOptionsPanel.js</file>
9191 <file>transitPlan.js</file>
9292 <file>transitPrintLayout.js</file>
93 <file>transitRouter.js</file>
9394 <file>transitRouteLabel.js</file>
9495 <file>transitStopRow.js</file>
96 <file>transitTweaks.js</file>
9597 <file>transitWalkMarker.js</file>
9698 <file>translations.js</file>
9799 <file>turnPointMarker.js</file>
110112 <file alias="geojsonvt/tile.js">tile.js</file>
111113 <file alias="geojsonvt/transform.js">transform.js</file>
112114 <file alias="geojsonvt/wrap.js">wrap.js</file>
115 <file>transitplugins/openTripPlanner.js</file>
116 <file>transitplugins/resrobot.js</file>
113117 </gresource>
114118 </gresources>
4646 var OSMConnection = class OSMConnection {
4747
4848 constructor() {
49 this._session = new Soup.Session();
49 this._session = new Soup.Session({ user_agent : 'gnome-maps/' + pkg.version });
5050
5151 /* OAuth proxy used for making OSM uploads */
5252 this._callProxy = Rest.OAuthProxy.new(CONSUMER_KEY, CONSUMER_SECRET,
6969 this.outputSortOrder = params.outputSortOrder || _DEFAULT_OUTPUT_SORT_ORDER;
7070
7171 // HTTP Session Variables
72 this._session = new Soup.Session();
72 this._session = new Soup.Session({ user_agent : 'gnome-maps/' + pkg.version });
7373 }
7474
7575 addInfo(place) {
148148
149149 _readService() {
150150 let photon = Service.getService().photonGeocode;
151 let locale = GLib.get_language_names()[0];
152 // the last item returned is the "bare" language
153 let language = GLib.get_locale_variants(locale).slice(-1)[0];
151 let language = Utils.getLanguage();
154152 let supportedLanguages;
155153
156154 if (photon) {
5656 _beginPrint(operation, context, data) {
5757 let route = Application.routingDelegator.graphHopper.route;
5858 let selectedTransitItinerary =
59 Application.routingDelegator.openTripPlanner.plan.selectedItinerary;
59 Application.routingDelegator.transitRouter.plan.selectedItinerary;
6060 let width = context.get_width();
6161 let height = context.get_height();
6262
3737
3838 // This one is not in GraphHopper, so choose
3939 // a reasonably unlikely number for this
40 START: 10000
40 START: 10000,
41 ELEVATOR: 10001,
42 UTURN_LEFT: 10002,
43 UTURN_RIGHT: 10003
4144 };
4245
4346 /* countries/terrotories driving on the left
238238 }
239239
240240 isValid() {
241 if (this.filledPoints.length >= 2)
241 if (this.filledPoints.length >= 2 &&
242 this.filledPoints.length === this.points.length)
242243 return true;
243244 else
244245 return false;
1919 */
2020
2121 const GraphHopper = imports.graphHopper;
22 const OpenTripPlanner = imports.openTripPlanner;
22 const TransitRouter = imports.transitRouter;
2323 const RouteQuery = imports.routeQuery;
2424
2525 const _FALLBACK_TRANSPORTATION = RouteQuery.Transportation.PEDESTRIAN;
3131
3232 this._transitRouting = false;
3333 this._graphHopper = new GraphHopper.GraphHopper({ query: this._query });
34 this._openTripPlanner =
35 new OpenTripPlanner.OpenTripPlanner({ query: this._query,
36 graphHopper: this._graphHopper });
34 this._transitRouter = new TransitRouter.TransitRouter({ query: this._query });
3735 this._query.connect('notify::points', this._onQueryChanged.bind(this));
3836
3937 /* if the query is set to transit mode when it's not available, revert
4038 * to a fallback mode
4139 */
4240 if (this._query.transportation === RouteQuery.Transportation.TRANSIT &&
43 !this._openTripPlanner.enabled) {
41 !this._transitRouter.enabled) {
4442 this._query.transportation = _FALLBACK_TRANSPORTATION;
4543 }
4644 }
4947 return this._graphHopper;
5048 }
5149
52 get openTripPlanner() {
53 return this._openTripPlanner;
50 get transitRouter() {
51 return this._transitRouter;
5452 }
5553
5654 set useTransit(useTransit) {
5957
6058 reset() {
6159 if (this._transitRouting)
62 this._openTripPlanner.plan.reset();
60 this._transitRouter.plan.reset();
6361 else
6462 this._graphHopper.route.reset();
6563 }
6765 _onQueryChanged() {
6866 if (this._query.isValid()) {
6967 if (this._transitRouting) {
70 this._openTripPlanner.fetchFirstResults();
68 this._transitRouter.fetchFirstResults();
7169 } else {
7270 this._graphHopper.fetchRoute(this._query.filledPoints,
7371 this._query.transportation);
6363 'transitItineraryListBox',
6464 'transitItineraryBackButton',
6565 'transitItineraryTimeLabel',
66 'transitItineraryDurationLabel']
66 'transitItineraryDurationLabel',
67 'transitAttributionLabel']
6768 }, class Sidebar extends Gtk.Revealer {
6869
6970 _init(mapView) {
9495 this._query.addPoint(1);
9596 this._switchRoutingMode(Application.routeQuery.transportation);
9697 /* Enable/disable transit mode switch based on the presence of
97 * OpenTripPlanner.
98 * public transit providers.
9899 * For some reason, setting visible to false in the UI file and
99100 * dynamically setting visible false here doesn't work, maybe because
100101 * it's part of a radio group? As a workaround, just remove the button
101102 * instead.
102103 */
103 if (!Application.routingDelegator.openTripPlanner.enabled)
104 if (!Application.routingDelegator.transitRouter.enabled)
104105 this._modeTransitToggle.destroy();
105106 }
106107
146147 _switchRoutingMode(mode) {
147148 if (mode === RouteQuery.Transportation.TRANSIT) {
148149 Application.routingDelegator.useTransit = true;
149 this._linkButtonStack.visible_child_name = 'openTripPlanner';
150 this._linkButtonStack.visible_child_name = 'transit';
150151 this._transitOptionsPanel.reset();
151152 this._transitRevealer.reveal_child = true;
152153 } else {
153154 Application.routingDelegator.useTransit = false;
154 this._linkButtonStack.visible_child_name = 'graphHopper';
155 this._linkButtonStack.visible_child_name = 'turnByTurn';
155156 this._transitRevealer.reveal_child = false;
156 Application.routingDelegator.openTripPlanner.plan.deselectItinerary();
157 Application.routingDelegator.transitRouter.plan.deselectItinerary();
157158 }
158159 this._clearInstructions();
159160 }
213214
214215 _initInstructionList() {
215216 let route = Application.routingDelegator.graphHopper.route;
216 let transitPlan = Application.routingDelegator.openTripPlanner.plan;
217 let transitPlan = Application.routingDelegator.transitRouter.plan;
217218
218219 route.connect('reset', () => {
219220 this._clearInstructions();
231232 /* don't remove query points as with the turn-based routing,
232233 * since we might get "no route" because of the time selected
233234 * and so on */
235 this._transitAttributionLabel.label = '';
234236 });
235237
236238 transitPlan.connect('no-more-results', () => {
247249 if (this._query.transportation === RouteQuery.Transportation.TRANSIT) {
248250 this._clearTransitOverview();
249251 this._showTransitOverview();
252 this._transitAttributionLabel.label = '';
250253 } else {
251254 this._clearInstructions();
252255 }
299302 });
300303
301304 transitPlan.connect('update', () => {
305 this._updateTransitAttribution();
302306 this._clearTransitOverview();
303307 this._showTransitOverview();
304308 this._populateTransitItineraryOverview();
339343 listBox.forall(listBox.remove.bind(listBox));
340344 }
341345
346 _updateTransitAttribution() {
347 let plan = Application.routingDelegator.transitRouter.plan;
348
349 if (plan.attribution) {
350 let attributionLabel =
351 _("Itineraries provided by %s").format(plan.attribution);
352 if (plan.attributionUrl) {
353 this._transitAttributionLabel.label =
354 '<a href="%s">%s</a>'.format([plan.attributionUrl],
355 attributionLabel);
356 } else {
357 this._transitAttributionLabel.label = attributionLabel;
358 }
359 } else {
360 this._transitAttributionLabel.label = '';
361 }
362 }
363
342364 _showTransitOverview() {
343 let plan = Application.routingDelegator.openTripPlanner.plan;
365 let plan = Application.routingDelegator.transitRouter.plan;
344366
345367 this._transitListStack.visible_child_name = 'overview';
346368 this._transitHeader.visible_child_name = 'options';
353375 }
354376
355377 _populateTransitItineraryOverview() {
356 let plan = Application.routingDelegator.openTripPlanner.plan;
378 let plan = Application.routingDelegator.transitRouter.plan;
357379
358380 plan.itineraries.forEach((itinerary) => {
359381 let row =
370392 }
371393
372394 _onItineraryActivated(itinerary) {
373 let plan = Application.routingDelegator.openTripPlanner.plan;
395 let plan = Application.routingDelegator.transitRouter.plan;
374396
375397 this._populateTransitItinerary(itinerary);
376398 this._showTransitItineraryView();
379401
380402 _onMoreActivated(row) {
381403 row.startLoading();
382 Application.routingDelegator.openTripPlanner.fetchMoreResults();
404 Application.routingDelegator.transitRouter.fetchMoreResults();
383405 }
384406
385407 _onItineraryOverviewRowActivated(listBox, row) {
2222 const Gtk = imports.gi.Gtk;
2323
2424 const TransitRouteLabel = imports.transitRouteLabel;
25
26 // maximum number of legs to show before abbreviating with a … in the middle
27 const MAX_LEGS_SHOWN = 8;
2528
2629 var TransitItineraryRow = GObject.registerClass({
2730 Template: 'resource:///org/gnome/Maps/ui/transit-itinerary-row.ui',
5356 */
5457 let useCompact = length > 2;
5558 /* don't show the route labels if too much space is consumed,
56 * the constant 28 here was empiracally tested out...
59 * the constant 26 here was empiracally tested out...
5760 */
5861 let estimatedSpace = this._calculateEstimatedSpace();
59 let useContractedLabels = estimatedSpace > 28;
62 let useContractedLabels = estimatedSpace > 26;
6063
61 this._itinerary.legs.forEach((leg, i) => {
64 if (length > MAX_LEGS_SHOWN) {
65 /* ellipsize list with horizontal dots to avoid overflowing and
66 * expanding the sidebar
67 */
68 this._renderLegs(this._itinerary.legs.slice(0, MAX_LEGS_SHOWN / 2),
69 true, true);
70 this._summaryGrid.add(new Gtk.Label({ visible: true,
71 label: '\u22ef' } ));
72 this._renderLegs(this._itinerary.legs.slice(-MAX_LEGS_SHOWN / 2),
73 true, true);
74 } else {
75 this._renderLegs(this._itinerary.legs, useCompact,
76 useContractedLabels);
77 }
78 }
79
80 _renderLegs(legs, useCompact, useContractedLabels) {
81 let length = legs.length;
82
83 legs.forEach((leg, i) => {
6284 this._summaryGrid.add(this._createLeg(leg, useCompact,
6385 useContractedLabels));
6486 if (i !== length - 1)
2424 const Gtk = imports.gi.Gtk;
2525
2626 const Application = imports.application;
27 const HVT = imports.hvt;
2728 const Time = imports.time;
2829 const TransitOptions = imports.transitOptions;
2930 const TransitPlan = imports.transitPlan;
4546 'tramCheckButton',
4647 'trainCheckButton',
4748 'subwayCheckButton',
48 'ferryCheckButton']
49 'ferryCheckButton',
50 'airplaneCheckButton']
4951 }, class TransitOptionsPanel extends Gtk.Grid {
5052
5153 _init(params) {
8789 this._transitTimeEntry.visible = false;
8890 this._transitDateButton.visible = false;
8991 this._query.arriveBy = false;
92 this._query.date = null;
9093 this._query.time = null;
91 this._query.date = null;
9294 this._timeSelected = null;
9395 this._dateSelected = null;
9496 } else {
170172 let trainSelected = this._trainCheckButton.active;
171173 let subwaySelected = this._subwayCheckButton.active;
172174 let ferrySelected = this._ferryCheckButton.active;
175 let airplaneSelected = this._airplaneCheckButton.active;
173176
174177 if (busSelected && tramSelected && trainSelected && subwaySelected &&
175 ferrySelected) {
178 ferrySelected && airplaneSelected) {
176179 options.showAllTransitTypes = true;
177180 } else {
178181 if (busSelected)
185188 options.addTransitType(TransitPlan.RouteType.SUBWAY);
186189 if (ferrySelected)
187190 options.addTransitType(TransitPlan.RouteType.FERRY);
191 if (airplaneSelected)
192 options.addTransitType(HVT.AIR_SERVICE);
188193 }
189194
190195 return options;
9595 _init(params) {
9696 super._init(params);
9797 this.reset();
98 this._attribution = null;
99 this._attributionUrl = null;
98100 }
99101
100102 get itineraries() {
103105
104106 get selectedItinerary() {
105107 return this._selectedItinerary;
108 }
109
110 get attribution() {
111 return this._attribution;
112 }
113
114 set attribution(attribution) {
115 this._attribution = attribution;
116 }
117
118 get attributionUrl() {
119 return this._attributionUrl;
120 }
121
122 set attributionUrl(attributionUrl) {
123 this._attributionUrl = attributionUrl;
106124 }
107125
108126 update(itineraries) {
111129 this.emit('update');
112130 }
113131
132 /**
133 * Update plan with new itineraries, setting the new itineraries if it's
134 * the first fetch for a query, or extending the existing ones if it's
135 * a request to load more
136 */
137 updateWithNewItineraries(itineraries, arriveBy, extendPrevious) {
138 /* sort itineraries, by departure time ascending if querying
139 * by leaving time, by arrival time descending when querying
140 * by arriving time
141 */
142 if (arriveBy)
143 itineraries.sort(sortItinerariesByArrivalDesc);
144 else
145 itineraries.sort(sortItinerariesByDepartureAsc);
146
147 let newItineraries =
148 extendPrevious ? this.itineraries.concat(itineraries) : itineraries;
149
150 this.update(newItineraries);
151 }
152
153
154
114155 reset() {
115156 this._itineraries = [];
116157 this.bbox = null;
117158 this._selectedItinerary = null;
159 this._attribution = null;
160 this._attributionUrl = null;
118161 this.emit('reset');
119162 }
120163
134177
135178 error(msg) {
136179 this.emit('error', msg);
180 }
181
182 noRouteFound() {
183 this.emit('error', _("No route found."));
184 }
185
186 noTimetable() {
187 this.emit('error', _("No timetable data found for this route."));
188 }
189
190 requestFailed() {
191 this.emit('error', _("Route request failed."));
192 }
193
194 noProvider() {
195 this.emit('error', _("No provider found for this route."));
137196 }
138197
139198 _createBBox() {
347406 get transitArrivalTimezoneOffset() {
348407 return this._getTransitArrivalLeg().timezoneOffset;
349408 }
409
410 get isWalkingOnly() {
411 return this.legs.length === 1 && !this.legs[0].isTransit;
412 }
350413 });
351414
352415 var Leg = class Leg {
422485
423486 get route() {
424487 return this._route;
488 }
489
490 set route(route) {
491 this._route = route;
425492 }
426493
427494 // try to get a shortened route name, suitable for overview rendering
463530 return this._routeType;
464531 }
465532
533 set routeType(routeType) {
534 this._routeType = routeType;
535 }
536
466537 get departure() {
467538 return this._departure;
468539 }
487558 return this._polyline;
488559 }
489560
561 set polyline(polyline) {
562 this._polyline = polyline;
563 }
564
490565 get fromCoordinate() {
491566 return this._fromCoordinate;
492567 }
507582 return this._intermediateStops;
508583 }
509584
585 set intermediateStops(intermediateStops) {
586 this._intermediateStops = intermediateStops;
587 }
588
510589 get headsign() {
511590 return this._headsign;
512591 }
519598 return this._distance;
520599 }
521600
601 set distance(distance) {
602 this._distance = distance;
603 }
604
522605 get duration() {
523606 return this._duration;
524607 }
543626 return this._color || DEFAULT_ROUTE_COLOR;
544627 }
545628
629 set color(color) {
630 this._color = color;
631 }
632
546633 get textColor() {
547634 return this._textColor || DEFAULT_ROUTE_TEXT_COLOR;
635 }
636
637 set textColor(textColor) {
638 this._textColor = textColor;
548639 }
549640
550641 get tripShortName() {
616707 case HVT.TAXI_SERVICE:
617708 /* TODO: should we have a dedicated taxi icon? */
618709 return 'route-car-symbolic';
710
711 case HVT.AIR_SERVICE:
712 return 'route-transit-airplane-symbolic';
713
619714 default:
620715 /* use a fallback question mark icon in case of some future,
621716 * for now unknown mode appears */
629724
630725 get walkingInstructions() {
631726 return this._walkingInstructions;
727 }
728
729 set walkingInstructions(walkingInstructions) {
730 this._walkingInstructions = walkingInstructions;
632731 }
633732
634733 /* Pretty print timing for a transit leg, set params.isStart: true when
712811 };
713812
714813 function sortItinerariesByDepartureAsc(first, second) {
715 return first.departure > second.departure;
814 /* always sort walk-only itineraries first, as they would always be
815 * starting at the earliest possible departure time
816 */
817 if (first.isWalkingOnly)
818 return -1;
819 else if (second.isWalkingOnly)
820 return 1;
821 else
822 return first.departure > second.departure;
716823 }
717824
718825 function sortItinerariesByArrivalDesc(first, second) {
719 return first.arrival < second.arrival;
720 }
826 /* always sort walk-only itineraries first, as they would always be
827 * ending at the latest possible arrival time
828 */
829 if (first.isWalkingOnly)
830 return -1;
831 else if (second.isWalkingOnly)
832 return 1;
833 else
834 return first.arrival < second.arrival;
835 }
0 /* -*- Mode: JS2; indent-tabs-mode: nil; js2-basic-offset: 4 -*- */
1 /* vim: set et ts=4 sw=4: */
2 /*
3 * Copyright (c) 2019 Marcus Lundblad
4 *
5 * GNOME Maps is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by the
7 * Free Software Foundation; either version 2 of the License, or (at your
8 * option) any later version.
9 *
10 * GNOME Maps is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13 * for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with GNOME Maps; if not, see <http://www.gnu.org/licenses/>.
17 *
18 * Author: Marcus Lundblad <ml@update.uu.se>
19 */
20
21 const Champlain = imports.gi.Champlain;
22
23 const Service = imports.service;
24 const TransitPlan = imports.transitPlan;
25 const Utils = imports.utils;
26
27 /**
28 * Class responsible for delegating requests to perform routing in transit
29 * mode.
30 * Holds the the shared plan instance (filled with journeys on successful
31 * requests).
32 */
33 var TransitRouter = class TransitRoute {
34 constructor(params) {
35 this._plan = new TransitPlan.Plan();
36 this._query = params.query;
37 this._providers = Service.getService().transitProviders;
38 this._providerCache = [];
39 this._language = Utils.getLanguage();
40 this._probePlugins();
41 }
42
43 get enabled() {
44 return this._providers !== undefined;
45 }
46
47 get plan() {
48 return this._plan;
49 }
50
51 /**
52 * Called when the query has been updated to trigger the first set
53 * of results-
54 */
55 fetchFirstResults() {
56 let bestProvider = this._getBestProviderForQuery();
57
58 if (bestProvider) {
59 let provider = bestProvider[0];
60
61 this._currPluginInstance = bestProvider[1];
62 this._plan.attribution = this._getAttributionForProvider(provider);
63 if (provider.attributionUrl)
64 this._plan.attributionUrl = provider.attributionUrl;
65 this._currPluginInstance.fetchFirstResults();
66 } else {
67 this._plan.reset();
68 this._query.reset();
69 this._plan.noProvider();
70 }
71 }
72
73 /**
74 * Called to fetch additional (later or earlier) results depending on the
75 * query settings.
76 */
77 fetchMoreResults() {
78 if (this._currPluginInstance)
79 this._currPluginInstance.fetchMoreResults();
80 else
81 throw new Error('No previous provider');
82 }
83
84 _probePlugins() {
85 this._availablePlugins = [];
86
87 for (let module in imports.transitplugins) {
88 for (let pluginClass in imports.transitplugins[module]) {
89 this._availablePlugins[pluginClass] = module;
90 }
91 }
92 }
93
94 /**
95 * Get attribution for a provider. Returns a language-specific
96 * 'attribution:<lang>' tag if available, otherwise 'attribution'
97 */
98 _getAttributionForProvider(provider) {
99 if (provider['attribution:' + this._language])
100 return provider['attribution:' + this._language];
101 else if (provider.attribution)
102 return provider.attribution;
103 else
104 return null;
105 }
106
107 _getMatchingProvidersForLocation(location) {
108 let country = Utils.getCountryCodeForCoordinates(location.latitude,
109 location.longitude);
110
111 let matchingProviders = [];
112
113 this._providers.forEach((p) => {
114 let provider = p.provider;
115 let areas = provider.areas;
116
117 if (!areas) {
118 Utils.debug('No coverage info for provider ' + provider.name);
119 return;
120 }
121
122 areas.forEach((area) => {
123 /* if the area has a specified priority, override the
124 * overall area priority, this allows sub-areas of of
125 * coverage for a provider to have higher or lowe priorities
126 * than other providers (e.g. one "native" to that area
127 */
128 if (area.priority)
129 provider.priority = area.priority;
130
131 let countries = area.countries;
132
133 if (countries) {
134 if (countries.includes(country)) {
135 matchingProviders[provider.name] = provider;
136 return;
137 }
138 }
139
140 let bbox = area.bbox;
141
142 if (bbox) {
143 if (bbox.length !== 4) {
144 Utils.debug('malformed bounding box for provider ' + provider.name);
145 return;
146 }
147
148 let [x1, y1, x2, y2] = bbox;
149 let cbbox = new Champlain.BoundingBox({ bottom: x1,
150 left: y1,
151 top: x2,
152 right: y2 });
153
154 if (cbbox.covers(location.latitude,
155 location.longitude)) {
156 matchingProviders[provider.name] = provider;
157 return;
158 }
159 }
160 });
161 });
162
163 return matchingProviders;
164 }
165
166 /**
167 * Get the most preferred provider for a given query.
168 * Return: an array with the provider definition and the plugin instance,
169 * or null if no matching provider was found.
170 */
171 _getBestProviderForQuery() {
172 let startLocation = this._query.filledPoints[0].place.location;
173 let endLocation =
174 this._query.filledPoints.last().place.location;
175
176 let matchingProvidersForStart =
177 this._getMatchingProvidersForLocation(startLocation);
178 let matchingProvidersForEnd =
179 this._getMatchingProvidersForLocation(endLocation);
180
181 let matchingProviders = [];
182
183 // check all candidate providers matching on the start location
184 for (let name in matchingProvidersForStart) {
185 let providerAtStart = matchingProvidersForStart[name];
186 let providerAtEnd = matchingProvidersForEnd[name];
187
188 /* if the provider also matches on the end location, consider it
189 * as a potential candidate
190 */
191 if (providerAtEnd) {
192 let order = this._sortProviders(providerAtStart, providerAtEnd);
193
194 /* add the provider at it lowest priority to favor higher
195 * priority providers in "fringe cases"
196 */
197 if (order < 0)
198 matchingProviders.push(providerAtStart);
199 else
200 matchingProviders.push(providerAtEnd);
201 }
202 }
203
204 matchingProviders.sort(this._sortProviders);
205
206 for (let i = 0; i < matchingProviders.length; i++) {
207 let provider = matchingProviders[i];
208 let plugin = provider.plugin;
209
210 if (this._providerCache[provider.name])
211 return [provider, this._providerCache[provider.name]];
212
213 let module = this._availablePlugins[plugin];
214
215 if (module) {
216 try {
217 let params = provider.params;
218 let instance =
219 params ? new imports.transitplugins[module][plugin](params) :
220 new imports.transitplugins[module][plugin]();
221
222 this._providerCache[provider.name] = instance;
223
224 return [provider, instance];
225 } catch (e) {
226 Utils.debug('Failed to instanciate transit plugin: ' +
227 plugin + ": " + e);
228 }
229 } else {
230 Utils.debug('Transit provider plugin not available: ' + plugin);
231 }
232 }
233
234 Utils.debug('No suitable transit provider found');
235 return null;
236 }
237
238 /**
239 * Sort function to sort providers in by preference.
240 * If both providers have a priority set, prefers the one with a lower
241 * value (higher prio), otherwise the one that has a priority set (if any),
242 * else no specific order.
243 */
244 _sortProviders(p1, p2) {
245 if (p1.priority && p2.priority)
246 return p1.priority - p2.priority;
247 else if (p1.priority)
248 return -1;
249 else if (p2.priority)
250 return 1;
251 else
252 return 0;
253 }
254 };
0 /* -*- Mode: JS2; indent-tabs-mode: nil; js2-basic-offset: 4 -*- */
1 /* vim: set et ts=4 sw=4: */
2 /*
3 * Copyright (c) 2019 Marcus Lundblad
4 *
5 * GNOME Maps is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by the
7 * Free Software Foundation; either version 2 of the License, or (at your
8 * option) any later version.
9 *
10 * GNOME Maps is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13 * for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with GNOME Maps; if not, see <http://www.gnu.org/licenses/>.
17 *
18 * Author: Marcus Lundblad <ml@update.uu.se>
19 */
20
21 const Champlain = imports.gi.Champlain;
22 const GLib = imports.gi.GLib;
23 const Soup = imports.gi.Soup;
24
25 const Utils = imports.utils;
26
27 const BASE_URL = 'https://gis.gnome.org/services/aux';
28
29 var TransitTweaks = class {
30 constructor(params) {
31 this._name = params.name;
32 this._session =
33 new Soup.Session({ user_agent: 'gnome-maps/' + pkg.version });
34
35 if (!this._name)
36 throw new Error('Missing tweak name');
37 }
38
39 applyTweaks(itineraries, callback) {
40 if (!this._tweaks) {
41 let variable = 'TRANSIT_TWEAKS_' + this._name.toUpperCase();
42 let filename = GLib.getenv(variable);
43
44 if (filename) {
45 this._readTweaksFromFile(filename);
46 this._doApplyTweaks(itineraries, callback);
47 } else {
48 this._fetchTweaksAsync(itineraries, callback);
49 }
50 } else {
51 this._doApplyTweaks(itineraries, callback);
52 }
53 }
54
55 _doApplyTweaks(itineraries, callback) {
56 if (this._tweaks !== {}) {
57 itineraries.forEach((itinerary) =>
58 this._applyTweaksToItinerary(itinerary));
59 }
60
61 callback();
62 }
63
64 _readTweaksFromFile(filename) {
65 let data = Utils.readFile(filename);
66
67 if (!data) {
68 Utils.debug('Failed to read from tweak file');
69 callback();
70 }
71
72 try {
73 this._tweaks = JSON.parse(Utils.getBufferText(data));
74 } catch (e) {
75 Utils.debug('Failed to parse tweaks: ' + e);
76 this._tweaks = {};
77 }
78 }
79
80 _fetchTweaksAsync(itineraries, callback) {
81 let uri = new Soup.URI(BASE_URL + '/' + 'tweaks-' + this._name + '.json');
82 let request = new Soup.Message({ method: 'GET', uri: uri });
83
84 this._session.queue_message(request, (obj, message) => {
85 if (message.status_code !== Soup.Status.OK) {
86 Utils.debug('Failed to download tweaks');
87 callback();
88 } else {
89 try {
90 this._tweaks = JSON.parse(message.response_body.data);
91 } catch (e) {
92 Utils.debug('Failed to parse tweaks: ' + e);
93 this._tweaks = {};
94 }
95
96 this._doApplyTweaks(itineraries, callback);
97 }
98 });
99 }
100
101 _applyTweaksToItinerary(itinerary) {
102 itinerary.legs.forEach((leg) => {
103 if (leg.transit)
104 this._applyTweaksToLeg(leg);
105 });
106 }
107
108 _applyTweaksToLeg(leg) {
109 let agencyTweaks = this._tweaks.agencies[leg.agencyName];
110
111 if (agencyTweaks) {
112 let routeTypeTweaks = agencyTweaks.routeTypes[leg.routeType];
113
114 if (routeTypeTweaks) {
115 let tweakToApply;
116 let bboxTweaks = routeTypeTweaks.bboxes;
117 let routeTweaks = routeTypeTweaks.routes ?
118 routeTypeTweaks.routes[leg.route] || null : null;
119 let routePatternTweaks = routeTypeTweaks.routePatterns;
120
121 // first check for boundingbox-specific tweaks
122 if (bboxTweaks) {
123 bboxTweaks.forEach((tweak) => {
124 let bbox = tweak.bbox;
125 let cbbox = new Champlain.BoundingBox({ bottom: bbox[0],
126 left: bbox[1],
127 top: bbox[2],
128 right: bbox[3] });
129
130 if (cbbox.covers(leg.polyline[0].latitude,
131 leg.polyline[0].longitude)) {
132 /* if boundingbox fits, use embedded route or
133 * route pattern tweaks
134 */
135 routeTweaks = tweak.routes ?
136 tweak.routes[leg.route] : null;
137 routePatternTweaks = tweak.routePatterns;
138 }
139 });
140 }
141
142 if (routeTweaks) {
143 tweakToApply = routeTweaks;
144 } else if (routePatternTweaks) {
145 routePatternTweaks.forEach((pattern) => {
146 if (!(pattern.regex instanceof RegExp)) {
147 pattern.regex = new RegExp(pattern.regex);
148 }
149
150 if (leg.route.match(pattern.regex))
151 tweakToApply = pattern;
152 });
153 }
154
155 if (!tweakToApply) {
156 tweakToApply = routeTypeTweaks;
157 }
158
159 this._applyRouteTweaksToLeg(leg, tweakToApply);
160 }
161 }
162 }
163
164 _applyRouteTweaksToLeg(leg, tweaks) {
165 if (tweaks.route)
166 leg.route = tweaks.route;
167
168 if (tweaks.routeType)
169 leg.routeType = tweaks.routeType;
170
171 if (tweaks.color)
172 leg.color = tweaks.color;
173
174 if (tweaks.textColor)
175 leg.textColor = tweaks.textColor;
176 }
177 }
0 This directory contains implementations of transit routing provider plugins.
1 Each plugin should contain an ES6 class implementing the plugin.
2
3 Each implementation implements two methods:
4
5 fetchFirstResults():
6
7 This is invoked when the singleton routing query has been updated and would
8 query itineraries from it's source, and on success populate the TransitPlan
9 singleton with an itinerary list and call plan.update(), or on error call
10 one of the pre-defined error methods on the plan, or trigger a custom error
11 with plan.error().
12
13 fetchMoreResults():
14
15 This is invoked when to fetch additional (later or earlier) results.
16 Would on success add additional itineraries and call plan.update(), or on
17 error call plan.noMoreResults().
18
19 Providers are configured via the downloaded service file using a JSON element
20 like:
21
22 "transitProviders": [
23 {
24 "provider": {
25 "name": "Description of provider 1",
26 "plugin": "OpenTripPlanner",
27 "attribution": "Provider 1",
28 "attributionUrl": "http://provider1.example.com",
29 "priority": 10,
30 "areas": [
31 {
32 "priority": 10,
33 "countries": [ "UT" ]
34 }
35 ],
36 "params": {
37 "baseUrl": "http://otp.provider1.example.com/otp"
38 }
39 }
40 },
41 {
42 "provider": {
43 "name": "Provider 2",
44 "plugin": "OpenTripPlanner",
45 "attribution": "Provider 2",
46 "attributionUrl": "https://provider2.example.com",
47 "areas": [
48 {
49 "bbox": [48.28,0.81,49.73,4.11]
50 }
51 ],
52 "params": {
53 "baseUrl": "https://provider2.example.com/otp"
54 }
55 }
56 }
57 ]
58
59 Each provider can have an optional priority, to allow more specific provider
60 (e.g. one serving a single city) within the area of a more general one.
61 Single sub-areas of a provider can also override the general provider priority.
62 This can be used to allow areas of provider to "shadow" neighboring providers
63 while keeping the the neighboring provider as the preferred one when used
64 exclusively for its region.
65 Custom parameters, if specified, will be passed as the "params" object the
66 constructor() of the plugin implementation.
0 /* -*- Mode: JS2; indent-tabs-mode: nil; js2-basic-offset: 4 -*- */
1 /* vim: set et ts=4 sw=4: */
2 /*
3 * Copyright (c) 2017 Marcus Lundblad
4 *
5 * GNOME Maps is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by the
7 * Free Software Foundation; either version 2 of the License, or (at your
8 * option) any later version.
9 *
10 * GNOME Maps is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13 * for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with GNOME Maps; if not, see <http://www.gnu.org/licenses/>.
17 *
18 * Author: Marcus Lundblad <ml@update.uu.se>
19 */
20
21 const _ = imports.gettext.gettext;
22
23 const Champlain = imports.gi.Champlain;
24 const GLib = imports.gi.GLib;
25 const Soup = imports.gi.Soup;
26
27 const Application = imports.application;
28 const EPAF = imports.epaf;
29 const GraphHopperTransit = imports.graphHopperTransit;
30 const HTTP = imports.http;
31 const HVT = imports.hvt;
32 const Location = imports.location;
33 const Place = imports.place;
34 const Route = imports.route;
35 const RouteQuery = imports.routeQuery;
36 const Service = imports.service;
37 const TransitPlan = imports.transitPlan;
38 const Utils = imports.utils;
39
40 /**
41 * This module implements the interface for communicating with an OpenTripPlanner
42 * server instance.
43 * The code is somewhat intricate since it supports instances of OpenTripPlanner
44 * running both with and without OSM data to complement the transit timetable
45 * data with turn-by-turn (walking) routing.
46 *
47 * There is two entry points for obtaining routes, one which is called by the
48 * routing delegator when the query is being modified (fetchFirstResults()),
49 * and the other being called when requesting additional results (later or
50 * earlier alternatives depending on search criteria) (fetchMoreResults())
51 * These call into an entry point function "_fatchRoute()".
52 * "_fetchRoutes()" is called.
53 * In the case where there is no OSM data (onlyTransitData is true), it
54 * asyncronously calls "_fetchTransitStops()" to get closest transit stop for
55 * each of the query point, this function will involve OpenTripPlanner calls to
56 * find stops within a search circle around the coordinate and then calls out
57 * to GraphHopper to find the actual walking distance and selects the closest
58 * stop.
59 * In the callback for the results of _fetchTransitStops() the actual call to
60 * OpenTripPlanner is made.
61 * The callback for "_fetchRoutes()" will in turn reformat the resulting
62 * itineraries obtained from OpenTripPlanner into our internal format specified
63 * in the TransitPlan module and calls "_recalculateItineraries()" which will
64 * traverse these and do yet another recursive asyncrous pass over these results
65 * to refine the results with actual walking routes obtained from GraphHopper,
66 * since the results from our OpenTripPlanner instance with only transit data
67 * will result in walking legs which use "as the crow flies" routes for walking.
68 * It will also clean up the results by pruning out pointless transit legs, such
69 * as taking transit very short distances when almost reaching the target.
70 *
71 * API docs for OpenTripPlanner can be found at: http://dev.opentripplanner.org/apidoc/1.0.0/
72 */
73
74 /* minimum distance when an explicit walk route will be requested to suppliment
75 * the transit route
76 */
77 const MIN_WALK_ROUTING_DISTANCE = 100;
78
79 /* minimum distance of a transit leg, below which we would replace the leg with
80 * walking if the leg is the first or last
81 */
82 const MIN_TRANSIT_LEG_DISTANCE = 300;
83
84 /* maximum walking distance for a potential replacement of a beginning or ending
85 * transit leg */
86 const MAX_WALK_OPTIMIZATION_DISTANCE = 1000;
87
88 /* maximum distance difference for performing a replacement of a beginning or
89 * ending transit leg with a walking leg
90 */
91 const MAX_WALK_OPTIMIZATION_DISTANCE_DIFFERENCE = 500;
92
93 /* minimum acceptable time margin when recalculating walking legs in the middle
94 * of an itinerary
95 */
96 const MIN_INTERMEDIATE_WALKING_SLACK = 60;
97
98 /* maximum walking distance, filter out itineraries containing walking legs
99 * whith longer walking after refined by GraphHopper
100 */
101 const MAX_WALKING_DISTANCE = 2000;
102
103 // maximum radius to search for stops
104 const STOP_SEARCH_RADIUS = 2000;
105
106 // maximum number of transit stops to consider as candidates for start/end points
107 const NUM_STOPS_TO_TRY = 5;
108
109 // gap to use when fetching additional routes
110 const GAP_BEFORE_MORE_RESULTS = 120;
111
112 var OpenTripPlanner = class OpenTripPlanner {
113
114 constructor(params) {
115 this._session = new Soup.Session({ user_agent : 'gnome-maps/' + pkg.version });
116 this._plan = Application.routingDelegator.transitRouter.plan;
117 this._query = Application.routeQuery;
118 this._baseUrl = params.baseUrl;
119 this._router = params.router || 'default';
120 this._routerUrl = params.routerUrl || null;
121 this._onlyTransitData = params.onlyTransitData || false;
122 this._extendPrevious = false;
123 this._language = Utils.getLanguage();
124
125 if (!this._baseUrl && !this._routerUrl)
126 throw new Error('must specify either baseUrl or routerUrl as an argument');
127
128 if (this._baseUrl && this._routerUrl)
129 throw new Error('can not specify both baseUrl and routerUrl as arguments');
130 }
131
132 get plan() {
133 return this._plan;
134 }
135
136 get enabled() {
137 return this._baseUrl !== null;
138 }
139
140 fetchFirstResults() {
141 this._extendPrevious = false;
142 this._fetchRoute();
143 }
144
145 fetchMoreResults() {
146 this._extendPrevious = true;
147 this._fetchRoute();
148 }
149
150 _getRouterUrl() {
151 return this._routerUrl ? this._routerUrl :
152 this._baseUrl + '/routers/' + this._router;
153 }
154
155 _getMode(routeType) {
156 switch (routeType) {
157 case TransitPlan.RouteType.TRAM:
158 return 'TRAM';
159 case TransitPlan.RouteType.TRAIN:
160 return 'RAIL';
161 case TransitPlan.RouteType.SUBWAY:
162 return 'SUBWAY';
163 case TransitPlan.RouteType.BUS:
164 return 'BUS';
165 case TransitPlan.RouteType.FERRY:
166 return 'FERRY';
167 case HVT.AIR_SERVICE:
168 return 'AIRPLANE';
169 default:
170 throw new Error('unhandled route type');
171 }
172 }
173
174 _getModes(options) {
175 let modes = options.transitTypes.map((transitType) => {
176 return this._getMode(transitType);
177 });
178
179 /* should always include walk when setting explicit modes,
180 * otherwise only routes ending close to a stop would work
181 */
182 modes.push('WALK');
183
184 return modes.join(',');
185 }
186
187 _selectBestStopRecursive(stops, index, stopIndex, callback) {
188 if (index < stops.length) {
189 let points = this._query.filledPoints;
190 let stop = stops[index];
191 let stopPoint =
192 GraphHopperTransit.createQueryPointForCoord([stop.lat, stop.lon]);
193
194 if (stops[0].dist < 100) {
195 /* if the stop is close enough to the intended point, just
196 * return the top most from the the original query */
197 this._selectBestStopRecursive(stops, index + 1, stopIndex,
198 callback);
199 } else if (stopIndex === 0) {
200 GraphHopperTransit.fetchWalkingRoute([points[0], stopPoint],
201 (route) => {
202 /* if we couldn't find an exact walking route, go with the
203 * "as the crow flies" distance */
204 if (route)
205 stop.dist = route.distance;
206 this._selectBestStopRecursive(stops, index + 1, stopIndex,
207 callback);
208 });
209 } else if (stopIndex === points.length - 1) {
210 GraphHopperTransit.fetchWalkingRoute([stopPoint, points.last()],
211 (route) => {
212 if (route)
213 stop.dist = route.distance;
214 this._selectBestStopRecursive(stops, index + 1, stopIndex,
215 callback);
216 });
217 } else {
218 /* for intermediate stops just return the one geographically
219 * closest */
220 this._selectBestStopRecursive(stops, index + 1, stopIndex,
221 callback);
222 }
223 } else {
224 /* re-sort stops by distance and select the closest after refining
225 * distances */
226 stops.sort(this._sortTransitStops);
227 Utils.debug('refined stops: ');
228 stops.forEach((stop) => Utils.debug(JSON.stringify(stop, '', 2)));
229 callback(stops[0]);
230 }
231 }
232
233 /* stopIndex here is the index of stop (i.e. starting point, intermediate
234 * stop, final stop
235 */
236 _selectBestStop(stops, stopIndex, callback) {
237 this._selectBestStopRecursive(stops, 0, stopIndex, callback);
238 }
239
240 _sortTransitStops(s1, s2) {
241 return s1.dist > s2.dist;
242 }
243
244 _fetchRoutesForStop(stop, callback) {
245 let query = new HTTP.Query();
246 let uri = new Soup.URI(this._getRouterUrl() + '/index/stops/' +
247 stop.id + '/routes');
248 let request = new Soup.Message({ method: 'GET', uri: uri });
249
250 request.request_headers.append('Accept', 'application/json');
251 this._session.queue_message(request, (obj, message) => {
252 if (message.status_code !== Soup.Status.OK) {
253 Utils.debug('Failed to get routes for stop');
254 this._reset();
255 } else {
256 let routes = JSON.parse(message.response_body.data);
257
258 Utils.debug('Routes for stop: ' + stop + ': ' + JSON.stringify(routes));
259 callback(routes);
260 }
261 });
262 }
263
264 _routeMatchesSelectedModes(route) {
265 let desiredTransitTypes = this._query.transitOptions.transitTypes;
266
267 for (let i = 0; i < desiredTransitTypes.length; i++) {
268 let type = desiredTransitTypes[i];
269
270 if (type === TransitPlan.RouteType.TRAM && route.mode === 'TRAM')
271 return true;
272 else if (type === TransitPlan.RouteType.SUBWAY && route.mode === 'SUBWAY')
273 return true;
274 else if (type === TransitPlan.RouteType.TRAIN && route.mode === 'RAIL')
275 return true;
276 else if (type === TransitPlan.RouteType.BUS &&
277 (route.mode === 'BUS' || route.mode === 'TAXI'))
278 return true;
279 else if (type === TransitPlan.RouteType.FERRY && route.mode === 'FERRY')
280 return true;
281 }
282
283 return false;
284 }
285
286 _filterStopsRecursive(stops, index, filteredStops, callback) {
287 if (index < stops.length) {
288 let stop = stops[index];
289
290 this._fetchRoutesForStop(stop, (routes) => {
291 for (let i = 0; i < routes.length; i++) {
292 let route = routes[i];
293
294 if (this._routeMatchesSelectedModes(route)) {
295 filteredStops.push(stop);
296 break;
297 }
298 }
299 this._filterStopsRecursive(stops, index + 1, filteredStops,
300 callback);
301 });
302 } else {
303 callback(filteredStops);
304 }
305 }
306
307 _filterStops(stops, callback) {
308 this._filterStopsRecursive(stops, 0, [], callback);
309 }
310
311 _fetchTransitStopsRecursive(index, result, callback) {
312 let points = this._query.filledPoints;
313
314 if (index < points.length) {
315 let point = points[index];
316 let params = { lat: point.place.location.latitude,
317 lon: point.place.location.longitude,
318 radius: STOP_SEARCH_RADIUS };
319 let query = new HTTP.Query(params);
320 let uri = new Soup.URI(this._getRouterUrl() +
321 '/index/stops?' + query.toString());
322 let request = new Soup.Message({ method: 'GET', uri: uri });
323
324 request.request_headers.append('Accept', 'application/json');
325 this._session.queue_message(request, (obj, message) => {
326 if (message.status_code !== Soup.Status.OK) {
327 Utils.debug('Failed to get stop for search point ' + point);
328 this._reset();
329 } else {
330 let stops = JSON.parse(message.response_body.data);
331
332 if (stops.length === 0) {
333 Utils.debug('No suitable stop found from router');
334 callback(null);
335 return;
336 }
337
338 if (this._query.transitOptions.showAllTransitTypes) {
339 stops.sort(this._sortTransitStops);
340 stops = stops.splice(0, NUM_STOPS_TO_TRY);
341
342 Utils.debug('stops: ' + JSON.stringify(stops, '', 2));
343 this._selectBestStop(stops, index, (stop) => {
344 result.push(stop);
345 this._fetchTransitStopsRecursive(index + 1, result,
346 callback);
347 });
348 } else {
349 this._filterStops(stops, (filteredStops) => {
350 filteredStops.sort(this._sortTransitStops);
351 filteredStops = filteredStops.splice(0, NUM_STOPS_TO_TRY);
352
353 if (filteredStops.length === 0) {
354 Utils.debug('No suitable stop found using selected transit modes');
355 callback(null);
356 return;
357 }
358
359 this._selectBestStop(filteredStops, index, (stop) => {
360 result.push(stop);
361 this._fetchTransitStopsRecursive(index + 1,
362 result, callback);
363 });
364 });
365 }
366 }
367 });
368 } else {
369 callback(result);
370 }
371 }
372
373 _fetchTransitStops(callback) {
374 this._fetchTransitStopsRecursive(0, [], callback);
375 }
376
377 // get a time suitably formatted for the OpenTripPlanner query param
378 _formatTime(time, offset) {
379 let utcTimeWithOffset = (time + offset) / 1000;
380 let date = GLib.DateTime.new_from_unix_utc(utcTimeWithOffset);
381
382 return date.format('%R');
383 }
384
385 // get a date suitably formatted for the OpenTripPlanner query param
386 _formatDate(time, offset) {
387 let utcTimeWithOffset = (time + offset) / 1000;
388 let date = GLib.DateTime.new_from_unix_utc(utcTimeWithOffset);
389
390 return date.format('%F');
391 }
392
393 _getPlaceParamFromLocation(location) {
394 return location.latitude + ',' + location.longitude;
395 }
396
397 _addCommonParams(params) {
398 params.numItineraries = 5;
399 params.showIntermediateStops = true;
400 params.locale = this._language;
401
402 let time = this._query.time;
403 let date = this._query.date;
404
405 if (this._extendPrevious) {
406 let itineraries = this.plan.itineraries;
407 let lastItinerary = itineraries.last();
408 let time;
409 let offset;
410
411 if (this._query.arriveBy) {
412 time = lastItinerary.transitArrivalTime -
413 GAP_BEFORE_MORE_RESULTS * 1000;
414 offset = lastItinerary.transitArrivalTimezoneOffset;
415 } else {
416 time = lastItinerary.transitDepartureTime +
417 GAP_BEFORE_MORE_RESULTS * 1000;
418 offset = lastItinerary.transitDepartureTimezoneOffset;
419 }
420
421 params.time = this._formatTime(time, offset);
422 params.date = this._formatDate(time, offset);
423 } else {
424 if (time) {
425 params.time = time;
426 /* it seems OTP doesn't like just setting a time, so if the query
427 * doesn't specify a date, go with today's date
428 */
429 if (!date) {
430 let dateTime = GLib.DateTime.new_now_local();
431
432 params.date = dateTime.format('%F');
433 }
434 }
435
436 if (date)
437 params.date = date;
438 }
439
440 if (this._query.arriveBy)
441 params.arriveBy = true;
442
443 let options = this._query.transitOptions;
444 if (options && !options.showAllTransitTypes)
445 params.mode = this._getModes(options);
446 }
447
448 _createParamsWithLocations() {
449 let points = this._query.filledPoints;
450 let params = {
451 fromPlace: this._getPlaceParamFromLocation(points[0].place.location),
452 toPlace: this._getPlaceParamFromLocation(points[points.length - 1].place.location) };
453 let intermediatePlaces = [];
454
455 for (let i = 1; i < points.length - 1; i++) {
456 let location = points[i].place.location;
457 intermediatePlaces.push(this._getPlaceParamFromLocation(location));
458 }
459 if (intermediatePlaces)
460 params.intermediatePlaces = intermediatePlaces;
461
462 params.maxWalkDistance = 2500;
463 this._addCommonParams(params);
464
465 return params;
466 }
467
468 // create parameter map for the request, given query and options
469 _createParamsWithStops(stops) {
470 let params = { fromPlace: stops[0].id,
471 toPlace: stops.last().id };
472 let intermediatePlaces = [];
473
474 for (let i = 1; i < stops.length - 1; i++) {
475 intermediatePlaces.push(stops[i].id);
476 }
477 if (intermediatePlaces.length > 0)
478 params.intermediatePlaces = intermediatePlaces;
479
480 /* set walking speed for transfers to a slightly lower value to
481 * compensate for running OTP with only transit data, giving straight-
482 * line walking paths
483 */
484 params.walkSpeed = 1.0;
485
486 this._addCommonParams(params);
487
488 return params;
489 }
490
491 _fetchPlan(params, callback) {
492 let query = new HTTP.Query(params);
493 let uri = new Soup.URI(this._getRouterUrl() + '/plan?' +
494 query.toString());
495 let request = new Soup.Message({ method: 'GET', uri: uri });
496
497 request.request_headers.append('Accept', 'application/json');
498 this._session.queue_message(request, (obj, message) => {
499 if (message.status_code !== Soup.Status.OK) {
500 Utils.debug('Failed to get route plan from router ' +
501 this._router + ' ' + message);
502 callback(null);
503 } else {
504 try {
505 let result = JSON.parse(message.response_body.data);
506
507 callback(result);
508 } catch (e) {
509 Utils.debug('Error parsing result: ' + e);
510 callback(null);
511 }
512 }
513 });
514 }
515
516 _fetchRoutes(callback) {
517 if (this._onlyTransitData) {
518 this._fetchTransitStops((stops) => {
519 let points = this._query.filledPoints;
520
521 if (!stops) {
522 callback(null);
523 return;
524 }
525
526 /* if there's only a start and end stop (no intermediate stops)
527 * and those stops are identical, reject the routing, since this
528 * means there would be no point in transit, and OTP would give
529 * some bizarre option like boarding transit, go one stop and then
530 * transfer to go back the same route
531 */
532 if (stops.length === 2 && stops[0].id === stops[1].id) {
533 callback(null);
534 return;
535 }
536
537 let params = this._createParamsWithStops(stops);
538
539 this._fetchPlan(params, callback);
540 });
541 } else {
542 let params = this._createParamsWithLocations();
543
544 this._fetchPlan(params, callback);
545 }
546 }
547
548 _reset() {
549 this._extendPrevious = false;
550 if (this._query.latest)
551 this._query.latest.place = null;
552 else
553 this.plan.reset();
554 }
555
556 /* Indicate that no routes where found, either shows the "No route found"
557 * message, or in case of loading additional (later/earlier) results,
558 * indicate no such where found, so that the sidebar can disable the
559 * "load more" functionallity as appropriate.
560 */
561 _noRouteFound() {
562 if (this._extendPrevious) {
563 this._extendPrevious = false;
564 this.plan.noMoreResults();
565 } else {
566 this._reset();
567 this.plan.noRouteFound();
568 }
569 }
570
571 _fetchRoute() {
572 let points = this._query.filledPoints;
573
574 this._fetchRoutes((route) => {
575 if (route) {
576 let itineraries = [];
577 let plan = route.plan;
578
579 Utils.debug('route: ' + JSON.stringify(route, null, 2));
580
581 if (plan && plan.itineraries) {
582 itineraries =
583 itineraries.concat(
584 this._createItineraries(plan.itineraries));
585 }
586
587 if (itineraries.length === 0) {
588 /* don't reset query points, unlike for turn-based
589 * routing, since options and timeing might influence
590 * results */
591 this._noRouteFound();
592 } else {
593 if (this._onlyTransitData)
594 this._recalculateItineraries(itineraries);
595 else
596 this._updateWithNewItineraries(itineraries);
597 }
598 } else {
599 this._noRouteFound();
600 }
601 });
602 }
603
604 _isOnlyWalkingItinerary(itinerary) {
605 return itinerary.legs.length === 1 && !itinerary.legs[0].transit;
606 }
607
608 _recalculateItineraries(itineraries) {
609 // filter out itineraries with only walking
610 let newItineraries = [];
611
612 itineraries.forEach((itinerary) => {
613 if (!this._isOnlyWalkingItinerary(itinerary))
614 newItineraries.push(itinerary);
615 });
616
617 /* TODO: should we always calculate a walking itinerary to put at the
618 * top if the total distance is below some threashhold?
619 */
620 this._recalculateItinerariesRecursive(newItineraries, 0);
621 }
622
623 _isItineraryRealistic(itinerary) {
624 for (let i = 0; i < itinerary.legs.length; i++) {
625 let leg = itinerary.legs[i];
626
627 if (!leg.transit) {
628 /* if a walking leg exceeds the maximum desired walking
629 * distance, or for a leg "in-between" two transit legs, if
630 * there's insufficent switch time
631 */
632 if (leg.distance > MAX_WALKING_DISTANCE) {
633 return false;
634 } else if (i >= 1 && i < itinerary.legs.length - 1) {
635 let previousLeg = itinerary.legs[i - 1];
636 let nextLeg = itinerary.legs[i + 1];
637
638 let availableTime =
639 (nextLeg.departure - previousLeg.arrival) / 1000;
640
641 if (availableTime <
642 leg.duration + MIN_INTERMEDIATE_WALKING_SLACK)
643 return false;
644 }
645 }
646 }
647
648 return true;
649 }
650
651 /**
652 * Update plan with new itineraries, setting the new itineraries if it's
653 * the first fetch for a query, or extending the existing ones if it's
654 * a request to load more
655 */
656 _updateWithNewItineraries(itineraries) {
657 this.plan.updateWithNewItineraries(itineraries, this._query.arriveBy,
658 this._extendPrevious);
659 this._extendPrevious = false;
660 }
661
662 _recalculateItinerariesRecursive(itineraries, index) {
663 if (index < itineraries.length) {
664 this._recalculateItinerary(itineraries[index], (itinerary) => {
665 itineraries[index] = itinerary;
666 this._recalculateItinerariesRecursive(itineraries, index + 1);
667 });
668 } else {
669 /* filter out itineraries where there are intermediate walking legs
670 * that are too narrow time-wise, this is nessesary since running
671 * OTP with only transit data can result in some over-optimistic
672 * walking itinerary legs, since it will use "line-of-sight"
673 * distances.
674 * also filter out itineraries where recalculation process ended
675 * up with just walking
676 */
677 let filteredItineraries = [];
678
679 itineraries.forEach((itinerary) => {
680 if (this._isItineraryRealistic(itinerary) &&
681 !this._isOnlyWalkingItinerary(itinerary))
682 filteredItineraries.push(itinerary);
683 });
684
685 if (filteredItineraries.length > 0) {
686 filteredItineraries.forEach((itinerary) => itinerary.adjustTimings());
687 this._updateWithNewItineraries(filteredItineraries);
688 } else {
689 this._noRouteFound();
690 }
691 }
692 }
693
694 _recalculateItinerary(itinerary, callback) {
695 let from = this._query.filledPoints[0];
696 let to = this._query.filledPoints.last();
697
698 if (itinerary.legs.length === 1 && !itinerary.legs[0].transit) {
699 /* special case, if there's just one leg of an itinerary, and that leg
700 * leg is a non-transit (walking), recalculate the route in its entire
701 * using walking
702 */
703 GraphHopperTransit.fetchWalkingRoute(this._query.filledPoints,
704 (route) => {
705 let leg = GraphHopperTransit.createWalkingLeg(from, to,
706 from.place.name,
707 to.place.name,
708 route);
709 let newItinerary =
710 new TransitPlan.Itinerary({departure: itinerary.departure,
711 duration: route.time / 1000,
712 legs: [leg]});
713 callback(newItinerary);
714 });
715 } else if (itinerary.legs.length === 1 && itinerary.legs[0].transit) {
716 // special case if there is extactly one transit leg
717 let leg = itinerary.legs[0];
718 let startLeg = GraphHopperTransit.createQueryPointForCoord(leg.fromCoordinate);
719 let endLeg = GraphHopperTransit.createQueryPointForCoord(leg.toCoordinate);
720 let fromLoc = from.place.location;
721 let startLoc = startLeg.place.location;
722 let endLoc = endLeg.place.location;
723 let toLoc = to.place.location;
724 let startWalkDistance = fromLoc.get_distance_from(startLoc) * 1000;
725 let endWalkDistance = endLoc.get_distance_from(toLoc) * 1000;
726
727 if (startWalkDistance >= MIN_WALK_ROUTING_DISTANCE &&
728 endWalkDistance >= MIN_WALK_ROUTING_DISTANCE) {
729 /* add an extra walking leg to both the beginning and end of the
730 * itinerary
731 */
732 GraphHopperTransit.fetchWalkingRoute([from, startLeg],
733 (firstRoute) => {
734 let firstLeg =
735 GraphHopperTransit.createWalkingLeg(from, startLeg,
736 from.place.name,
737 leg.from, firstRoute);
738 GraphHopperTransit.fetchWalkingRoute([endLeg, to],
739 (lastRoute) => {
740 let lastLeg =
741 GraphHopperTransit.createWalkingLeg(endLeg, to,
742 leg.to,
743 to.place.name,
744 lastRoute);
745 itinerary.legs.unshift(firstLeg);
746 itinerary.legs.push(lastLeg);
747 callback(itinerary);
748 });
749 });
750 } else if (endWalkDistance >= MIN_WALK_ROUTING_DISTANCE) {
751 // add an extra walking leg to the end of the itinerary
752 GraphHopperTransit.fetchWalkingRoute([endLeg, to],
753 (lastRoute) => {
754 let lastLeg =
755 GraphHopperTransit.createWalkingLeg(endLeg, to, leg.to,
756 to.place.name,
757 lastRoute);
758 itinerary.legs.push(lastLeg);
759 callback(itinerary);
760 });
761 } else {
762 /* if only there's only a walking leg to be added to the start
763 * let the recursive routine dealing with multi-leg itineraries
764 * handle it
765 */
766 this._recalculateItineraryRecursive(itinerary, 0, callback);
767 }
768 } else {
769 /* replace walk legs with GraphHopper-generated paths (hence the
770 * callback nature of this. Filter out unrealistic itineraries (having
771 * walking segments not possible in reasonable time, due to our running
772 * of OTP with only transit data).
773 */
774 this._recalculateItineraryRecursive(itinerary, 0, callback);
775 }
776 }
777
778 _recalculateItineraryRecursive(itinerary, index, callback) {
779 if (index < itinerary.legs.length) {
780 let leg = itinerary.legs[index];
781 if (index === 0) {
782 let from = this._query.filledPoints[0];
783 let startLeg =
784 GraphHopperTransit.createQueryPointForCoord(leg.fromCoordinate);
785 let endLeg =
786 GraphHopperTransit.createQueryPointForCoord(leg.toCoordinate);
787 let fromLoc = from.place.location;
788 let startLegLoc = startLeg.place.location;
789 let endLegLoc = endLeg.place.location;
790 let distanceToEndLeg =
791 fromLoc.get_distance_from(endLegLoc) * 1000;
792 let distanceToStartLeg =
793 fromLoc.get_distance_from(startLegLoc) * 1000;
794 let nextLeg = itinerary.legs[index + 1];
795
796 if (!leg.transit ||
797 ((leg.distance <= MIN_TRANSIT_LEG_DISTANCE ||
798 (distanceToEndLeg <= MAX_WALK_OPTIMIZATION_DISTANCE &&
799 distanceToEndLeg - distanceToStartLeg <=
800 MAX_WALK_OPTIMIZATION_DISTANCE_DIFFERENCE)) &&
801 itinerary.legs.length > 1)) {
802 /* if the first leg of the intinerary returned by OTP is a
803 * walking one, recalculate it with GH using the actual
804 * starting coordinate from the input query,
805 * also replace a transit leg at the start with walking if
806 * its distance is below a threashhold, to avoid suboptimal
807 * routes due to only running OTP with transit data,
808 * also optimize away cases where the routing would make one
809 * "pass by" a stop at the next step in the itinerary due to
810 * similar reasons
811 */
812 let to = GraphHopperTransit.createQueryPointForCoord(leg.toCoordinate);
813 let toName = leg.to;
814
815 /* if the next leg is a walking one, "fold" it into the one
816 * we create here */
817 if (nextLeg && !nextLeg.transit) {
818 to = GraphHopperTransit.createQueryPointForCoord(nextLeg.toCoordinate);
819 toName = nextLeg.to;
820 itinerary.legs.splice(index + 1, index + 1);
821 }
822
823 GraphHopperTransit.fetchWalkingRoute([from, to], (route) => {
824 let newLeg =
825 GraphHopperTransit.createWalkingLeg(from, to,
826 from.place.name,
827 toName, route);
828 itinerary.legs[index] = newLeg;
829 this._recalculateItineraryRecursive(itinerary, index + 1,
830 callback);
831 });
832 } else {
833 /* introduce an additional walking leg calculated
834 * by GH in case the OTP starting point as far enough from
835 * the original starting point
836 */
837 let to = GraphHopperTransit.createQueryPointForCoord(leg.fromCoordinate);
838 let fromLoc = from.place.location;
839 let toLoc = to.place.location;
840 let distance = fromLoc.get_distance_from(toLoc) * 1000;
841
842 if (distance >= MIN_WALK_ROUTING_DISTANCE) {
843 GraphHopperTransit.fetchWalkingRoute([from, to],
844 (route) => {
845 let newLeg =
846 GraphHopperTransit.createWalkingLeg(from, to,
847 from.place.name,
848 leg.from,
849 route);
850 itinerary.legs.unshift(newLeg);
851 /* now, next index will be two steps up, since we
852 * inserted a new leg
853 */
854 this._recalculateItineraryRecursive(itinerary,
855 index + 2,
856 callback);
857 });
858 } else {
859 this._recalculateItineraryRecursive(itinerary, index + 1,
860 callback);
861 }
862 }
863 } else if (index === itinerary.legs.length - 1) {
864 let to = this._query.filledPoints.last();
865 let startLeg =
866 GraphHopperTransit.createQueryPointForCoord(leg.fromCoordinate);
867 let endLeg =
868 GraphHopperTransit.createQueryPointForCoord(leg.toCoordinate);
869 let toLoc = to.place.location;
870 let startLegLoc = startLeg.place.location;
871 let endLegLoc = endLeg.place.location;
872 let distanceFromEndLeg =
873 toLoc.get_distance_from(endLegLoc) * 1000;
874 let distanceFromStartLeg =
875 toLoc.get_distance_from(startLegLoc) * 1000;
876 let previousLeg = itinerary.legs[itinerary.legs.length - 2];
877
878 if (!leg.transit ||
879 ((leg.distance <= MIN_TRANSIT_LEG_DISTANCE ||
880 (distanceFromStartLeg <= MAX_WALK_OPTIMIZATION_DISTANCE &&
881 distanceFromStartLeg - distanceFromEndLeg <=
882 MAX_WALK_OPTIMIZATION_DISTANCE_DIFFERENCE)) &&
883 itinerary.legs.length > 1)) {
884 /* if the final leg of the itinerary returned by OTP is a
885 * walking one, recalculate it with GH using the actual
886 * ending coordinate from the input query
887 * also replace a transit leg at the end with walking if
888 * its distance is below a threashhold, to avoid suboptimal
889 * routes due to only running OTP with transit data,
890 * also optimize away cases where the routing would make one
891 * "pass by" a stop at the previous step in the itinerary
892 * due to similar reasons
893 */
894 let finalTransitLeg;
895 let insertIndex;
896 if (leg.transit && previousLeg && !previousLeg.transit) {
897 /* if we optimize away the final transit leg, and the
898 * previous leg is a walking one, "fold" both into a
899 * single walking leg */
900 finalTransitLeg = previousLeg;
901 insertIndex = index -1;
902 itinerary.legs.pop();
903 } else {
904 finalTransitLeg = leg;
905 insertIndex = index;
906 }
907 let from =
908 GraphHopperTransit.createQueryPointForCoord(finalTransitLeg.fromCoordinate);
909 GraphHopperTransit.fetchWalkingRoute([from, to], (route) => {
910 let newLeg =
911 GraphHopperTransit.createWalkingLeg(from, to,
912 finalTransitLeg.from,
913 to.place.name, route);
914 itinerary.legs[insertIndex] = newLeg;
915 this._recalculateItineraryRecursive(itinerary,
916 insertIndex + 1,
917 callback);
918 });
919 } else {
920 /* introduce an additional walking leg calculated by GH in
921 * case the OTP end point as far enough from the original
922 * end point
923 */
924 let from = GraphHopperTransit.createQueryPointForCoord(leg.toCoordinate);
925 let fromLoc = from.place.location;
926 let toLoc = to.place.location;
927 let distance = fromLoc.get_distance_from(toLoc) * 1000;
928
929 if (distance >= MIN_WALK_ROUTING_DISTANCE) {
930 GraphHopperTransit.fetchWalkingRoute([from, to],
931 (route) => {
932 let newLeg =
933 GraphHopperTransit.createWalkingLeg(from, to,
934 leg.to, to.place.name, route);
935 itinerary.legs.push(newLeg);
936 /* now, next index will be two steps up, since we
937 * inserted a new leg
938 */
939 this._recalculateItineraryRecursive(itinerary,
940 index + 2,
941 callback);
942 });
943 } else {
944 this._recalculateItineraryRecursive(itinerary, index + 1,
945 callback);
946 }
947 }
948 } else {
949 /* if an intermediate leg is a walking one, and it's distance is
950 * above the threashhold distance, calculate an exact route
951 */
952 if (!leg.transit && leg.distance >= MIN_WALK_ROUTING_DISTANCE) {
953 let from = GraphHopperTransit.createQueryPointForCoord(leg.fromCoordinate);
954 let to = GraphHopperTransit.createQueryPointForCoord(leg.toCoordinate);
955
956 /* if the next leg is the final one of the itinerary,
957 * and it's shorter than the "optimize away" distance,
958 * create a walking leg all the way to the final destination
959 */
960 let nextLeg = itinerary.legs[index + 1];
961 if (index === itinerary.legs.length - 2 &&
962 nextLeg.distance <= MIN_TRANSIT_LEG_DISTANCE) {
963 to = this._query.filledPoints.last();
964 itinerary.legs.splice(index + 1, index + 1);
965 }
966
967 GraphHopperTransit.fetchWalkingRoute([from, to], (route) => {
968 let newLeg =
969 GraphHopperTransit.createWalkingLeg(from, to, leg.from,
970 leg.to, route);
971 itinerary.legs[index] = newLeg;
972 this._recalculateItineraryRecursive(itinerary,
973 index + 1,
974 callback);
975 });
976 } else {
977 this._recalculateItineraryRecursive(itinerary, index + 1,
978 callback);
979 }
980 }
981 } else {
982 callback(itinerary);
983 }
984 }
985
986 _createItineraries(itineraries) {
987 return itineraries.map((itinerary) => this._createItinerary(itinerary));
988 }
989
990 _createItinerary(itinerary) {
991 let legs = this._createLegs(itinerary.legs);
992 return new TransitPlan.Itinerary({ duration: itinerary.duration,
993 transfers: itinerary.transfers,
994 departure: itinerary.startTime,
995 arrival: itinerary.endTime,
996 legs: legs});
997 }
998
999 _createLegs(legs) {
1000 return legs.map((leg, index, legs) => this._createLeg(leg, index, legs));
1001 }
1002
1003 /* check if a string is a valid hex RGB string */
1004 _isValidHexColor(string) {
1005 if (string && string.length === 6) {
1006 let regex = /^[A-Fa-f0-9]/;
1007
1008 return string.match(regex);
1009 }
1010
1011 return false;
1012 }
1013
1014 _createLeg(leg, index, legs) {
1015 let polyline = EPAF.decode(leg.legGeometry.points);
1016 let color = leg.routeColor && this._isValidHexColor(leg.routeColor) ?
1017 leg.routeColor : null;
1018 let textColor = leg.routeTextColor && this._isValidHexColor(leg.routeTextColor) ?
1019 leg.routeTextColor : null;
1020 let first = index === 0;
1021 let last = index === legs.length - 1;
1022 /* for walking legs in the beginning or end, use the name from the
1023 * query, so we get the names of the place the user searched for in
1024 * the results, when starting/ending at a transitstop, use the stop
1025 * name
1026 */
1027 let from =
1028 first && !leg.transitLeg ? this._query.filledPoints[0].place.name :
1029 leg.from.name;
1030 let to =
1031 last && !leg.transitLeg ? this._query.filledPoints.last().place.name :
1032 leg.to.name;
1033
1034 let result = new TransitPlan.Leg({ departure: leg.from.departure,
1035 arrival: leg.to.arrival,
1036 from: from,
1037 to: to,
1038 headsign: leg.headsign,
1039 fromCoordinate: [leg.from.lat,
1040 leg.from.lon],
1041 toCoordinate: [leg.to.lat,
1042 leg.to.lon],
1043 route: leg.route,
1044 routeType: leg.routeType,
1045 polyline: polyline,
1046 isTransit: leg.transitLeg,
1047 distance: leg.distance,
1048 duration: leg.duration,
1049 agencyName: leg.agencyName,
1050 agencyUrl: leg.agencyUrl,
1051 agencyTimezoneOffset: leg.agencyTimeZoneOffset,
1052 color: color,
1053 textColor: textColor,
1054 tripShortName: leg.tripShortName });
1055
1056 if (leg.transitLeg)
1057 result.intermediateStops = this._createIntermediateStops(leg);
1058 else if (!this._onlyTransitData)
1059 result.walkingInstructions = this._createTurnpoints(leg, polyline);
1060
1061 return result;
1062 }
1063
1064 _createIntermediateStops(leg) {
1065 let stops = leg.intermediateStops;
1066 let intermediateStops =
1067 stops.map((stop) => this._createIntermediateStop(stop, leg));
1068
1069 /* instroduce an extra stop at the end (in additional to the
1070 * intermediate stops we get from OTP
1071 */
1072 intermediateStops.push(new TransitPlan.Stop({ name: leg.to.name,
1073 arrival: leg.to.arrival,
1074 agencyTimezoneOffset: leg.agencyTimeZoneOffset,
1075 coordinate: [leg.to.lat,
1076 leg.to.lon] }));
1077 return intermediateStops;
1078 }
1079
1080 _createIntermediateStop(stop, leg) {
1081 return new TransitPlan.Stop({ name: stop.name,
1082 arrival: stop.arrival,
1083 departure: stop.departure,
1084 agencyTimezoneOffset: leg.agencyTimeZoneOffset,
1085 coordinate: [stop.lat, stop.lon] });
1086 }
1087
1088 /**
1089 * Create a turnpoints list on the same format we use with GraphHopper
1090 * from OpenTripPlanner walking steps
1091 */
1092 _createTurnpoints(leg, polyline) {
1093 if (leg.steps) {
1094 let steps = leg.steps;
1095 let startPoint = new Route.TurnPoint({
1096 coordinate: polyline[0],
1097 type: Route.TurnPointType.START,
1098 distance: 0,
1099 instruction: _("Start!"),
1100 time: 0,
1101 turnAngle: 0
1102 });
1103 let turnpoints = [startPoint];
1104 steps.forEach((step) => {
1105 turnpoints.push(this._createTurnpoint(step));
1106 });
1107
1108 let endPoint = new Route.TurnPoint({
1109 coordinate: polyline.last(),
1110 type: Route.TurnPoint.END,
1111 distance: 0,
1112 instruction:_("Arrive")
1113 });
1114
1115 turnpoints.push(endPoint);
1116
1117 return turnpoints;
1118 } else {
1119 return null;
1120 }
1121 }
1122
1123 _createTurnpoint(step) {
1124 let coordinate = new Champlain.Coordinate({ latitude: step.lat,
1125 longitude: step.lon });
1126 let turnpoint = new Route.TurnPoint({
1127 coordinate: coordinate,
1128 type: this._getTurnpointType(step),
1129 distance: step.distance,
1130 instruction: this._getTurnpointInstruction(step)
1131 });
1132
1133 return turnpoint;
1134 }
1135
1136 _getTurnpointType(step) {
1137 switch (step.relativeDirection) {
1138 case 'DEPART':
1139 case 'CONTINUE':
1140 return Route.TurnPointType.CONTINUE;
1141 case 'LEFT':
1142 return Route.TurnPointType.LEFT;
1143 case 'SLIGHTLY_LEFT':
1144 return Route.TurnPointType.SLIGHT_LEFT;
1145 case 'HARD_LEFT':
1146 return Route.TurnPointType.SHARP_LEFT;
1147 case 'RIGHT':
1148 return Route.TurnPointType.RIGHT;
1149 case 'SLIGHTLY_RIGHT':
1150 return Route.TurnPointType.SLIGHT_RIGHT;
1151 case 'HARD_RIGHT':
1152 return Route.TurnPointType.SHARP_RIGHT;
1153 case 'CIRCLE_CLOCKWISE':
1154 case 'CIRCLE_COUNTERCLOCKWISE':
1155 return Route.TurnPointType.ROUNDABOUT;
1156 case 'ELEVATOR':
1157 return Route.TurnPointType.ELEVATOR;
1158 case 'UTURN_LEFT':
1159 return Route.TurnPointType.UTURN_LEFT;
1160 case 'UTURN_RIGHT':
1161 return Route.TurnPointType.UTURN_RIGHT;
1162 default:
1163 return null;
1164 }
1165 }
1166
1167 _getTurnpointInstruction(step) {
1168 let street = !step.bogusName ? step.streetName : null;
1169 switch (step.relativeDirection) {
1170 case 'DEPART':
1171 case 'CONTINUE':
1172 if (street)
1173 return _("Continue on %s").format(street);
1174 else
1175 return _("Continue");
1176 case 'LEFT':
1177 if (street)
1178 return _("Turn left on %s").format(street);
1179 else
1180 return _("Turn left");
1181 case 'SLIGHTLY_LEFT':
1182 if (street)
1183 return _("Turn slightly left on %s").format(street);
1184 else
1185 return _("Turn slightly left");
1186 case 'HARD_LEFT':
1187 if (street)
1188 return _("Turn sharp left on %s").format(street);
1189 else
1190 return _("Turn sharp left");
1191 case 'RIGHT':
1192 if (street)
1193 return _("Turn right on %s").format(street);
1194 else
1195 return _("Turn right");
1196 case 'SLIGHTLY_RIGHT':
1197 if (street)
1198 return _("Turn slightly right on %s").format(street);
1199 else
1200 return _("Turn slightly right");
1201 case 'HARD_RIGHT':
1202 if (street)
1203 return _("Turn sharp right on %s").format(street);
1204 else
1205 return _("Turn sharp right");
1206 case 'CIRCLE_CLOCKWISE':
1207 case 'CIRCLE_COUNTERCLOCKWISE': {
1208 let exit = step.exit;
1209
1210 if (exit)
1211 return _("In the roundaboat, take exit %s").format(exit);
1212 else if (street)
1213 return _("In the roundabout, take exit to %s").format(street);
1214 else
1215 return _("Take the roundabout");
1216 }
1217 case 'ELEVATOR': {
1218 if (street)
1219 return _("Take the elevator and get off at %s").format(street);
1220 else
1221 return _("Take the elevator");
1222 }
1223 case 'UTURN_LEFT':
1224 if (street)
1225 return _("Make a left u-turn onto %s").format(street);
1226 else
1227 return _("Make a left u-turn");
1228 case 'UTURN_RIGHT':
1229 if (street)
1230 return _("Make a right u-turn onto %s").format(street);
1231 else
1232 return _("Make a rigth u-turn");
1233 default:
1234 return '';
1235 }
1236 }
1237 };
0 /* -*- Mode: JS2; indent-tabs-mode: nil; js2-basic-offset: 4 -*- */
1 /* vim: set et ts=4 sw=4: */
2 /*
3 * Copyright (c) 2019 Marcus Lundblad
4 *
5 * GNOME Maps is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by the
7 * Free Software Foundation; either version 2 of the License, or (at your
8 * option) any later version.
9 *
10 * GNOME Maps is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13 * for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with GNOME Maps; if not, see <http://www.gnu.org/licenses/>.
17 *
18 * Author: Marcus Lundblad <ml@update.uu.se>
19 */
20
21 /**
22 * This module implements a transit routing plugin for the Swedish national
23 * Resrobot transit journey planning API.
24 *
25 * API docs for Resrobot can be found at:
26 * https://www.trafiklab.se/api/resrobot-reseplanerare/dokumentation/sokresa
27 */
28
29 const Champlain = imports.gi.Champlain;
30 const GLib = imports.gi.GLib;
31 const Soup = imports.gi.Soup;
32
33 const Application = imports.application;
34 const GraphHopperTransit = imports.graphHopperTransit;
35 const HTTP = imports.http;
36 const HVT = imports.hvt;
37 const TransitPlan = imports.transitPlan;
38 const TransitTweaks = imports.transitTweaks;
39 const Utils = imports.utils;
40
41 const BASE_URL = 'https://api.resrobot.se';
42 const API_VERSION = 'v2';
43
44 // Timezone for timestamps returned by this provider
45 const NATIVE_TIMEZONE = 'Europe/Stockholm';
46
47 const ISO_8601_DURATION_REGEXP = new RegExp(/P((\d+)D)?T((\d+)H)?((\d+)M)?/);
48
49 const Products = {
50 EXPRESS_TRAIN: 2,
51 REGIONAL_TRAIN: 4,
52 EXPRESS_BUS: 8,
53 LOCAL_TRAIN: 16,
54 SUBWAY: 32,
55 TRAM: 64,
56 BUS: 128,
57 FERRY: 256,
58 TAXI: 512
59 };
60
61 const LegType = {
62 WALK: 'WALK',
63 TRANSIT: 'JNY',
64 TRANSFER: 'TRSF'
65 };
66
67 const CatCode = {
68 EXPRESS_TRAIN: 1,
69 REGIONAL_TRAIN: 2,
70 EXPRESS_BUS: 3,
71 LOCAL_TRAIN: 4,
72 SUBWAY: 5,
73 TRAM: 6,
74 BUS: 7,
75 FERRY: 8,
76 TAXI: 9
77 };
78
79 const MAX_NUM_NEARBY_STOPS = 5;
80 const NEARBY_STOPS_SEARCH_RADIUS = 500;
81
82 // ignore walking legs at the beginning/end when below this distance
83 const DISTANCE_THREASHOLD_TO_IGNORE = 50;
84
85 // search radius to search for walk-only journeys
86 const WALK_SEARCH_RADIUS = 2000;
87
88 // maximum distance for walk-only journey
89 const MAX_WALK_ONLY_DISTANCE = 2500;
90
91 var Resrobot = class Resrobot {
92 constructor(params) {
93 this._session = new Soup.Session({ user_agent : 'gnome-maps/' + pkg.version });
94 this._plan = Application.routingDelegator.transitRouter.plan;
95 this._query = Application.routeQuery;
96 this._key = params.key;
97 this._tz = GLib.TimeZone.new(NATIVE_TIMEZONE);
98 this._tweaks = new TransitTweaks.TransitTweaks({ name: 'resrobot' });
99
100 if (!this._key)
101 throw new Error('missing key');
102 }
103
104 fetchFirstResults() {
105 let filledPoints = this._query.filledPoints;
106
107 this._extendPrevious = false;
108 this._viaId = null;
109
110 if (filledPoints.length > 3) {
111 Utils.debug('This plugin supports at most one via location');
112 this._plan.reset();
113 this._plan.requestFailed();
114 this._query.reset();
115 } else if (filledPoints.length === 2) {
116 this._fetchResults();
117 } else {
118 let lat = filledPoints[1].place.location.latitude;
119 let lon = filledPoints[1].place.location.longitude;
120
121 this._fetchNearbyStops(lat, lon, MAX_NUM_NEARBY_STOPS,
122 NEARBY_STOPS_SEARCH_RADIUS,
123 () => this._fetchResults());
124 }
125 }
126
127 fetchMoreResults() {
128 this._extendPrevious = true;
129
130 if ((!this._scrF && !this._query.arriveBy) ||
131 (!this._scrB && this._query.arriveBy))
132 this._noRouteFound();
133 else
134 this._fetchResults();
135 }
136
137 _fetchNearbyStops(lat, lon, num, radius, callback) {
138 let query = new HTTP.Query(this._getNearbyStopsQueryParams(lat, lon,
139 num, radius));
140 let uri = new Soup.URI(BASE_URL + '/' + API_VERSION +
141 '/location.nearbystops?' + query.toString());
142 let request = new Soup.Message({ method: 'GET', uri: uri });
143
144 this._session.queue_message(request, (obj, message) => {
145 if (message.status_code !== Soup.Status.OK) {
146 Utils.debug('Failed to get nearby stops: ' + message.status_code);
147 this._noRouteFound();
148 } else {
149 try {
150 let result = JSON.parse(message.response_body.data);
151 let stopLocations = result.StopLocation;
152
153 Utils.debug('nearby stops: ' + JSON.stringify(result, null, 2));
154
155 if (stopLocations && stopLocations.length > 0) {
156 let stopLocation = stopLocations[0];
157
158 this._viaId = stopLocation.id;
159 callback();
160 } else {
161 Utils.debug('No nearby stops found');
162 this._noRouteFound();
163 }
164 } catch (e) {
165 Utils.debug('Error parsing result: ' + e);
166 this._plan.reset();
167 this._plan.requestFailed();
168 }
169 }
170 });
171 }
172
173 _fetchResults() {
174 let query = new HTTP.Query(this._getQueryParams());
175 let uri = new Soup.URI(BASE_URL + '/' + API_VERSION + '/trip?' +
176 query.toString());
177 let request = new Soup.Message({ method: 'GET', uri: uri });
178
179 this._session.queue_message(request, (obj, message) => {
180 if (message.status_code !== Soup.Status.OK) {
181 Utils.debug('Failed to get trip: ' + message.status_code);
182 /* No routes found. If this is the first search
183 * (not "load more") and the distance is short
184 * enough, generate a walk-only itinerary
185 */
186 let [start, end, distance] =
187 this._getAsTheCrowFliesPointsAndDistanceForQuery();
188
189 if (!this._extendPrevious &&
190 distance <= WALK_SEARCH_RADIUS) {
191 GraphHopperTransit.fetchWalkingRoute([start, end], (route) => {
192 if (route && route.distance <= MAX_WALK_ONLY_DISTANCE) {
193 let walkingItinerary =
194 this._createWalkingOnlyItinerary(start,
195 end,
196 route);
197 this._plan.updateWithNewItineraries([walkingItinerary]);
198 } else {
199 this._noRouteFound();
200 }
201 });
202 } else {
203 this._noRouteFound();
204 }
205 } else {
206 try {
207 let result = JSON.parse(message.response_body.data);
208
209 Utils.debug('result: ' + JSON.stringify(result, null, 2));
210 if (result.Trip) {
211 let itineraries = this._createItineraries(result.Trip);
212
213 // store the back and forward references from the result
214 this._scrB = result.scrB;
215 this._scrF = result.scrF;
216 this._tweaks.applyTweaks(itineraries, () => {
217 this._processItineraries(itineraries)
218 });
219 } else {
220 this._noRouteFound();
221 }
222 } catch (e) {
223 Utils.debug('Error parsing result: ' + e);
224 this._plan.reset();
225 this._plan.requestFailed();
226 }
227 }
228 });
229 }
230
231 /* get total "as the crow flies" start, and end points, and distance for
232 * the query
233 */
234 _getAsTheCrowFliesPointsAndDistanceForQuery() {
235 let start = this._query.filledPoints[0];
236 let end = this._query.filledPoints.last();
237 let startLoc = start.place.location;
238 let endLoc = end.place.location;
239
240 return [start, end, endLoc.get_distance_from(startLoc) * 1000];
241 }
242
243 _processItineraries(itineraries) {
244 /* if this is the first request, and the distance is short enough,
245 * add an additional walking-only itinerary at the beginning
246 */
247 let [start, end, distance] =
248 this._getAsTheCrowFliesPointsAndDistanceForQuery();
249
250 if (!this._extendPrevious && distance <= WALK_SEARCH_RADIUS) {
251 GraphHopperTransit.fetchWalkingRoute([start, end], (route) => {
252 if (route && route.distance <= MAX_WALK_ONLY_DISTANCE) {
253 let walkingItinerary =
254 this._createWalkingOnlyItinerary(start, end, route);
255
256 itineraries.unshift(walkingItinerary);
257 }
258 GraphHopperTransit.addWalkingToItineraries(itineraries,
259 () => this._plan.updateWithNewItineraries(itineraries,
260 this._query.arriveBy,
261 this._extendPrevious));
262 });
263 } else {
264 GraphHopperTransit.addWalkingToItineraries(itineraries,
265 () => this._plan.updateWithNewItineraries(itineraries,
266 this._query.arriveBy,
267 this._extendPrevious));
268 }
269 }
270
271 _createWalkingOnlyItinerary(start, end, route) {
272 let walkingLeg = GraphHopperTransit.createWalkingLeg(start, end,
273 start.place.name,
274 end.place.name,
275 route);
276 let duration = route.duration;
277 /* if the query has no date, just use a fake, since only the time
278 * is relevant for displaying in this case
279 */
280 let date = this._query.date || '2019-01-01';
281 let time = this._query.time + ':00';
282
283 let [timestamp, tzOffset] =
284 this._query.time ? this._parseTime(time, date) :
285 this._getTimestampAndTzOffsetNow();
286
287 if (this._query.arriveBy) {
288 walkingLeg.arrival = timestamp;
289 walkingLeg.departure = timestamp - route.time;
290 } else {
291 walkingLeg.departure = timestamp;
292 walkingLeg.arrival = timestamp + route.time;
293 }
294
295 walkingLeg.agencyTimezoneOffset = tzOffset;
296
297 let walkingItinerary =
298 new TransitPlan.Itinerary({ legs: [walkingLeg]} );
299
300 walkingItinerary.adjustTimings();
301
302 return walkingItinerary;
303 }
304
305 _reset() {
306 if (this._query.latest)
307 this._query.latest.place = null;
308 else
309 this._plan.reset();
310 }
311
312 /* Indicate that no routes where found, either shows the "No route found"
313 * message, or in case of loading additional (later/earlier) results,
314 * indicate no such where found, so that the sidebar can disable the
315 * "load more" functionallity as appropriate.
316 */
317 _noRouteFound() {
318 if (this._extendPrevious) {
319 this._plan.noMoreResults();
320 } else {
321 this._reset();
322 this._plan.noRouteFound();
323 }
324 }
325
326 _createItineraries(trips) {
327 return trips.map((trip) => this._createItinerary(trip));
328 }
329
330 _createItinerary(trip) {
331 let legs = this._createLegs(trip.LegList.Leg);
332 let duration = this._parseDuration(trip.duration);
333 let origin = trip.LegList.Leg[0].Origin;
334 let destination = trip.LegList.Leg.last().Destination;
335 let [startTime,] = this._parseTime(origin.time, origin.date);
336 let [endTime,] = this._parseTime(destination.time, destination.date);
337
338 return new TransitPlan.Itinerary({ duration: duration,
339 departure: startTime,
340 arrival: endTime,
341 legs: legs,
342 duration: duration });
343 }
344
345 /**
346 * Parse a time and date string into a timestamp into an array with
347 * an absolute timestamp in ms since Unix epoch and a timezone offset
348 * for the provider's native timezone at the given time and date
349 */
350 _parseTime(time, date) {
351 let timeText = '%sT%s'.format(date, time);
352 let dateTime = GLib.DateTime.new_from_iso8601(timeText, this._tz);
353
354 return [dateTime.to_unix() * 1000, dateTime.get_utc_offset() / 1000];
355 }
356
357 /**
358 * Get absolute timestamp for "now" in ms and timezone offset in the
359 * native timezone of the provider's native timezone @ "now"
360 */
361 _getTimestampAndTzOffsetNow() {
362 let dateTime = GLib.DateTime.new_now(this._tz);
363
364 return [dateTime.to_unix() * 1000, dateTime.get_utc_offset() / 1000];
365 }
366
367 /**
368 * Parse a subset of ISO 8601 duration expressions.
369 * Handle hour and minute parts
370 */
371 _parseDuration(duration) {
372 let match = duration.match(ISO_8601_DURATION_REGEXP);
373
374 if (match) {
375 let [,,d,,h,,min] = match;
376
377 return (d || 0) * 86400 + (h || 0) * 3600 + (min || 0) * 60;
378 } else {
379 Utils.debug('Unknown duration: ' + duration);
380
381 return -1;
382 }
383 }
384
385 _createLegs(legs) {
386 let result = legs.map((leg, index, legs) => this._createLeg(leg, index, legs));
387
388 if (this._canLegBeIgnored(result[0]))
389 result.shift();
390
391 if (this._canLegBeIgnored(result.last()))
392 result.splice(-1);
393
394 return result;
395 }
396
397 /* determines if a leg can ignored at the start or end, to catch the
398 * case when the user probably meant to search for a trip from a transit
399 * stop anyway
400 */
401 _canLegBeIgnored(leg) {
402 if (!leg.isTransit) {
403 /* check that the distance is below the threashold and also that
404 * the duration is below 1 min, since the API in some occasions
405 * apparently gives distance 0, even though a walking leg has
406 * longer duration, and spans a distance in coordinates.
407 */
408 return leg.distance <= DISTANCE_THREASHOLD_TO_IGNORE &&
409 leg.duration <= 60;
410 } else {
411 return false;
412 }
413 }
414
415 _createLeg(leg, index, legs) {
416 let isTransit;
417
418 if (leg.type === LegType.TRANSIT)
419 isTransit = true;
420 else if (leg.type === LegType.WALK || leg.type === LegType.TRANSFER)
421 isTransit = false;
422 else
423 throw new Error('Unknown leg type: ' + leg.type);
424
425 let origin = leg.Origin;
426 let destination = leg.Destination;
427 let product = leg.Product;
428
429 if (!origin)
430 throw new Error('Missing Origin element');
431 if (!destination)
432 throw new Error('Missing Destination element');
433 if (!product && isTransit)
434 throw new Error('Missing Product element for transit leg');
435
436 let first = index === 0;
437 let last = index === legs.length - 1;
438 /* for walking legs in the beginning or end, use the name from the
439 * query, so we get the names of the place the user searched for in
440 * the results, when starting/ending at a transitstop, use the stop
441 * name
442 */
443 let from =
444 first && !isTransit ? this._query.filledPoints[0].place.name :
445 origin.name;
446 let to =
447 last && !isTransit ? this._query.filledPoints.last().place.name :
448 destination.name;
449 let [departure, tzOffset] = this._parseTime(origin.time, origin.date);
450 let [arrival,] = this._parseTime(destination.time, destination.date);
451 let route = isTransit ? product.num : null;
452 let routeType =
453 isTransit ? this._getHVTCodeFromCatCode(product.catCode) : null;
454 let agencyName = isTransit ? product.operator : null;
455 let agencyUrl = isTransit ? product.operatorUrl : null;
456 let polyline = this._createPolylineForLeg(leg);
457 let duration = leg.duration ? this._parseDuration(leg.duration) : null;
458
459 let result = new TransitPlan.Leg({ departure: departure,
460 arrival: arrival,
461 from: from,
462 to: to,
463 headsign: leg.direction,
464 fromCoordinate: [origin.lat,
465 origin.lon],
466 toCoordinate: [destination.lat,
467 destination.lon],
468 route: route,
469 routeType: routeType,
470 polyline: polyline,
471 isTransit: isTransit,
472 distance: leg.dist,
473 duration: duration,
474 agencyName: agencyName,
475 agencyUrl: agencyUrl,
476 agencyTimezoneOffset: tzOffset,
477 tripShortName: route });
478
479 if (isTransit)
480 result.intermediateStops = this._createIntermediateStops(leg);
481
482 return result;
483 }
484
485 _createPolylineForLeg(leg) {
486 let polyline;
487
488 if (leg.Stops && leg.Stops.Stop) {
489 polyline = [];
490
491 leg.Stops.Stop.forEach((stop) => {
492 polyline.push(new Champlain.Coordinate({ latitude: stop.lat,
493 longitude: stop.lon }));
494 });
495 } else {
496 polyline =
497 [new Champlain.Coordinate({ latitude: leg.Origin.lat,
498 longitude: leg.Origin.lon }),
499 new Champlain.Coordinate({ latitude: leg.Destination.lat,
500 longitude: leg.Destination.lon })];
501 }
502
503 return polyline;
504 }
505
506 _createIntermediateStops(leg) {
507 let result = [];
508
509 if (!leg.Stops && !leg.Stops.Stop)
510 throw new Error('Missing Stops element');
511
512 leg.Stops.Stop.forEach((stop, index) => {
513 if (index !== 0)
514 result.push(this._createIntermediateStop(stop));
515 });
516
517 return result;
518 }
519
520 _createIntermediateStop(stop) {
521 let [departure, departureTzOffset] = [,];
522 let [arrival, arrivalTzOffset] = [,];
523
524 if (stop.depTime && stop.depDate)
525 [departure, departureTzOffset] = this._parseTime(stop.depTime, stop.depDate);
526 if (stop.arrTime && stop.arrDate)
527 [arrival, arrivalTzOffset] = this._parseTime(stop.arrTime, stop.arrDate);
528
529 if (!arrival)
530 arrival = departure;
531 if (!departure)
532 departure = arrival;
533
534 return new TransitPlan.Stop({ name: stop.name,
535 arrival: arrival,
536 departure: departure,
537 agencyTimezoneOffset: departureTzOffset || arrivalTzOffset,
538 coordinate: [stop.lat, stop.lon] });
539 }
540
541 _getHVTCodeFromCatCode(code) {
542 switch (parseInt(code)) {
543 case CatCode.EXPRESS_TRAIN:
544 return HVT.HIGH_SPEED_RAIL_SERVICE;
545 case CatCode.REGIONAL_TRAIN:
546 return HVT.REGIONAL_RAIL_SERVICE;
547 case CatCode.EXPRESS_BUS:
548 return HVT.EXPRESS_BUS_SERVICE;
549 case CatCode.LOCAL_TRAIN:
550 return HVT.SUBURBAN_RAILWAY_SERVICE;
551 case CatCode.SUBWAY:
552 return HVT.METRO_SERVICE;
553 case CatCode.TRAM:
554 return HVT.TRAM_SERVICE;
555 case CatCode.BUS:
556 return HVT.BUS_SERVICE;
557 case CatCode.FERRY:
558 return HVT.WATER_TRANSPORT_SERVICE;
559 case CatCode.TAXI:
560 return HVT.COMMUNAL_TAXI_SERVICE;
561 default:
562 Utils.debug('Unknown catCode: ' + code);
563 return HVT.MISCELLANEOUS_SERVICE;
564 }
565 }
566
567 _getQueryParams() {
568 let points = this._query.filledPoints;
569 let originLocation = points[0].place.location;
570 let destLocation = points.last().place.location;
571 let transitOptions = this._query.transitOptions;
572 let params = { key: this._key,
573 originCoordLat: originLocation.latitude,
574 originCoordLong: originLocation.longitude,
575 destCoordLat: destLocation.latitude,
576 destCoordLong: destLocation.longitude,
577 format: 'json' };
578
579 if (!transitOptions.showAllTransitTypes)
580 params.products = this._getAllowedProductsForQuery();
581
582 if (this._viaId)
583 params.viaId = this._viaId;
584
585 if (this._extendPrevious) {
586 params.context = this._query.arriveBy ? this._scrB : this._scrF;
587 } else {
588 if (this._query.arriveBy)
589 params.searchForArrival = 1;
590
591 if (this._query.time)
592 params.time = this._query.time;
593
594 if (this._query.date)
595 params.date = this._query.date;
596 }
597
598 return params;
599 }
600
601 _getNearbyStopsQueryParams(lat, lon, num, radius) {
602 let params = { key: this._key,
603 originCoordLat: lat,
604 originCoordLong: lon,
605 maxNo: num,
606 r: radius,
607 format: 'json' };
608
609 return params;
610 }
611
612 _getAllowedProductsForQuery() {
613 let products = 0;
614
615 this._query.transitOptions.transitTypes.forEach((type) => {
616 products += this._productCodeForTransitType(type);
617 });
618
619 return products;
620 }
621
622 _productCodeForTransitType(type) {
623 switch (type) {
624 case TransitPlan.RouteType.BUS:
625 return Products.BUS + Products.EXPRESS_BUS + Products.TAXI;
626 case TransitPlan.RouteType.TRAM:
627 return Products.TRAM;
628 case TransitPlan.RouteType.TRAIN:
629 return Products.EXPRESS_TRAIN + Products.LOCAL_TRAIN;
630 case TransitPlan.RouteType.SUBWAY:
631 return Products.SUBWAY;
632 case TransitPlan.RouteType.FERRY:
633 return Products.FERRY;
634 default:
635 return 0;
636 }
637 }
638 }
212212 return measurementSystem;
213213 }
214214
215 /**
216 * Get the higest priority bare lange currently in use.
217 */
218 function getLanguage() {
219 let locale = GLib.get_language_names()[0];
220 // the last item returned is the "bare" language
221 return GLib.get_locale_variants(locale).slice(-1)[0];
222 }
223
215224 function getAccuracyDescription(accuracy) {
216225 switch(accuracy) {
217226 case Geocode.LOCATION_ACCURACY_UNKNOWN:
2929 let _soupSession = null;
3030 function _getSoupSession() {
3131 if (_soupSession === null) {
32 _soupSession = new Soup.Session ();
32 _soupSession = new Soup.Session({ user_agent : 'gnome-maps/' + pkg.version });
3333 }
3434
3535 return _soupSession;
4242 }
4343
4444 function getArticle(wiki) {
45 return Soup.uri_encode(wiki.replace(/ /g, '_').split(':').splice(1).join(':'),
46 '\'');
47 }
48
49 function getHtmlEntityEncodedArticle(wiki) {
4550 return GLib.markup_escape_text(wiki.split(':').splice(1).join(':'), -1);
4651 }
4752
5257 */
5358 function fetchArticleThumbnail(wiki, size, callback) {
5459 let lang = getLanguage(wiki);
55 let title = getArticle(wiki);
60 let title = getHtmlEntityEncodedArticle(wiki);
5661 let uri = Format.vprintf('https://%s.wikipedia.org/w/api.php', [ lang ]);
5762 let msg = Soup.form_request_new_from_hash('GET', uri, { action: 'query',
5863 titles: title,