Import upstream version 3.35.1+git20191114.4eb1d51, md5 e732769835c928ec0d8606294e9d2294
Debian Janitor
4 years ago
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 | |
1 | 1 | ========================= |
2 | 2 | |
3 | 3 | 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 | |
5 | 9 | |
6 | 10 | Added/updated/fixed translations |
11 | - Spanish | |
7 | 12 | - Danish |
8 | 13 | - Slovak |
9 | - Persian | |
10 | 14 | - Dutch |
11 | 15 | - Friulian |
12 | 16 | - Italian |
13 | 17 | |
14 | 18 | All contributors to this release |
15 | 19 | Ask Hjorth Larsen <asklarsen@gmail.com> |
16 | Danial Behzadi <dani.behzi@ubuntu.com> | |
20 | Daniel Mustieles <daniel.mustieles@gmail.com> | |
17 | 21 | Dušan Kazik <prescott66@gmail.com> |
18 | 22 | Fabio Tomat <f.t.public@gmail.com> |
19 | 23 | Gianvito Cavasoli <gianvito@gmx.it> |
24 | Jakub Steiner <jimmac@gmail.com> | |
20 | 25 | Marcus Lundblad <ml@update.uu.se> |
21 | 26 | Nathan Follens <nfollens@gnome.org> |
22 | 27 |
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>⏎ |
41 | 41 | </screenshot> |
42 | 42 | </screenshots> |
43 | 43 | <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> | |
51 | 44 | <release date="2019-09-09" version="3.34.0"> |
52 | 45 | <description> |
53 | 46 | <ul> |
270 | 270 | <child> |
271 | 271 | <object class="GtkStack" id="linkButtonStack"> |
272 | 272 | <child> |
273 | <object class="GtkLinkButton" id="graphHopperLinkButton"> | |
273 | <object class="GtkLinkButton"> | |
274 | 274 | <property name="label" translatable="yes">Route search by GraphHopper</property> |
275 | 275 | <property name="visible">True</property> |
276 | 276 | <property name="can_focus">True</property> |
283 | 283 | </style> |
284 | 284 | </object> |
285 | 285 | <packing> |
286 | <property name="name">graphHopper</property> | |
286 | <property name="name">turnByTurn</property> | |
287 | 287 | </packing> |
288 | 288 | </child> |
289 | 289 | <child> |
291 | 291 | <property name="visible">True</property> |
292 | 292 | <property name="halign">GTK_ALIGN_END</property> |
293 | 293 | <child> |
294 | <object class="GtkLinkButton" id="openTripPlannerLinkButton"> | |
295 | <property name="label" translatable="yes">Route search by OpenTripPlanner</property> | |
294 | <object class="GtkLabel" id="transitAttributionLabel"> | |
296 | 295 | <property name="visible">True</property> |
297 | 296 | <property name="can_focus">True</property> |
298 | 297 | <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> | |
304 | 299 | <style> |
305 | 300 | <class name="small-label"/> |
306 | 301 | </style> |
313 | 308 | <child> |
314 | 309 | <object class="GtkMenuButton"> |
315 | 310 | <property name="visible">True</property> |
316 | <property name="popover">openTripPlannerDisclaimerPopover</property> | |
311 | <property name="popover">transitDisclaimerPopover</property> | |
317 | 312 | <property name="halign">GTK_ALIGN_END</property> |
318 | 313 | <property name="margin-top">5</property> |
319 | 314 | <property name="margin-bottom">5</property> |
345 | 340 | </child> |
346 | 341 | </object> |
347 | 342 | <packing> |
348 | <property name="name">openTripPlanner</property> | |
343 | <property name="name">transit</property> | |
349 | 344 | </packing> |
350 | 345 | </child> |
351 | 346 | </object> |
353 | 348 | </object> |
354 | 349 | </child> |
355 | 350 | </template> |
356 | <object class="GtkPopover" id="openTripPlannerDisclaimerPopover"> | |
351 | <object class="GtkPopover" id="transitDisclaimerPopover"> | |
357 | 352 | <property name="visible">False</property> |
358 | 353 | <child> |
359 | 354 | <object class="GtkGrid"> |
365 | 360 | <property name="margin-bottom">5</property> |
366 | 361 | <property name="margin-start">5</property> |
367 | 362 | <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. | |
371 | 365 | 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. | |
372 | 369 | Names and brands shown are to be considered as registered trademarks when applicable.</property> |
373 | 370 | </object> |
374 | 371 | </child> |
144 | 144 | <property name="label" translatable="yes">Ferries</property> |
145 | 145 | </object> |
146 | 146 | </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> | |
147 | 154 | </object> |
148 | 155 | </child> |
149 | 156 | </object> |
20 | 20 | |
21 | 21 | <maintainer> |
22 | 22 | <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> | |
37 | 23 | <foaf:name>Marcus Lundblad</foaf:name> |
38 | 24 | <foaf:mbox rdf:resource="mailto:ml@update.uu.se" /> |
39 | 25 | <gnome:userid>mlundblad</gnome:userid> |
38 | 38 | src/layersPopover.js |
39 | 39 | src/mainWindow.js |
40 | 40 | src/mapView.js |
41 | src/openTripPlanner.js | |
42 | 41 | src/osmConnection.js |
43 | 42 | src/osmEditDialog.js |
44 | 43 | src/photonParser.js |
58 | 57 | src/transitPlan.js |
59 | 58 | src/translations.js |
60 | 59 | src/utils.js |
60 | src/transitplugins/openTripPlanner.js |
6 | 6 | msgstr "" |
7 | 7 | "Project-Id-Version: gnome-maps\n" |
8 | 8 | "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" | |
11 | 11 | "Last-Translator: Marek Černocký <marek@manet.cz>\n" |
12 | 12 | "Language-Team: čeština <gnome-cs-list@gnome.org>\n" |
13 | 13 | "Language: cs\n" |
55 | 55 | "Můžete také hledat určité typy míst, jako „restaurace poblíž ulice Kobližná, " |
56 | 56 | "Brno“ nebo „hotely poblíž Alexanderplatz, Berlín“." |
57 | 57 | |
58 | #: data/org.gnome.Maps.appdata.xml.in:59 | |
58 | #: data/org.gnome.Maps.appdata.xml.in:92 | |
59 | 59 | msgid "The GNOME Project" |
60 | 60 | msgstr "Projekt GNOME" |
61 | 61 | |
640 | 640 | msgid "Route search by GraphHopper" |
641 | 641 | msgstr "Hledání trasy pomocí GraphHopper" |
642 | 642 | |
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" | |
653 | 647 | "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" | |
654 | 653 | "Names and brands shown are to be considered as registered trademarks when " |
655 | 654 | "applicable." |
656 | 655 | 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." | |
662 | 665 | |
663 | 666 | #: data/ui/social-place-more-results-row.ui:8 |
664 | 667 | msgid "Show more results" |
714 | 717 | msgid "Ferries" |
715 | 718 | msgstr "trajekty" |
716 | 719 | |
720 | #: data/ui/transit-options-panel.ui:152 | |
721 | msgid "Airplanes" | |
722 | msgstr "letadla" | |
723 | ||
717 | 724 | #: data/ui/user-location-bubble.ui:13 src/geoclue.js:118 |
718 | 725 | msgid "Current location" |
719 | 726 | msgstr "Současné místo" |
756 | 763 | msgid "Show the version of the program" |
757 | 764 | msgstr "Zobrazit verzi programu" |
758 | 765 | |
766 | #: src/application.js:104 | |
767 | msgid "Ignore network availability" | |
768 | msgstr "Ignorovat dostupnost sítě" | |
769 | ||
759 | 770 | #: src/checkInDialog.js:167 |
760 | 771 | msgid "Select an account" |
761 | 772 | msgstr "Výběr účtu" |
886 | 897 | msgid "unknown geometry" |
887 | 898 | msgstr "neznámé geometrické údaje" |
888 | 899 | |
889 | #: src/graphHopper.js:112 src/openTripPlanner.js:652 | |
900 | #: src/graphHopper.js:112 src/transitPlan.js:192 | |
890 | 901 | msgid "Route request failed." |
891 | 902 | msgstr "Požadavek na vyhledání cesty selhal." |
892 | 903 | |
893 | #: src/graphHopper.js:119 src/openTripPlanner.js:615 | |
904 | #: src/graphHopper.js:119 src/transitPlan.js:184 | |
894 | 905 | msgid "No route found." |
895 | 906 | msgstr "Nenalezena žádná cesta." |
896 | 907 | |
897 | #: src/graphHopper.js:207 | |
908 | #: src/graphHopper.js:207 src/transitplugins/openTripPlanner.js:1100 | |
898 | 909 | msgid "Start!" |
899 | 910 | msgstr "Start!" |
900 | 911 | |
947 | 958 | msgid "Search provided by %s using %s" |
948 | 959 | msgstr "Hledání zprostředkuje %s pomocí %s" |
949 | 960 | |
950 | #: src/mapView.js:374 | |
961 | #: src/mapView.js:375 | |
951 | 962 | msgid "File type is not supported" |
952 | 963 | msgstr "Typ souboru není podporován" |
953 | 964 | |
954 | #: src/mapView.js:381 | |
965 | #: src/mapView.js:382 | |
955 | 966 | msgid "Failed to open layer" |
956 | 967 | msgstr "Selhalo otevření vrstvy" |
957 | 968 | |
958 | #: src/mapView.js:417 | |
969 | #: src/mapView.js:418 | |
959 | 970 | msgid "Failed to open GeoURI" |
960 | 971 | 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." | |
965 | 972 | |
966 | 973 | #. setting the status in session.cancel_message still seems |
967 | 974 | #. to always give status IO_ERROR |
1376 | 1383 | msgstr "selhalo načtení souboru" |
1377 | 1384 | |
1378 | 1385 | #. Translators: %s is a time expression with the format "%f h" or "%f min" |
1379 | #: src/sidebar.js:293 | |
1386 | #: src/sidebar.js:296 | |
1380 | 1387 | #, javascript-format |
1381 | 1388 | msgid "Estimated time: %s" |
1382 | 1389 | 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" | |
1383 | 1395 | |
1384 | 1396 | #. Translators: this is a format string indicating instructions |
1385 | 1397 | #. * starting a journey at the address given as the parameter |
1413 | 1425 | msgid "Arrive at %s" |
1414 | 1426 | msgstr "Příjezd do %s" |
1415 | 1427 | |
1416 | #: src/transit.js:77 | |
1428 | #: src/transit.js:77 src/transitplugins/openTripPlanner.js:1113 | |
1417 | 1429 | msgid "Arrive" |
1418 | 1430 | msgstr "Příjezd" |
1419 | 1431 | |
1445 | 1457 | #. * Translators: this is a format string giving the equivalent to |
1446 | 1458 | #. * "may 29" according to the current locale's convensions. |
1447 | 1459 | #. |
1448 | #: src/transitOptionsPanel.js:141 | |
1460 | #: src/transitOptionsPanel.js:143 | |
1449 | 1461 | msgctxt "month-day-date" |
1450 | 1462 | msgid "%b %e" |
1451 | 1463 | 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ů." | |
1452 | 1472 | |
1453 | 1473 | #. Translators: this is a format string for showing a departure and |
1454 | 1474 | #. * arrival time, like: |
1455 | 1475 | #. * "12:00 – 13:03" where the placeholder %s are the actual times, |
1456 | 1476 | #. * these could be rearranged if needed. |
1457 | 1477 | #. |
1458 | #: src/transitPlan.js:254 | |
1478 | #: src/transitPlan.js:313 | |
1459 | 1479 | #, javascript-format |
1460 | 1480 | msgid "%s – %s" |
1461 | 1481 | msgstr "%s – %s" |
1464 | 1484 | #. * less than an hour, with only the minutes part, using plural forms |
1465 | 1485 | #. * as appropriate |
1466 | 1486 | #. |
1467 | #: src/transitPlan.js:281 | |
1487 | #: src/transitPlan.js:340 | |
1468 | 1488 | #, javascript-format |
1469 | 1489 | msgid "%d minute" |
1470 | 1490 | msgid_plural "%d minutes" |
1476 | 1496 | #. * where the duration is an exact number of hours (i.e. no |
1477 | 1497 | #. * minutes part), using plural forms as appropriate |
1478 | 1498 | #. |
1479 | #: src/transitPlan.js:292 | |
1499 | #: src/transitPlan.js:351 | |
1480 | 1500 | #, javascript-format |
1481 | 1501 | msgid "%d hour" |
1482 | 1502 | msgid_plural "%d hours" |
1488 | 1508 | #. * where the duration contains an hour and minute part, it's |
1489 | 1509 | #. * pluralized on the hours part |
1490 | 1510 | #. |
1491 | #: src/transitPlan.js:298 | |
1511 | #: src/transitPlan.js:357 | |
1492 | 1512 | #, javascript-format |
1493 | 1513 | msgid "%d:%02d hour" |
1494 | 1514 | msgid_plural "%d:%02d hours" |
1502 | 1522 | #. * "12:00–13:03" where the placeholder %s are the actual times, |
1503 | 1523 | #. * these could be rearranged if needed. |
1504 | 1524 | #. |
1505 | #: src/transitPlan.js:651 | |
1525 | #: src/transitPlan.js:750 | |
1506 | 1526 | #, javascript-format |
1507 | 1527 | msgid "%s–%s" |
1508 | 1528 | msgstr "%s – %s" |
1656 | 1676 | msgstr "služba" |
1657 | 1677 | |
1658 | 1678 | #. Translators: Accuracy of user location information |
1659 | #: src/utils.js:220 | |
1679 | #: src/utils.js:229 | |
1660 | 1680 | msgid "Unknown" |
1661 | 1681 | msgstr "neznámá" |
1662 | 1682 | |
1663 | 1683 | #. Translators: Accuracy of user location information |
1664 | #: src/utils.js:223 | |
1684 | #: src/utils.js:232 | |
1665 | 1685 | msgid "Exact" |
1666 | 1686 | msgstr "velmi přesná" |
1667 | 1687 | |
1668 | #: src/utils.js:281 | |
1688 | #: src/utils.js:290 | |
1669 | 1689 | #, javascript-format |
1670 | 1690 | msgid "%f h" |
1671 | 1691 | msgstr "%f h" |
1672 | 1692 | |
1673 | #: src/utils.js:283 | |
1693 | #: src/utils.js:292 | |
1674 | 1694 | #, javascript-format |
1675 | 1695 | msgid "%f min" |
1676 | 1696 | msgstr "%f min" |
1677 | 1697 | |
1678 | #: src/utils.js:285 | |
1698 | #: src/utils.js:294 | |
1679 | 1699 | #, javascript-format |
1680 | 1700 | msgid "%f s" |
1681 | 1701 | msgstr "%f s" |
1682 | 1702 | |
1683 | 1703 | #. Translators: This is a distance measured in kilometers |
1684 | #: src/utils.js:296 | |
1704 | #: src/utils.js:305 | |
1685 | 1705 | #, javascript-format |
1686 | 1706 | msgid "%s km" |
1687 | 1707 | msgstr "%s km" |
1688 | 1708 | |
1689 | 1709 | #. Translators: This is a distance measured in meters |
1690 | #: src/utils.js:299 | |
1710 | #: src/utils.js:308 | |
1691 | 1711 | #, javascript-format |
1692 | 1712 | msgid "%s m" |
1693 | 1713 | msgstr "%s m" |
1694 | 1714 | |
1695 | 1715 | #. Translators: This is a distance measured in miles |
1696 | #: src/utils.js:307 | |
1716 | #: src/utils.js:316 | |
1697 | 1717 | #, javascript-format |
1698 | 1718 | msgid "%s mi" |
1699 | 1719 | msgstr "%s mi" |
1700 | 1720 | |
1701 | 1721 | #. Translators: This is a distance measured in feet |
1702 | #: src/utils.js:310 | |
1722 | #: src/utils.js:319 | |
1703 | 1723 | #, javascript-format |
1704 | 1724 | msgid "%s ft" |
1705 | 1725 | 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" |
7 | 7 | msgstr "" |
8 | 8 | "Project-Id-Version: gnome-maps master\n" |
9 | 9 | "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" | |
12 | 12 | "Last-Translator: Daniel Mustieles <daniel.mustieles@gmail.com>\n" |
13 | 13 | "Language-Team: Spanish - Spain <gnome-es-list@gnome.org>\n" |
14 | 14 | "Language: es_ES\n" |
16 | 16 | "Content-Type: text/plain; charset=UTF-8\n" |
17 | 17 | "Content-Transfer-Encoding: 8bit\n" |
18 | 18 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" |
19 | "X-Generator: Gtranslator 3.32.1\n" | |
19 | "X-Generator: Gtranslator 3.34.0\n" | |
20 | 20 | |
21 | 21 | #: data/org.gnome.Maps.appdata.xml.in:6 |
22 | 22 | msgid "GNOME Maps" |
55 | 55 | "También puede buscar por tipos de ubicaciones específicos como «Bares cerca " |
56 | 56 | "de la Gran Vía, Madrid» u «Hoteles cerca de la Plaza Mayor, Madrid»." |
57 | 57 | |
58 | #: data/org.gnome.Maps.appdata.xml.in:59 | |
58 | #: data/org.gnome.Maps.appdata.xml.in:92 | |
59 | 59 | msgid "The GNOME Project" |
60 | 60 | msgstr "El Proyecto GNOME" |
61 | 61 | |
68 | 68 | #. |
69 | 69 | #. Translators: This is the program name. |
70 | 70 | #: 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 | |
72 | 72 | msgid "Maps" |
73 | 73 | msgstr "Mapas" |
74 | 74 | |
102 | 102 | msgstr "Nivel de ampliación" |
103 | 103 | |
104 | 104 | #: data/org.gnome.Maps.gschema.xml:21 |
105 | #| msgctxt "shortcut window" | |
106 | #| msgid "Map View" | |
107 | 105 | msgid "Map type" |
108 | 106 | msgstr "Tipo de mapa" |
109 | 107 | |
641 | 639 | msgid "Route search by GraphHopper" |
642 | 640 | msgstr "Ruta buscada por GraphHopper" |
643 | 641 | |
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" | |
654 | 646 | "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" | |
655 | 652 | "Names and brands shown are to be considered as registered trademarks when " |
656 | 653 | "applicable." |
657 | 654 | 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" | |
662 | 657 | "GNOME no puede garantizar la corrección de los itinerarios y los tiempos " |
663 | 658 | "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." | |
666 | 666 | |
667 | 667 | #: data/ui/social-place-more-results-row.ui:8 |
668 | 668 | msgid "Show more results" |
718 | 718 | msgid "Ferries" |
719 | 719 | msgstr "Ferris" |
720 | 720 | |
721 | #: data/ui/transit-options-panel.ui:152 | |
722 | msgid "Airplanes" | |
723 | msgstr "Aviones" | |
724 | ||
721 | 725 | #: data/ui/user-location-bubble.ui:13 src/geoclue.js:118 |
722 | 726 | msgid "Current location" |
723 | 727 | msgstr "Ubicación actual" |
760 | 764 | msgid "Show the version of the program" |
761 | 765 | msgstr "Mostrar la versión del programa" |
762 | 766 | |
767 | #: src/application.js:104 | |
768 | msgid "Ignore network availability" | |
769 | msgstr "Ignorar la disponibilidad de la red" | |
770 | ||
763 | 771 | #: src/checkInDialog.js:167 |
764 | 772 | msgid "Select an account" |
765 | 773 | msgstr "Seleccionar una cuenta" |
890 | 898 | msgid "unknown geometry" |
891 | 899 | msgstr "geometría desconocida" |
892 | 900 | |
893 | #: src/graphHopper.js:112 src/openTripPlanner.js:652 | |
901 | #: src/graphHopper.js:112 src/transitPlan.js:192 | |
894 | 902 | msgid "Route request failed." |
895 | 903 | msgstr "Falló al solicitar la ruta." |
896 | 904 | |
897 | #: src/graphHopper.js:119 src/openTripPlanner.js:615 | |
905 | #: src/graphHopper.js:119 src/transitPlan.js:184 | |
898 | 906 | msgid "No route found." |
899 | 907 | msgstr "No se ha encontrado la ruta." |
900 | 908 | |
901 | #: src/graphHopper.js:207 | |
909 | #: src/graphHopper.js:207 src/transitplugins/openTripPlanner.js:1100 | |
902 | 910 | msgid "Start!" |
903 | 911 | msgstr "Empezar" |
904 | 912 | |
906 | 914 | msgid "All Layer Files" |
907 | 915 | msgstr "Todos los archivos de capas" |
908 | 916 | |
909 | #: src/mainWindow.js:442 | |
917 | #: src/mainWindow.js:446 | |
910 | 918 | msgid "Failed to connect to location service" |
911 | 919 | msgstr "Falló al conectar al servicio de ubicación" |
912 | 920 | |
913 | #: src/mainWindow.js:507 | |
921 | #: src/mainWindow.js:511 | |
914 | 922 | msgid "translator-credits" |
915 | 923 | msgstr "Daniel Mustieles <daniel.mustieles@gmail.com>, 2013" |
916 | 924 | |
917 | #: src/mainWindow.js:510 | |
925 | #: src/mainWindow.js:514 | |
918 | 926 | msgid "A map application for GNOME" |
919 | 927 | msgstr "Una aplicación de mapas para GNOME" |
920 | 928 | |
921 | #: src/mainWindow.js:521 | |
929 | #: src/mainWindow.js:525 | |
922 | 930 | msgid "Copyright © 2011 – 2019 Red Hat, Inc. and The GNOME Maps authors" |
923 | 931 | msgstr "Copyright © 2011 – 2019 Red Hat, Inc. y los autores de Mapas de GNOME" |
924 | 932 | |
925 | #: src/mainWindow.js:541 | |
933 | #: src/mainWindow.js:545 | |
926 | 934 | #, javascript-format |
927 | 935 | msgid "Map data by %s and contributors" |
928 | 936 | msgstr "Datos de mapas por %s y colaboradores" |
932 | 940 | #. * the bare name of the tile provider, or a linkified URL if one |
933 | 941 | #. * is available |
934 | 942 | #. |
935 | #: src/mainWindow.js:557 | |
943 | #: src/mainWindow.js:561 | |
936 | 944 | #, javascript-format |
937 | 945 | msgid "Map tiles provided by %s" |
938 | 946 | msgstr "Cuadrículas de mapas proporcionadas por %s" |
946 | 954 | #. * (i.e. "%2$s ... %1$s ..." for positioning the project URL |
947 | 955 | #. * before the provider). |
948 | 956 | #. |
949 | #: src/mainWindow.js:586 | |
957 | #: src/mainWindow.js:590 | |
950 | 958 | #, javascript-format |
951 | 959 | msgid "Search provided by %s using %s" |
952 | 960 | msgstr "Búsqueda proporcionada por %s usando %s" |
953 | 961 | |
954 | #: src/mapView.js:374 | |
962 | #: src/mapView.js:375 | |
955 | 963 | msgid "File type is not supported" |
956 | 964 | msgstr "Tipo de archivo no soportado" |
957 | 965 | |
958 | #: src/mapView.js:381 | |
966 | #: src/mapView.js:382 | |
959 | 967 | msgid "Failed to open layer" |
960 | 968 | msgstr "Falló al abrir la capa" |
961 | 969 | |
962 | #: src/mapView.js:417 | |
970 | #: src/mapView.js:418 | |
963 | 971 | msgid "Failed to open GeoURI" |
964 | 972 | 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." | |
969 | 973 | |
970 | 974 | #. setting the status in session.cancel_message still seems |
971 | 975 | #. to always give status IO_ERROR |
1268 | 1272 | msgid "Phone:" |
1269 | 1273 | msgstr "Teléfono:" |
1270 | 1274 | |
1271 | #: src/placeEntry.js:205 | |
1275 | #: src/placeEntry.js:209 | |
1272 | 1276 | msgid "Failed to parse Geo URI" |
1273 | 1277 | msgstr "Falló al analizar el URI de Geo" |
1274 | 1278 | |
1381 | 1385 | msgstr "falló al cargar el archivo" |
1382 | 1386 | |
1383 | 1387 | #. Translators: %s is a time expression with the format "%f h" or "%f min" |
1384 | #: src/sidebar.js:293 | |
1388 | #: src/sidebar.js:296 | |
1385 | 1389 | #, javascript-format |
1386 | 1390 | msgid "Estimated time: %s" |
1387 | 1391 | 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" | |
1388 | 1397 | |
1389 | 1398 | #. Translators: this is a format string indicating instructions |
1390 | 1399 | #. * starting a journey at the address given as the parameter |
1418 | 1427 | msgid "Arrive at %s" |
1419 | 1428 | msgstr "Llegar a %s" |
1420 | 1429 | |
1421 | #: src/transit.js:77 | |
1430 | #: src/transit.js:77 src/transitplugins/openTripPlanner.js:1113 | |
1422 | 1431 | msgid "Arrive" |
1423 | 1432 | msgstr "Llegar" |
1424 | 1433 | |
1450 | 1459 | #. * Translators: this is a format string giving the equivalent to |
1451 | 1460 | #. * "may 29" according to the current locale's convensions. |
1452 | 1461 | #. |
1453 | #: src/transitOptionsPanel.js:141 | |
1462 | #: src/transitOptionsPanel.js:143 | |
1454 | 1463 | msgctxt "month-day-date" |
1455 | 1464 | msgid "%b %e" |
1456 | 1465 | 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." | |
1457 | 1474 | |
1458 | 1475 | #. Translators: this is a format string for showing a departure and |
1459 | 1476 | #. * arrival time, like: |
1460 | 1477 | #. * "12:00 – 13:03" where the placeholder %s are the actual times, |
1461 | 1478 | #. * these could be rearranged if needed. |
1462 | 1479 | #. |
1463 | #: src/transitPlan.js:254 | |
1480 | #: src/transitPlan.js:313 | |
1464 | 1481 | #, javascript-format |
1465 | 1482 | msgid "%s – %s" |
1466 | 1483 | msgstr "%s – %s" |
1469 | 1486 | #. * less than an hour, with only the minutes part, using plural forms |
1470 | 1487 | #. * as appropriate |
1471 | 1488 | #. |
1472 | #: src/transitPlan.js:281 | |
1489 | #: src/transitPlan.js:340 | |
1473 | 1490 | #, javascript-format |
1474 | 1491 | msgid "%d minute" |
1475 | 1492 | msgid_plural "%d minutes" |
1480 | 1497 | #. * where the duration is an exact number of hours (i.e. no |
1481 | 1498 | #. * minutes part), using plural forms as appropriate |
1482 | 1499 | #. |
1483 | #: src/transitPlan.js:292 | |
1500 | #: src/transitPlan.js:351 | |
1484 | 1501 | #, javascript-format |
1485 | 1502 | msgid "%d hour" |
1486 | 1503 | msgid_plural "%d hours" |
1491 | 1508 | #. * where the duration contains an hour and minute part, it's |
1492 | 1509 | #. * pluralized on the hours part |
1493 | 1510 | #. |
1494 | #: src/transitPlan.js:298 | |
1511 | #: src/transitPlan.js:357 | |
1495 | 1512 | #, javascript-format |
1496 | 1513 | msgid "%d:%02d hour" |
1497 | 1514 | msgid_plural "%d:%02d hours" |
1504 | 1521 | #. * "12:00–13:03" where the placeholder %s are the actual times, |
1505 | 1522 | #. * these could be rearranged if needed. |
1506 | 1523 | #. |
1507 | #: src/transitPlan.js:651 | |
1524 | #: src/transitPlan.js:750 | |
1508 | 1525 | #, javascript-format |
1509 | 1526 | msgid "%s–%s" |
1510 | 1527 | msgstr "%s–%s" |
1658 | 1675 | msgstr "servicio" |
1659 | 1676 | |
1660 | 1677 | #. Translators: Accuracy of user location information |
1661 | #: src/utils.js:220 | |
1678 | #: src/utils.js:229 | |
1662 | 1679 | msgid "Unknown" |
1663 | 1680 | msgstr "Desconocida" |
1664 | 1681 | |
1665 | 1682 | #. Translators: Accuracy of user location information |
1666 | #: src/utils.js:223 | |
1683 | #: src/utils.js:232 | |
1667 | 1684 | msgid "Exact" |
1668 | 1685 | msgstr "Exacta" |
1669 | 1686 | |
1670 | #: src/utils.js:281 | |
1687 | #: src/utils.js:290 | |
1671 | 1688 | #, javascript-format |
1672 | 1689 | msgid "%f h" |
1673 | 1690 | msgstr "%f h" |
1674 | 1691 | |
1675 | #: src/utils.js:283 | |
1692 | #: src/utils.js:292 | |
1676 | 1693 | #, javascript-format |
1677 | 1694 | msgid "%f min" |
1678 | 1695 | msgstr "%f min" |
1679 | 1696 | |
1680 | #: src/utils.js:285 | |
1697 | #: src/utils.js:294 | |
1681 | 1698 | #, javascript-format |
1682 | 1699 | msgid "%f s" |
1683 | 1700 | msgstr "%f s" |
1684 | 1701 | |
1685 | 1702 | #. Translators: This is a distance measured in kilometers |
1686 | #: src/utils.js:296 | |
1703 | #: src/utils.js:305 | |
1687 | 1704 | #, javascript-format |
1688 | 1705 | msgid "%s km" |
1689 | 1706 | msgstr "%s km" |
1690 | 1707 | |
1691 | 1708 | #. Translators: This is a distance measured in meters |
1692 | #: src/utils.js:299 | |
1709 | #: src/utils.js:308 | |
1693 | 1710 | #, javascript-format |
1694 | 1711 | msgid "%s m" |
1695 | 1712 | msgstr "%s m" |
1696 | 1713 | |
1697 | 1714 | #. Translators: This is a distance measured in miles |
1698 | #: src/utils.js:307 | |
1715 | #: src/utils.js:316 | |
1699 | 1716 | #, javascript-format |
1700 | 1717 | msgid "%s mi" |
1701 | 1718 | msgstr "%s mi" |
1702 | 1719 | |
1703 | 1720 | #. Translators: This is a distance measured in feet |
1704 | #: src/utils.js:310 | |
1721 | #: src/utils.js:319 | |
1705 | 1722 | #, javascript-format |
1706 | 1723 | msgid "%s ft" |
1707 | 1724 | 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" | |
1708 | 1832 | |
1709 | 1833 | #~ msgid "Open with another application" |
1710 | 1834 | #~ msgstr "Abrir con otra aplicación" |
8 | 8 | msgid "" |
9 | 9 | msgstr "Project-Id-Version: gnome-maps master\n" |
10 | 10 | "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" | |
13 | 13 | "Last-Translator: Asier Sarasua Garmendia <asier.sarasua@gmail.com>\n" |
14 | 14 | "Language-Team: Basque <librezale@librezale.eus>\n" |
15 | 15 | "Language: eu\n" |
20 | 20 | |
21 | 21 | #: data/org.gnome.Maps.appdata.xml.in:6 |
22 | 22 | msgid "GNOME Maps" |
23 | msgstr "GNOMEren Mapak" | |
23 | msgstr "GNOME Mapak" | |
24 | 24 | |
25 | 25 | #: data/org.gnome.Maps.appdata.xml.in:7 |
26 | 26 | msgid "Find places around the world" |
48 | 48 | "Street, Boston” or “Hotels near Alexanderplatz, Berlin”." |
49 | 49 | msgstr "Bilatu kokaleku mota zehatz bat, adibidez 'Diru-truke bulegoa, Bilbo' edo 'Hotelak kale nagusia, Bilbo'" |
50 | 50 | |
51 | #: data/org.gnome.Maps.appdata.xml.in:59 | |
51 | #: data/org.gnome.Maps.appdata.xml.in:92 | |
52 | 52 | msgid "The GNOME Project" |
53 | 53 | msgstr "GNOME proiektua" |
54 | 54 | |
61 | 61 | #. |
62 | 62 | #. Translators: This is the program name. |
63 | 63 | #: 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 | |
65 | 65 | msgid "Maps" |
66 | 66 | msgstr "Mapak" |
67 | 67 | |
100 | 100 | |
101 | 101 | #: data/org.gnome.Maps.gschema.xml:22 |
102 | 102 | 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)" | |
104 | 104 | |
105 | 105 | #: data/org.gnome.Maps.gschema.xml:26 |
106 | 106 | msgid "Window size" |
467 | 467 | |
468 | 468 | #: data/ui/osm-account-dialog.ui:56 |
469 | 469 | msgid "Email" |
470 | msgstr "Helb. el." | |
470 | msgstr "Posta elektronikoa" | |
471 | 471 | |
472 | 472 | #: data/ui/osm-account-dialog.ui:81 |
473 | 473 | msgid "Password" |
615 | 615 | msgid "Route search by GraphHopper" |
616 | 616 | msgstr "ibilbidea GraphHopper-ek bilatuta" |
617 | 617 | |
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" | |
628 | 622 | "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" | |
629 | 628 | "Names and brands shown are to be considered as registered trademarks when " |
630 | 629 | "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" | |
635 | 633 | "Erakutsitako izenak eta markak erregistratutako marka gisa hartuko dira dagokienenean." |
636 | 634 | |
637 | 635 | #: data/ui/social-place-more-results-row.ui:8 |
686 | 684 | |
687 | 685 | #: data/ui/transit-options-panel.ui:145 |
688 | 686 | msgid "Ferries" |
689 | msgstr "Ferriak" | |
687 | msgstr "Ferryak" | |
688 | ||
689 | #: data/ui/transit-options-panel.ui:152 | |
690 | msgid "Airplanes" | |
691 | msgstr "Hegazkinak" | |
690 | 692 | |
691 | 693 | #: data/ui/user-location-bubble.ui:13 src/geoclue.js:118 |
692 | 694 | msgid "Current location" |
776 | 778 | #: src/checkIn.js:144 |
777 | 779 | #, javascript-format |
778 | 780 | msgid "Cannot find “%s” in the social service" |
779 | msgstr "Ezin da “%s“ aurkitu zerbitzu sozialean" | |
781 | msgstr "Ezin da “%s” aurkitu zerbitzu sozialean" | |
780 | 782 | |
781 | 783 | #: src/checkIn.js:146 |
782 | 784 | msgid "Cannot find a suitable place to check-in in this location" |
851 | 853 | msgid "unknown geometry" |
852 | 854 | msgstr "geometria ezezaguna" |
853 | 855 | |
854 | #: src/graphHopper.js:112 src/openTripPlanner.js:652 | |
856 | #: src/graphHopper.js:112 src/transitPlan.js:169 | |
855 | 857 | msgid "Route request failed." |
856 | 858 | msgstr "Huts egin du ibilbidearen eskaerak." |
857 | 859 | |
858 | #: src/graphHopper.js:119 src/openTripPlanner.js:615 | |
860 | #: src/graphHopper.js:119 src/transitPlan.js:161 | |
859 | 861 | msgid "No route found." |
860 | 862 | msgstr "Ez da ibilbiderik aurkitu." |
861 | 863 | |
862 | #: src/graphHopper.js:207 | |
864 | #: src/graphHopper.js:207 src/transitplugins/openTripPlanner.js:1164 | |
863 | 865 | msgid "Start!" |
864 | 866 | msgstr "Hasi!" |
865 | 867 | |
867 | 869 | msgid "All Layer Files" |
868 | 870 | msgstr "Geruzen fitxategi guztiak" |
869 | 871 | |
870 | #: src/mainWindow.js:442 | |
872 | #: src/mainWindow.js:446 | |
871 | 873 | msgid "Failed to connect to location service" |
872 | 874 | msgstr "Huts egin du kokapen-zerbitzuarekin konektatzean" |
873 | 875 | |
874 | #: src/mainWindow.js:507 | |
876 | #: src/mainWindow.js:511 | |
875 | 877 | msgid "translator-credits" |
876 | 878 | msgstr "Asier Sarasua Garmendia <asier.sarasua@gmail.com>" |
877 | 879 | |
878 | #: src/mainWindow.js:510 | |
880 | #: src/mainWindow.js:514 | |
879 | 881 | msgid "A map application for GNOME" |
880 | 882 | msgstr "GNOMEren mapen aplikazioa" |
881 | 883 | |
882 | #: src/mainWindow.js:521 | |
884 | #: src/mainWindow.js:525 | |
883 | 885 | msgid "Copyright © 2011 – 2019 Red Hat, Inc. and The GNOME Maps authors" |
884 | 886 | msgstr "Copyright © 2011 – 2019 Red Hat, Inc. eta GNOME Maps egileak" |
885 | 887 | |
886 | #: src/mainWindow.js:541 | |
888 | #: src/mainWindow.js:545 | |
887 | 889 | #, javascript-format |
888 | 890 | msgid "Map data by %s and contributors" |
889 | 891 | msgstr "Maparen datuen sortzaileak: %s eta laguntzaileak" |
893 | 895 | #. * the bare name of the tile provider, or a linkified URL if one |
894 | 896 | #. * is available |
895 | 897 | #. |
896 | #: src/mainWindow.js:557 | |
898 | #: src/mainWindow.js:561 | |
897 | 899 | #, javascript-format |
898 | 900 | msgid "Map tiles provided by %s" |
899 | 901 | msgstr "Mapa-lauzen hornitzaileak: %s" |
907 | 909 | #. * (i.e. "%2$s ... %1$s ..." for positioning the project URL |
908 | 910 | #. * before the provider). |
909 | 911 | #. |
910 | #: src/mainWindow.js:586 | |
912 | #: src/mainWindow.js:590 | |
911 | 913 | #, javascript-format |
912 | 914 | msgid "Search provided by %s using %s" |
913 | 915 | msgstr "%s motorrak hornitutako bilaketa %s bidez" |
914 | 916 | |
915 | #: src/mapView.js:374 | |
917 | #: src/mapView.js:375 | |
916 | 918 | msgid "File type is not supported" |
917 | 919 | msgstr "Fitxategi mota ez dago onartuta" |
918 | 920 | |
919 | #: src/mapView.js:381 | |
921 | #: src/mapView.js:382 | |
920 | 922 | msgid "Failed to open layer" |
921 | 923 | msgstr "Huts egin du geruza irekitzean" |
922 | 924 | |
923 | #: src/mapView.js:417 | |
925 | #: src/mapView.js:418 | |
924 | 926 | msgid "Failed to open GeoURI" |
925 | 927 | 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." | |
930 | 928 | |
931 | 929 | #. setting the status in session.cancel_message still seems |
932 | 930 | #. to always give status IO_ERROR |
1219 | 1217 | msgid "Phone:" |
1220 | 1218 | msgstr "Telefonoa:" |
1221 | 1219 | |
1222 | #: src/placeEntry.js:205 | |
1220 | #: src/placeEntry.js:209 | |
1223 | 1221 | msgid "Failed to parse Geo URI" |
1224 | 1222 | msgstr "Huts egin du Geo URIa aztertzean" |
1225 | 1223 | |
1282 | 1280 | #: src/printLayout.js:312 |
1283 | 1281 | #, javascript-format |
1284 | 1282 | msgid "From %s to %s" |
1285 | msgstr "\"%s\"(e)ndik \"%s\"(e)ra" | |
1283 | msgstr "“%s”(e)ndik “%s”(e)ra" | |
1286 | 1284 | |
1287 | 1285 | #: src/printOperation.js:46 |
1288 | 1286 | msgid "Loading map tiles for printing" |
1332 | 1330 | msgstr "huts egin du fitxategia kargatzean" |
1333 | 1331 | |
1334 | 1332 | #. Translators: %s is a time expression with the format "%f h" or "%f min" |
1335 | #: src/sidebar.js:293 | |
1333 | #: src/sidebar.js:296 | |
1336 | 1334 | #, javascript-format |
1337 | 1335 | msgid "Estimated time: %s" |
1338 | 1336 | msgstr "Aurreikusitako denbora: %s" |
1337 | ||
1338 | #: src/sidebar.js:352 | |
1339 | #, javascript-format | |
1340 | msgid "Itineraries provided by %s" | |
1341 | msgstr "Ibilbideen hornitzailea: %s" | |
1339 | 1342 | |
1340 | 1343 | #. Translators: this is a format string indicating instructions |
1341 | 1344 | #. * starting a journey at the address given as the parameter |
1369 | 1372 | msgid "Arrive at %s" |
1370 | 1373 | msgstr "Iritsi: %s" |
1371 | 1374 | |
1372 | #: src/transit.js:77 | |
1375 | #: src/transit.js:77 src/transitplugins/openTripPlanner.js:1177 | |
1373 | 1376 | msgid "Arrive" |
1374 | 1377 | msgstr "Iritsi" |
1375 | 1378 | |
1401 | 1404 | #. * Translators: this is a format string giving the equivalent to |
1402 | 1405 | #. * "may 29" according to the current locale's convensions. |
1403 | 1406 | #. |
1404 | #: src/transitOptionsPanel.js:141 | |
1407 | #: src/transitOptionsPanel.js:143 | |
1405 | 1408 | msgctxt "month-day-date" |
1406 | 1409 | msgid "%b %e" |
1407 | 1410 | 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." | |
1408 | 1419 | |
1409 | 1420 | #. Translators: this is a format string for showing a departure and |
1410 | 1421 | #. * arrival time, like: |
1411 | 1422 | #. * "12:00 – 13:03" where the placeholder %s are the actual times, |
1412 | 1423 | #. * these could be rearranged if needed. |
1413 | 1424 | #. |
1414 | #: src/transitPlan.js:254 | |
1425 | #: src/transitPlan.js:290 | |
1415 | 1426 | #, javascript-format |
1416 | 1427 | msgid "%s – %s" |
1417 | 1428 | msgstr "%s – %s" |
1420 | 1431 | #. * less than an hour, with only the minutes part, using plural forms |
1421 | 1432 | #. * as appropriate |
1422 | 1433 | #. |
1423 | #: src/transitPlan.js:281 | |
1434 | #: src/transitPlan.js:317 | |
1424 | 1435 | #, javascript-format |
1425 | 1436 | msgid "%d minute" |
1426 | 1437 | msgid_plural "%d minutes" |
1431 | 1442 | #. * where the duration is an exact number of hours (i.e. no |
1432 | 1443 | #. * minutes part), using plural forms as appropriate |
1433 | 1444 | #. |
1434 | #: src/transitPlan.js:292 | |
1445 | #: src/transitPlan.js:328 | |
1435 | 1446 | #, javascript-format |
1436 | 1447 | msgid "%d hour" |
1437 | 1448 | msgid_plural "%d hours" |
1442 | 1453 | #. * where the duration contains an hour and minute part, it's |
1443 | 1454 | #. * pluralized on the hours part |
1444 | 1455 | #. |
1445 | #: src/transitPlan.js:298 | |
1456 | #: src/transitPlan.js:334 | |
1446 | 1457 | #, javascript-format |
1447 | 1458 | msgid "%d:%02d hour" |
1448 | 1459 | msgid_plural "%d:%02d hours" |
1455 | 1466 | #. * "12:00–13:03" where the placeholder %s are the actual times, |
1456 | 1467 | #. * these could be rearranged if needed. |
1457 | 1468 | #. |
1458 | #: src/transitPlan.js:651 | |
1469 | #: src/transitPlan.js:699 | |
1459 | 1470 | #, javascript-format |
1460 | 1471 | msgid "%s–%s" |
1461 | 1472 | msgstr "%s–%s" |
1609 | 1620 | msgstr "zerbitzua" |
1610 | 1621 | |
1611 | 1622 | #. Translators: Accuracy of user location information |
1612 | #: src/utils.js:220 | |
1623 | #: src/utils.js:229 | |
1613 | 1624 | msgid "Unknown" |
1614 | 1625 | msgstr "Ezezaguna" |
1615 | 1626 | |
1616 | 1627 | #. Translators: Accuracy of user location information |
1617 | #: src/utils.js:223 | |
1628 | #: src/utils.js:232 | |
1618 | 1629 | msgid "Exact" |
1619 | 1630 | msgstr "Zehatza" |
1620 | 1631 | |
1621 | #: src/utils.js:281 | |
1632 | #: src/utils.js:290 | |
1622 | 1633 | #, javascript-format |
1623 | 1634 | msgid "%f h" |
1624 | 1635 | msgstr "%f o" |
1625 | 1636 | |
1626 | #: src/utils.js:283 | |
1637 | #: src/utils.js:292 | |
1627 | 1638 | #, javascript-format |
1628 | 1639 | msgid "%f min" |
1629 | 1640 | msgstr "%f min" |
1630 | 1641 | |
1631 | #: src/utils.js:285 | |
1642 | #: src/utils.js:294 | |
1632 | 1643 | #, javascript-format |
1633 | 1644 | msgid "%f s" |
1634 | 1645 | msgstr "%f s" |
1635 | 1646 | |
1636 | 1647 | #. Translators: This is a distance measured in kilometers |
1637 | #: src/utils.js:296 | |
1648 | #: src/utils.js:305 | |
1638 | 1649 | #, javascript-format |
1639 | 1650 | msgid "%s km" |
1640 | 1651 | msgstr "%s km" |
1641 | 1652 | |
1642 | 1653 | #. Translators: This is a distance measured in meters |
1643 | #: src/utils.js:299 | |
1654 | #: src/utils.js:308 | |
1644 | 1655 | #, javascript-format |
1645 | 1656 | msgid "%s m" |
1646 | 1657 | msgstr "%s m" |
1647 | 1658 | |
1648 | 1659 | #. Translators: This is a distance measured in miles |
1649 | #: src/utils.js:307 | |
1660 | #: src/utils.js:316 | |
1650 | 1661 | #, javascript-format |
1651 | 1662 | msgid "%s mi" |
1652 | 1663 | msgstr "%s mi" |
1653 | 1664 | |
1654 | 1665 | #. Translators: This is a distance measured in feet |
1655 | #: src/utils.js:310 | |
1666 | #: src/utils.js:319 | |
1656 | 1667 | #, javascript-format |
1657 | 1668 | msgid "%s ft" |
1658 | 1669 | 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" | |
1659 | 1777 | |
1660 | 1778 | #~ msgid "org.gnome.Maps" |
1661 | 1779 | #~ msgstr "org.gnome.Maps" |
6 | 6 | msgstr "" |
7 | 7 | "Project-Id-Version: gnome-maps gnome-3-10\n" |
8 | 8 | "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" | |
11 | 11 | "Last-Translator: Danial Behzadi <dani.behzi@ubuntu.com>\n" |
12 | 12 | "Language-Team: Persian\n" |
13 | 13 | "Language: fa\n" |
15 | 15 | "Content-Type: text/plain; charset=UTF-8\n" |
16 | 16 | "Content-Transfer-Encoding: 8bit\n" |
17 | 17 | "Plural-Forms: nplurals=1; plural=0;\n" |
18 | "X-Generator: Poedit 2.2.1\n" | |
18 | "X-Generator: Poedit 2.2.4\n" | |
19 | 19 | |
20 | 20 | #: data/org.gnome.Maps.appdata.xml.in:6 |
21 | 21 | msgid "GNOME Maps" |
28 | 28 | #: data/org.gnome.Maps.appdata.xml.in:9 |
29 | 29 | msgid "" |
30 | 30 | "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 | "کنید." | |
37 | 37 | |
38 | 38 | #: data/org.gnome.Maps.appdata.xml.in:14 |
39 | 39 | 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 | "در سراسر جهان درست شده است." | |
45 | 45 | |
46 | 46 | #. Translators: Search is carried out on OpenStreetMap data using Nominatim. |
47 | 47 | #. Visit http://wiki.openstreetmap.org/wiki/Nominatim/Special_Phrases and click |
51 | 51 | "You can even search for specific types of locations, such as “Pubs near Main " |
52 | 52 | "Street, Boston” or “Hotels near Alexanderplatz, Berlin”." |
53 | 53 | 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» جستوجو کنید." | |
56 | 56 | |
57 | 57 | #: data/org.gnome.Maps.appdata.xml.in:92 |
58 | 58 | msgid "The GNOME Project" |
66 | 66 | #. * overview. |
67 | 67 | #. |
68 | 68 | #. 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 | |
71 | 71 | msgid "Maps" |
72 | 72 | msgstr "نقشهها" |
73 | 73 | |
178 | 178 | "Latest used Foursquare check-in privacy setting. Possible values are: public, " |
179 | 179 | "followers or private." |
180 | 180 | msgstr "" |
181 | "آخرین تنظیمات استفاده شدهٔ محرمانگی اعلام حضور فوراسکور. مقادیر مجاز عبارتند " | |
182 | "از: public، followers یا private." | |
181 | "آخرین تنظیمات استفاده شدهٔ محرمانگی اعلام حضور فوراسکور. مقادیر مجاز عبارتند از: " | |
182 | "public، followers یا private." | |
183 | 183 | |
184 | 184 | #: data/org.gnome.Maps.gschema.xml:67 |
185 | 185 | msgid "Foursquare check-in Facebook broadcasting" |
187 | 187 | |
188 | 188 | #: data/org.gnome.Maps.gschema.xml:68 |
189 | 189 | 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." | |
192 | 192 | msgstr "" |
193 | 193 | "مشخّص میکند که آیا فوراسکور باید اعلام حضور را به عنوان یک فرسته در حساب فیسبوک " |
194 | 194 | "مرتبط با حساب فوراسکور منتشر کند یا نه." |
199 | 199 | |
200 | 200 | #: data/org.gnome.Maps.gschema.xml:73 |
201 | 201 | 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." | |
204 | 204 | msgstr "" |
205 | 205 | "مشخّص میکند که آیا فوراسکور باید اعلام حضور را به عنوان یک توییت در حساب توییتر " |
206 | 206 | "مرتبط با حساب فوراسکور منتشر کند یا نه." |
211 | 211 | |
212 | 212 | #: data/org.gnome.Maps.gschema.xml:78 |
213 | 213 | msgid "Indicates if the user has signed in to edit OpenStreetMap data." |
214 | msgstr "" | |
215 | "مشخّص میکند که آیا کاربر برای ویرایش دادههای OpenStreetMap وارد شده یا نه." | |
214 | msgstr "مشخّص میکند که آیا کاربر برای ویرایش دادههای OpenStreetMap وارد شده یا نه." | |
216 | 215 | |
217 | 216 | #: data/org.gnome.Maps.gschema.xml:82 |
218 | 217 | msgid "Last used transportation type for routing" |
501 | 500 | #: data/ui/osm-account-dialog.ui:159 |
502 | 501 | msgid "" |
503 | 502 | "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." | |
506 | 505 | msgstr "" |
507 | 506 | "متأسّفانه تأثیری نداشت. لطفا دوباره تلاش کرده یا برای\n" |
508 | 507 | "باز نشانی گذرواژهتان به <a href=\"https://www.openstreetmap.org/user/forgot-" |
571 | 570 | "OpenStreetMap data." |
572 | 571 | msgstr "" |
573 | 572 | "تغییرات نقشه در تمام نقشههایی که از دادههای\n" |
574 | " OpenStreetMap استفاده میکنند قابل مشاهده است." | |
573 | "اوپناستریتمپ استفاده میکنند قابل مشاهده است." | |
575 | 574 | |
576 | 575 | #: data/ui/osm-edit-dialog.ui:241 |
577 | 576 | msgid "Recently Used" |
634 | 633 | msgid "Route search by GraphHopper" |
635 | 634 | msgstr "جستوجوی مسیر به دست GraphHopper" |
636 | 635 | |
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" | |
646 | 640 | "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" | |
647 | 645 | "Names and brands shown are to be considered as registered trademarks when " |
648 | 646 | "applicable." |
649 | 647 | msgstr "" |
650 | "مسیریابی برنامهٔ سفر برای حمل و نقل عمومی با استفاده از دادههای جدول زمانی\n" | |
651 | "گرفته شده از آژانسها یا شرکتهای مسافرتی به دست گنوم فراهم شده است.\n" | |
652 | "آژانسها و شرکتها نمیتوانند در قبال نتایج نمایشداده شده مسيول باشند.\n" | |
653 | "گنوم نمیتواند درستی رمانبندی برنامههای سفر نمایشداده شده را تضمین کند.\n" | |
654 | "نامها و مارکهای نمایشداده شده، در صورت امکان باید علامتهای تجاری ثبتشده در نظر " | |
655 | "گرفته شوند." | |
648 | "مسیریابی برنامهٔ سفر برای حملونقل عمومی به دست خدمات ثالث فراهم شده است.\n" | |
649 | "گنوم نمیتواند درستی برنامهٔ سفر و زمانبندیهای نمایش دادهشده را تصمین کند.\n" | |
650 | "به خاطر داشته باشید که ممکن است برخی فراهمکنندگان، تمام حالتهای موجود برای\n" | |
651 | "حملونقل را نداشته باشند. مثلاً ممکن است یک فراهمکنندهٔ ملّی، خطوط هوایی را نداشته\n" | |
652 | "باشد و یک فراهمکنندهٔ محلّی میتواند قطارهای منطقهای را نشان ندهد.\n" | |
653 | "نامها و برندهای نمایشدادهشده باید هنگام امکان به عنوان علایم تجاری در نظر گرفته " | |
654 | "شوند." | |
656 | 655 | |
657 | 656 | #: data/ui/social-place-more-results-row.ui:8 |
658 | 657 | msgid "Show more results" |
708 | 707 | msgid "Ferries" |
709 | 708 | msgstr "قایقها" |
710 | 709 | |
710 | #: data/ui/transit-options-panel.ui:152 | |
711 | msgid "Airplanes" | |
712 | msgstr "هواپیماها" | |
713 | ||
711 | 714 | #: data/ui/user-location-bubble.ui:13 src/geoclue.js:118 |
712 | 715 | msgid "Current location" |
713 | 716 | msgstr "موقعیت کنونی" |
764 | 767 | |
765 | 768 | #: src/checkInDialog.js:201 |
766 | 769 | 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 | "برگزینید." | |
772 | 775 | |
773 | 776 | #: src/checkInDialog.js:203 |
774 | 777 | 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." | |
777 | 780 | msgstr "" |
778 | 781 | "نقشهها نمیتواند مکان را برای اعلام حضور در فوراسکور بیابد. لطفاً موقعیتی را از " |
779 | 782 | "فهرست برگزینید." |
808 | 811 | |
809 | 812 | #: src/checkIn.js:150 |
810 | 813 | 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 | "را بگشایید" | |
816 | 819 | |
817 | 820 | #: src/contextMenu.js:99 |
818 | 821 | msgid "Route from here" |
832 | 835 | |
833 | 836 | #: src/contextMenu.js:190 |
834 | 837 | 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 | "کمی طول بکشد." | |
840 | 843 | |
841 | 844 | #. Translators: This is a format string for a PNG filename for an |
842 | 845 | #. * exported image with coordinates. The .png extension should be kept |
879 | 882 | msgid "unknown geometry" |
880 | 883 | msgstr "هندسهٔ ناشناخته" |
881 | 884 | |
882 | #: src/graphHopper.js:112 src/openTripPlanner.js:652 | |
885 | #: src/graphHopper.js:112 src/transitPlan.js:192 | |
883 | 886 | msgid "Route request failed." |
884 | 887 | msgstr "درخواست مسیر شکست خورد." |
885 | 888 | |
886 | #: src/graphHopper.js:119 src/openTripPlanner.js:615 | |
889 | #: src/graphHopper.js:119 src/transitPlan.js:184 | |
887 | 890 | msgid "No route found." |
888 | 891 | msgstr "هیچ مسیری پیدا نشد." |
889 | 892 | |
890 | #: src/graphHopper.js:207 | |
893 | #: src/graphHopper.js:207 src/transitplugins/openTripPlanner.js:1100 | |
891 | 894 | msgid "Start!" |
892 | 895 | msgstr "شروع!" |
893 | 896 | |
942 | 945 | msgid "Search provided by %s using %s" |
943 | 946 | msgstr "جستوجو به دست %s با استفاده از %s فراهم شده" |
944 | 947 | |
945 | #: src/mapView.js:374 | |
948 | #: src/mapView.js:375 | |
946 | 949 | msgid "File type is not supported" |
947 | 950 | msgstr "گونهٔ پرونده پشتیبانی نمیشود" |
948 | 951 | |
949 | #: src/mapView.js:381 | |
952 | #: src/mapView.js:382 | |
950 | 953 | msgid "Failed to open layer" |
951 | 954 | msgstr "شکست در گشودن لایه" |
952 | 955 | |
953 | #: src/mapView.js:417 | |
956 | #: src/mapView.js:418 | |
954 | 957 | msgid "Failed to open GeoURI" |
955 | 958 | msgstr "شکست در گشودن نشانی جغرافیایی" |
956 | ||
957 | #: src/openTripPlanner.js:648 | |
958 | msgid "No timetable data found for this route." | |
959 | msgstr "هیچ دادهٔٔ زمانیای برای این مسیر پیدا نشد." | |
960 | 959 | |
961 | 960 | #. setting the status in session.cancel_message still seems |
962 | 961 | #. to always give status IO_ERROR |
1006 | 1005 | |
1007 | 1006 | #: src/osmEditDialog.js:122 |
1008 | 1007 | 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." | |
1011 | 1010 | msgstr "" |
1012 | 1011 | "پایگاه وب رسمی. سعی کنید از سادهترین شکل آدرس استفاده کنید مثلا http://example." |
1013 | 1012 | "com به جای http://example.com/index.html." |
1030 | 1029 | |
1031 | 1030 | #: src/osmEditDialog.js:140 |
1032 | 1031 | 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» باشد." | |
1038 | 1036 | |
1039 | 1037 | #: src/osmEditDialog.js:144 |
1040 | 1038 | msgid "Opening hours" |
1201 | 1199 | |
1202 | 1200 | #: src/osmEditDialog.js:220 |
1203 | 1201 | 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 | "نگارنده هنگام ایجادش یا راهنمایی برای بهبودهای بعدی." | |
1210 | 1207 | |
1211 | 1208 | #: src/osmEditDialog.js:325 |
1212 | 1209 | msgctxt "dialog title" |
1303 | 1300 | |
1304 | 1301 | #: src/place.js:475 |
1305 | 1302 | msgid "Place not found in OpenStreetMap" |
1306 | msgstr "مکان در OpenStreetMap پیدا نشد" | |
1303 | msgstr "مکان در اوپن استریت مپ پیدا نشد" | |
1307 | 1304 | |
1308 | 1305 | #: src/place.js:495 |
1309 | 1306 | msgid "OpenStreetMap URL is not valid" |
1370 | 1367 | msgstr "شکست در بارگیری پرونده" |
1371 | 1368 | |
1372 | 1369 | #. Translators: %s is a time expression with the format "%f h" or "%f min" |
1373 | #: src/sidebar.js:293 | |
1370 | #: src/sidebar.js:296 | |
1374 | 1371 | #, javascript-format |
1375 | 1372 | msgid "Estimated time: %s" |
1376 | 1373 | msgstr "زمان تخمینی: %s" |
1374 | ||
1375 | #: src/sidebar.js:352 | |
1376 | #, javascript-format | |
1377 | msgid "Itineraries provided by %s" | |
1378 | msgstr "برنامه سفر فراهم شده به دست %s" | |
1377 | 1379 | |
1378 | 1380 | #. Translators: this is a format string indicating instructions |
1379 | 1381 | #. * starting a journey at the address given as the parameter |
1407 | 1409 | msgid "Arrive at %s" |
1408 | 1410 | msgstr "رسیدن به مقصد در %s" |
1409 | 1411 | |
1410 | #: src/transit.js:77 | |
1412 | #: src/transit.js:77 src/transitplugins/openTripPlanner.js:1113 | |
1411 | 1413 | msgid "Arrive" |
1412 | 1414 | msgstr "رسیدن" |
1413 | 1415 | |
1439 | 1441 | #. * Translators: this is a format string giving the equivalent to |
1440 | 1442 | #. * "may 29" according to the current locale's convensions. |
1441 | 1443 | #. |
1442 | #: src/transitOptionsPanel.js:141 | |
1444 | #: src/transitOptionsPanel.js:143 | |
1443 | 1445 | msgctxt "month-day-date" |
1444 | 1446 | msgid "%b %e" |
1445 | 1447 | 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 "برای این مسیر هیچ فراهم کنندهای پیدا نشد." | |
1446 | 1456 | |
1447 | 1457 | #. Translators: this is a format string for showing a departure and |
1448 | 1458 | #. * arrival time, like: |
1449 | 1459 | #. * "12:00 – 13:03" where the placeholder %s are the actual times, |
1450 | 1460 | #. * these could be rearranged if needed. |
1451 | 1461 | #. |
1452 | #: src/transitPlan.js:254 | |
1462 | #: src/transitPlan.js:313 | |
1453 | 1463 | #, javascript-format |
1454 | 1464 | msgid "%s – %s" |
1455 | 1465 | msgstr "%s – %s" |
1458 | 1468 | #. * less than an hour, with only the minutes part, using plural forms |
1459 | 1469 | #. * as appropriate |
1460 | 1470 | #. |
1461 | #: src/transitPlan.js:281 | |
1471 | #: src/transitPlan.js:340 | |
1462 | 1472 | #, javascript-format |
1463 | 1473 | msgid "%d minute" |
1464 | 1474 | msgid_plural "%d minutes" |
1468 | 1478 | #. * where the duration is an exact number of hours (i.e. no |
1469 | 1479 | #. * minutes part), using plural forms as appropriate |
1470 | 1480 | #. |
1471 | #: src/transitPlan.js:292 | |
1481 | #: src/transitPlan.js:351 | |
1472 | 1482 | #, javascript-format |
1473 | 1483 | msgid "%d hour" |
1474 | 1484 | msgid_plural "%d hours" |
1478 | 1488 | #. * where the duration contains an hour and minute part, it's |
1479 | 1489 | #. * pluralized on the hours part |
1480 | 1490 | #. |
1481 | #: src/transitPlan.js:298 | |
1491 | #: src/transitPlan.js:357 | |
1482 | 1492 | #, javascript-format |
1483 | 1493 | msgid "%d:%02d hour" |
1484 | 1494 | msgid_plural "%d:%02d hours" |
1490 | 1500 | #. * "12:00–13:03" where the placeholder %s are the actual times, |
1491 | 1501 | #. * these could be rearranged if needed. |
1492 | 1502 | #. |
1493 | #: src/transitPlan.js:651 | |
1503 | #: src/transitPlan.js:734 | |
1494 | 1504 | #, javascript-format |
1495 | 1505 | msgid "%s–%s" |
1496 | 1506 | msgstr "%s–%s" |
1644 | 1654 | msgstr "خدمت" |
1645 | 1655 | |
1646 | 1656 | #. Translators: Accuracy of user location information |
1647 | #: src/utils.js:220 | |
1657 | #: src/utils.js:229 | |
1648 | 1658 | msgid "Unknown" |
1649 | 1659 | msgstr "ناشناخته" |
1650 | 1660 | |
1651 | 1661 | #. Translators: Accuracy of user location information |
1652 | #: src/utils.js:223 | |
1662 | #: src/utils.js:232 | |
1653 | 1663 | msgid "Exact" |
1654 | 1664 | msgstr "دقیقاً" |
1655 | 1665 | |
1656 | #: src/utils.js:281 | |
1666 | #: src/utils.js:290 | |
1657 | 1667 | #, javascript-format |
1658 | 1668 | msgid "%f h" |
1659 | 1669 | msgstr "%If ساعت" |
1660 | 1670 | |
1661 | #: src/utils.js:283 | |
1671 | #: src/utils.js:292 | |
1662 | 1672 | #, javascript-format |
1663 | 1673 | msgid "%f min" |
1664 | 1674 | msgstr "%If دقیقه" |
1665 | 1675 | |
1666 | #: src/utils.js:285 | |
1676 | #: src/utils.js:294 | |
1667 | 1677 | #, javascript-format |
1668 | 1678 | msgid "%f s" |
1669 | 1679 | msgstr "%f s" |
1670 | 1680 | |
1671 | 1681 | #. Translators: This is a distance measured in kilometers |
1672 | #: src/utils.js:296 | |
1682 | #: src/utils.js:305 | |
1673 | 1683 | #, javascript-format |
1674 | 1684 | msgid "%s km" |
1675 | 1685 | msgstr "%s کم" |
1676 | 1686 | |
1677 | 1687 | #. Translators: This is a distance measured in meters |
1678 | #: src/utils.js:299 | |
1688 | #: src/utils.js:308 | |
1679 | 1689 | #, javascript-format |
1680 | 1690 | msgid "%s m" |
1681 | 1691 | msgstr "%s م" |
1682 | 1692 | |
1683 | 1693 | #. Translators: This is a distance measured in miles |
1684 | #: src/utils.js:307 | |
1694 | #: src/utils.js:316 | |
1685 | 1695 | #, javascript-format |
1686 | 1696 | msgid "%s mi" |
1687 | 1697 | msgstr "%s ما" |
1688 | 1698 | |
1689 | 1699 | #. Translators: This is a distance measured in feet |
1690 | #: src/utils.js:310 | |
1700 | #: src/utils.js:319 | |
1691 | 1701 | #, javascript-format |
1692 | 1702 | msgid "%s ft" |
1693 | 1703 | 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" | |
1694 | 1811 | |
1695 | 1812 | #~ msgid "Quit" |
1696 | 1813 | #~ msgstr "خروج" |
6 | 6 | msgstr "" |
7 | 7 | "Project-Id-Version: gnome-maps master\n" |
8 | 8 | "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" | |
11 | 11 | "Last-Translator: Fabio Tomat <f.t.public@gmail.com>\n" |
12 | 12 | "Language-Team: Friulian <fur@li.org>\n" |
13 | 13 | "Language: fur\n" |
14 | 14 | "MIME-Version: 1.0\n" |
15 | 15 | "Content-Type: text/plain; charset=UTF-8\n" |
16 | 16 | "Content-Transfer-Encoding: 8bit\n" |
17 | "X-Generator: Poedit 2.2.3\n" | |
17 | "X-Generator: Poedit 2.2.4\n" | |
18 | 18 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" |
19 | 19 | |
20 | 20 | #: data/org.gnome.Maps.appdata.xml.in:6 |
138 | 138 | |
139 | 139 | #: data/org.gnome.Maps.gschema.xml:42 |
140 | 140 | 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." | |
142 | 142 | |
143 | 143 | #: data/org.gnome.Maps.gschema.xml:46 |
144 | 144 | msgid "Number of recent places to store" |
639 | 639 | msgid "Route search by GraphHopper" |
640 | 640 | msgstr "Ricercje dal percors cun GraphHopper" |
641 | 641 | |
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" | |
652 | 646 | "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" | |
653 | 652 | "Names and brands shown are to be considered as registered trademarks when " |
654 | 653 | "applicable." |
655 | 654 | 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" | |
661 | 663 | "I nons e lis marchis mostradis si àn di considerâ come marchis regjistradis " |
662 | 664 | "cuant che si puedin aplicâ." |
663 | 665 | |
715 | 717 | msgid "Ferries" |
716 | 718 | msgstr "Traghets" |
717 | 719 | |
720 | #: data/ui/transit-options-panel.ui:152 | |
721 | msgid "Airplanes" | |
722 | msgstr "Avions" | |
723 | ||
718 | 724 | #: data/ui/user-location-bubble.ui:13 src/geoclue.js:118 |
719 | 725 | msgid "Current location" |
720 | 726 | msgstr "Posizion atuâl" |
884 | 890 | |
885 | 891 | #: src/geoJSONSource.js:180 |
886 | 892 | 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 | |
890 | 896 | msgid "Route request failed." |
891 | 897 | msgstr "Richieste dal percors falide." |
892 | 898 | |
893 | #: src/graphHopper.js:119 src/openTripPlanner.js:615 | |
899 | #: src/graphHopper.js:119 src/transitPlan.js:161 | |
894 | 900 | msgid "No route found." |
895 | 901 | msgstr "Percors no cjatât." |
896 | 902 | |
897 | #: src/graphHopper.js:207 | |
903 | #: src/graphHopper.js:207 src/transitplugins/openTripPlanner.js:1154 | |
898 | 904 | msgid "Start!" |
899 | 905 | msgstr "Invie!" |
900 | 906 | |
908 | 914 | |
909 | 915 | #: src/mainWindow.js:511 |
910 | 916 | msgid "translator-credits" |
911 | msgstr "Fabio Tomat" | |
917 | msgstr "Fabio Tomat, <f.t.public@gmail.com>" | |
912 | 918 | |
913 | 919 | #: src/mainWindow.js:514 |
914 | 920 | msgid "A map application for GNOME" |
947 | 953 | msgid "Search provided by %s using %s" |
948 | 954 | msgstr "Ricercje furnide di %s doprant %s" |
949 | 955 | |
950 | #: src/mapView.js:374 | |
956 | #: src/mapView.js:375 | |
951 | 957 | msgid "File type is not supported" |
952 | 958 | msgstr "Gjenar di file no supuartât" |
953 | 959 | |
954 | #: src/mapView.js:381 | |
960 | #: src/mapView.js:382 | |
955 | 961 | msgid "Failed to open layer" |
956 | 962 | msgstr "Impussibil vierzi il strât" |
957 | 963 | |
958 | #: src/mapView.js:417 | |
964 | #: src/mapView.js:418 | |
959 | 965 | msgid "Failed to open GeoURI" |
960 | 966 | 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." | |
965 | 967 | |
966 | 968 | #. setting the status in session.cancel_message still seems |
967 | 969 | #. to always give status IO_ERROR |
1377 | 1379 | msgstr "impussibil cjariâ il file" |
1378 | 1380 | |
1379 | 1381 | #. Translators: %s is a time expression with the format "%f h" or "%f min" |
1380 | #: src/sidebar.js:293 | |
1382 | #: src/sidebar.js:296 | |
1381 | 1383 | #, javascript-format |
1382 | 1384 | msgid "Estimated time: %s" |
1383 | 1385 | 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" | |
1384 | 1391 | |
1385 | 1392 | #. Translators: this is a format string indicating instructions |
1386 | 1393 | #. * starting a journey at the address given as the parameter |
1414 | 1421 | msgid "Arrive at %s" |
1415 | 1422 | msgstr "Rive a %s" |
1416 | 1423 | |
1417 | #: src/transit.js:77 | |
1424 | #: src/transit.js:77 src/transitplugins/openTripPlanner.js:1167 | |
1418 | 1425 | msgid "Arrive" |
1419 | 1426 | msgstr "Rivade" |
1420 | 1427 | |
1446 | 1453 | #. * Translators: this is a format string giving the equivalent to |
1447 | 1454 | #. * "may 29" according to the current locale's convensions. |
1448 | 1455 | #. |
1449 | #: src/transitOptionsPanel.js:141 | |
1456 | #: src/transitOptionsPanel.js:143 | |
1450 | 1457 | msgctxt "month-day-date" |
1451 | 1458 | msgid "%b %e" |
1452 | 1459 | 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." | |
1453 | 1468 | |
1454 | 1469 | #. Translators: this is a format string for showing a departure and |
1455 | 1470 | #. * arrival time, like: |
1456 | 1471 | #. * "12:00 – 13:03" where the placeholder %s are the actual times, |
1457 | 1472 | #. * these could be rearranged if needed. |
1458 | 1473 | #. |
1459 | #: src/transitPlan.js:254 | |
1474 | #: src/transitPlan.js:290 | |
1460 | 1475 | #, javascript-format |
1461 | 1476 | msgid "%s – %s" |
1462 | 1477 | msgstr "%s – %s" |
1465 | 1480 | #. * less than an hour, with only the minutes part, using plural forms |
1466 | 1481 | #. * as appropriate |
1467 | 1482 | #. |
1468 | #: src/transitPlan.js:281 | |
1483 | #: src/transitPlan.js:317 | |
1469 | 1484 | #, javascript-format |
1470 | 1485 | msgid "%d minute" |
1471 | 1486 | msgid_plural "%d minutes" |
1476 | 1491 | #. * where the duration is an exact number of hours (i.e. no |
1477 | 1492 | #. * minutes part), using plural forms as appropriate |
1478 | 1493 | #. |
1479 | #: src/transitPlan.js:292 | |
1494 | #: src/transitPlan.js:328 | |
1480 | 1495 | #, javascript-format |
1481 | 1496 | msgid "%d hour" |
1482 | 1497 | msgid_plural "%d hours" |
1487 | 1502 | #. * where the duration contains an hour and minute part, it's |
1488 | 1503 | #. * pluralized on the hours part |
1489 | 1504 | #. |
1490 | #: src/transitPlan.js:298 | |
1505 | #: src/transitPlan.js:334 | |
1491 | 1506 | #, javascript-format |
1492 | 1507 | msgid "%d:%02d hour" |
1493 | 1508 | msgid_plural "%d:%02d hours" |
1500 | 1515 | #. * "12:00–13:03" where the placeholder %s are the actual times, |
1501 | 1516 | #. * these could be rearranged if needed. |
1502 | 1517 | #. |
1503 | #: src/transitPlan.js:651 | |
1518 | #: src/transitPlan.js:699 | |
1504 | 1519 | #, javascript-format |
1505 | 1520 | msgid "%s–%s" |
1506 | 1521 | msgstr "%s–%s" |
1511 | 1526 | |
1512 | 1527 | #: src/translations.js:58 |
1513 | 1528 | msgid "from sunrise to sunset" |
1514 | msgstr "Di un scûr a chel altri" | |
1529 | msgstr "di un scûr a chel altri" | |
1515 | 1530 | |
1516 | 1531 | #. Translators: |
1517 | 1532 | #. * This is a format string with two separate time ranges |
1582 | 1597 | |
1583 | 1598 | #: src/translations.js:185 |
1584 | 1599 | msgid "every day" |
1585 | msgstr "Ogni dì" | |
1600 | msgstr "ogni dì" | |
1586 | 1601 | |
1587 | 1602 | #. Translators: |
1588 | 1603 | #. * This represents a range of days with a starting and ending day. |
1595 | 1610 | |
1596 | 1611 | #: src/translations.js:208 |
1597 | 1612 | msgid "public holidays" |
1598 | msgstr "Feriis" | |
1613 | msgstr "festîfs" | |
1599 | 1614 | |
1600 | 1615 | #: src/translations.js:210 |
1601 | 1616 | msgid "school holidays" |
1602 | msgstr "Vacancis di scuele" | |
1617 | msgstr "vacancis di scuele" | |
1603 | 1618 | |
1604 | 1619 | #. Translators: |
1605 | 1620 | #. * This is a list with two time intervals, such as: |
1617 | 1632 | |
1618 | 1633 | #: src/translations.js:264 |
1619 | 1634 | msgid "not open" |
1620 | msgstr "No viert" | |
1635 | msgstr "no viert" | |
1621 | 1636 | |
1622 | 1637 | #. Translators: |
1623 | 1638 | #. * This is a time interval with a starting and an ending time. |
1654 | 1669 | msgstr "servizi" |
1655 | 1670 | |
1656 | 1671 | #. Translators: Accuracy of user location information |
1657 | #: src/utils.js:220 | |
1672 | #: src/utils.js:229 | |
1658 | 1673 | msgid "Unknown" |
1659 | 1674 | msgstr "No cognossût" |
1660 | 1675 | |
1661 | 1676 | #. Translators: Accuracy of user location information |
1662 | #: src/utils.js:223 | |
1677 | #: src/utils.js:232 | |
1663 | 1678 | msgid "Exact" |
1664 | 1679 | msgstr "Di precîs" |
1665 | 1680 | |
1666 | #: src/utils.js:281 | |
1681 | #: src/utils.js:290 | |
1667 | 1682 | #, javascript-format |
1668 | 1683 | msgid "%f h" |
1669 | 1684 | msgstr "%f h" |
1670 | 1685 | |
1671 | #: src/utils.js:283 | |
1686 | #: src/utils.js:292 | |
1672 | 1687 | #, javascript-format |
1673 | 1688 | msgid "%f min" |
1674 | 1689 | msgstr "%f min" |
1675 | 1690 | |
1676 | #: src/utils.js:285 | |
1691 | #: src/utils.js:294 | |
1677 | 1692 | #, javascript-format |
1678 | 1693 | msgid "%f s" |
1679 | 1694 | msgstr "%f s" |
1680 | 1695 | |
1681 | 1696 | #. Translators: This is a distance measured in kilometers |
1682 | #: src/utils.js:296 | |
1697 | #: src/utils.js:305 | |
1683 | 1698 | #, javascript-format |
1684 | 1699 | msgid "%s km" |
1685 | 1700 | msgstr "%s km" |
1686 | 1701 | |
1687 | 1702 | #. Translators: This is a distance measured in meters |
1688 | #: src/utils.js:299 | |
1703 | #: src/utils.js:308 | |
1689 | 1704 | #, javascript-format |
1690 | 1705 | msgid "%s m" |
1691 | 1706 | msgstr "%s m" |
1692 | 1707 | |
1693 | 1708 | #. Translators: This is a distance measured in miles |
1694 | #: src/utils.js:307 | |
1709 | #: src/utils.js:316 | |
1695 | 1710 | #, javascript-format |
1696 | 1711 | msgid "%s mi" |
1697 | 1712 | msgstr "%s mi" |
1698 | 1713 | |
1699 | 1714 | #. Translators: This is a distance measured in feet |
1700 | #: src/utils.js:310 | |
1715 | #: src/utils.js:319 | |
1701 | 1716 | #, javascript-format |
1702 | 1717 | msgid "%s ft" |
1703 | 1718 | 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" | |
1704 | 1826 | |
1705 | 1827 | #~ msgid "Open with another application" |
1706 | 1828 | #~ msgstr "Vierç cuntune altre aplicazion" |
7 | 7 | msgstr "" |
8 | 8 | "Project-Id-Version: gnome-maps gnome-3-28\n" |
9 | 9 | "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" | |
13 | 13 | "Language-Team: Indonesian <gnome@i15n.org>\n" |
14 | 14 | "Language: id\n" |
15 | 15 | "MIME-Version: 1.0\n" |
16 | 16 | "Content-Type: text/plain; charset=UTF-8\n" |
17 | 17 | "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" | |
20 | 20 | |
21 | 21 | #: data/org.gnome.Maps.appdata.xml.in:6 |
22 | 22 | msgid "GNOME Maps" |
57 | 57 | "\"Pub dekat Main Street, Boston\" atau \"Hotel dekat Alexanderplatz, Berlin" |
58 | 58 | "\"." |
59 | 59 | |
60 | #: data/org.gnome.Maps.appdata.xml.in:59 | |
60 | #: data/org.gnome.Maps.appdata.xml.in:92 | |
61 | 61 | msgid "The GNOME Project" |
62 | 62 | msgstr "Proyek GNOME" |
63 | 63 | |
70 | 70 | #. |
71 | 71 | #. Translators: This is the program name. |
72 | 72 | #: 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 | |
74 | 74 | msgid "Maps" |
75 | 75 | msgstr "Peta" |
76 | 76 | |
641 | 641 | msgid "Route search by GraphHopper" |
642 | 642 | msgstr "Pencarian rute oleh GraphHopper" |
643 | 643 | |
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" | |
654 | 648 | "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" | |
655 | 654 | "Names and brands shown are to be considered as registered trademarks when " |
656 | 655 | "applicable." |
657 | 656 | 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" | |
662 | 658 | "GNOME tidak dapat menjamin kebenaran dari perjalanan dan jadwal yang " |
663 | 659 | "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" | |
664 | 665 | "Nama dan merek yang ditampilkan dianggap sebagai merek dagang terdaftar " |
665 | 666 | "ketika berlaku." |
666 | 667 | |
718 | 719 | msgid "Ferries" |
719 | 720 | msgstr "Feri" |
720 | 721 | |
722 | #: data/ui/transit-options-panel.ui:152 | |
723 | msgid "Airplanes" | |
724 | msgstr "Penerbangan" | |
725 | ||
721 | 726 | #: data/ui/user-location-bubble.ui:13 src/geoclue.js:118 |
722 | 727 | msgid "Current location" |
723 | 728 | msgstr "Lokasi saat ini" |
760 | 765 | msgid "Show the version of the program" |
761 | 766 | msgstr "Tampilkan versi program" |
762 | 767 | |
768 | #: src/application.js:104 | |
769 | msgid "Ignore network availability" | |
770 | msgstr "Abaikan ketersediaan jaringan" | |
771 | ||
763 | 772 | #: src/checkInDialog.js:167 |
764 | 773 | msgid "Select an account" |
765 | 774 | msgstr "Pilih sebuah akun" |
890 | 899 | msgid "unknown geometry" |
891 | 900 | msgstr "geometri tak dikenal" |
892 | 901 | |
893 | #: src/graphHopper.js:112 src/openTripPlanner.js:652 | |
902 | #: src/graphHopper.js:112 src/transitPlan.js:192 | |
894 | 903 | msgid "Route request failed." |
895 | 904 | msgstr "Permintaan rute gagal." |
896 | 905 | |
897 | #: src/graphHopper.js:119 src/openTripPlanner.js:615 | |
906 | #: src/graphHopper.js:119 src/transitPlan.js:184 | |
898 | 907 | msgid "No route found." |
899 | 908 | msgstr "Rute tak ditemukan." |
900 | 909 | |
901 | #: src/graphHopper.js:207 | |
910 | #: src/graphHopper.js:207 src/transitplugins/openTripPlanner.js:1100 | |
902 | 911 | msgid "Start!" |
903 | 912 | msgstr "Mulai!" |
904 | 913 | |
906 | 915 | msgid "All Layer Files" |
907 | 916 | msgstr "Semua Berkas Lapisan" |
908 | 917 | |
909 | #: src/mainWindow.js:442 | |
918 | #: src/mainWindow.js:446 | |
910 | 919 | msgid "Failed to connect to location service" |
911 | 920 | msgstr "Gagal menyambung ke layanan lokasi" |
912 | 921 | |
913 | #: src/mainWindow.js:507 | |
922 | #: src/mainWindow.js:511 | |
914 | 923 | msgid "translator-credits" |
915 | 924 | msgstr "" |
916 | "Andika Triwidada <andika@gmail.com>, 2013-2016, 2018.\n" | |
925 | "Andika Triwidada <andika@gmail.com>, 2013-2016, 2018, 2019.\n" | |
917 | 926 | "Kukuh Syafaat <kukuhsyafaat@gnome.org>, 2017, 2018, 2019." |
918 | 927 | |
919 | #: src/mainWindow.js:510 | |
928 | #: src/mainWindow.js:514 | |
920 | 929 | msgid "A map application for GNOME" |
921 | 930 | msgstr "Aplikasi peta bagi GNOME" |
922 | 931 | |
923 | #: src/mainWindow.js:521 | |
932 | #: src/mainWindow.js:525 | |
924 | 933 | msgid "Copyright © 2011 – 2019 Red Hat, Inc. and The GNOME Maps authors" |
925 | 934 | msgstr "Hak Cipta © 2011 – 2019 Red Hat, Inc. dan Penulis GNOME Peta" |
926 | 935 | |
927 | #: src/mainWindow.js:541 | |
936 | #: src/mainWindow.js:545 | |
928 | 937 | #, javascript-format |
929 | 938 | msgid "Map data by %s and contributors" |
930 | 939 | msgstr "Data peta oleh %s dan kontributor" |
934 | 943 | #. * the bare name of the tile provider, or a linkified URL if one |
935 | 944 | #. * is available |
936 | 945 | #. |
937 | #: src/mainWindow.js:557 | |
946 | #: src/mainWindow.js:561 | |
938 | 947 | #, javascript-format |
939 | 948 | msgid "Map tiles provided by %s" |
940 | 949 | msgstr "Ubin peta disediakan oleh %s" |
948 | 957 | #. * (i.e. "%2$s ... %1$s ..." for positioning the project URL |
949 | 958 | #. * before the provider). |
950 | 959 | #. |
951 | #: src/mainWindow.js:586 | |
960 | #: src/mainWindow.js:590 | |
952 | 961 | #, javascript-format |
953 | 962 | msgid "Search provided by %s using %s" |
954 | 963 | msgstr "Pencarian disediakan oleh %s menggunakan %s" |
955 | 964 | |
956 | #: src/mapView.js:374 | |
965 | #: src/mapView.js:375 | |
957 | 966 | msgid "File type is not supported" |
958 | 967 | msgstr "Jenis berkas tak didukung" |
959 | 968 | |
960 | #: src/mapView.js:381 | |
969 | #: src/mapView.js:382 | |
961 | 970 | msgid "Failed to open layer" |
962 | 971 | msgstr "Gagal membuka layer" |
963 | 972 | |
964 | #: src/mapView.js:417 | |
973 | #: src/mapView.js:418 | |
965 | 974 | msgid "Failed to open GeoURI" |
966 | 975 | 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." | |
971 | 976 | |
972 | 977 | #. setting the status in session.cancel_message still seems |
973 | 978 | #. to always give status IO_ERROR |
1269 | 1274 | msgid "Phone:" |
1270 | 1275 | msgstr "Telepon:" |
1271 | 1276 | |
1272 | #: src/placeEntry.js:205 | |
1277 | #: src/placeEntry.js:209 | |
1273 | 1278 | msgid "Failed to parse Geo URI" |
1274 | 1279 | msgstr "Gagal mengurai URI Geo" |
1275 | 1280 | |
1382 | 1387 | msgstr "gagal memuat berkas" |
1383 | 1388 | |
1384 | 1389 | #. Translators: %s is a time expression with the format "%f h" or "%f min" |
1385 | #: src/sidebar.js:293 | |
1390 | #: src/sidebar.js:296 | |
1386 | 1391 | #, javascript-format |
1387 | 1392 | msgid "Estimated time: %s" |
1388 | 1393 | 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" | |
1389 | 1399 | |
1390 | 1400 | #. Translators: this is a format string indicating instructions |
1391 | 1401 | #. * starting a journey at the address given as the parameter |
1419 | 1429 | msgid "Arrive at %s" |
1420 | 1430 | msgstr "Tiba di %s" |
1421 | 1431 | |
1422 | #: src/transit.js:77 | |
1432 | #: src/transit.js:77 src/transitplugins/openTripPlanner.js:1113 | |
1423 | 1433 | msgid "Arrive" |
1424 | 1434 | msgstr "Tiba" |
1425 | 1435 | |
1451 | 1461 | #. * Translators: this is a format string giving the equivalent to |
1452 | 1462 | #. * "may 29" according to the current locale's convensions. |
1453 | 1463 | #. |
1454 | #: src/transitOptionsPanel.js:141 | |
1464 | #: src/transitOptionsPanel.js:143 | |
1455 | 1465 | msgctxt "month-day-date" |
1456 | 1466 | msgid "%b %e" |
1457 | 1467 | 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." | |
1458 | 1476 | |
1459 | 1477 | #. Translators: this is a format string for showing a departure and |
1460 | 1478 | #. * arrival time, like: |
1461 | 1479 | #. * "12:00 – 13:03" where the placeholder %s are the actual times, |
1462 | 1480 | #. * these could be rearranged if needed. |
1463 | 1481 | #. |
1464 | #: src/transitPlan.js:254 | |
1482 | #: src/transitPlan.js:313 | |
1465 | 1483 | #, javascript-format |
1466 | 1484 | msgid "%s – %s" |
1467 | 1485 | msgstr "%s – %s" |
1470 | 1488 | #. * less than an hour, with only the minutes part, using plural forms |
1471 | 1489 | #. * as appropriate |
1472 | 1490 | #. |
1473 | #: src/transitPlan.js:281 | |
1491 | #: src/transitPlan.js:340 | |
1474 | 1492 | #, javascript-format |
1475 | 1493 | msgid "%d minute" |
1476 | 1494 | msgid_plural "%d minutes" |
1481 | 1499 | #. * where the duration is an exact number of hours (i.e. no |
1482 | 1500 | #. * minutes part), using plural forms as appropriate |
1483 | 1501 | #. |
1484 | #: src/transitPlan.js:292 | |
1502 | #: src/transitPlan.js:351 | |
1485 | 1503 | #, javascript-format |
1486 | 1504 | msgid "%d hour" |
1487 | 1505 | msgid_plural "%d hours" |
1492 | 1510 | #. * where the duration contains an hour and minute part, it's |
1493 | 1511 | #. * pluralized on the hours part |
1494 | 1512 | #. |
1495 | #: src/transitPlan.js:298 | |
1513 | #: src/transitPlan.js:357 | |
1496 | 1514 | #, javascript-format |
1497 | 1515 | msgid "%d:%02d hour" |
1498 | 1516 | msgid_plural "%d:%02d hours" |
1505 | 1523 | #. * "12:00–13:03" where the placeholder %s are the actual times, |
1506 | 1524 | #. * these could be rearranged if needed. |
1507 | 1525 | #. |
1508 | #: src/transitPlan.js:651 | |
1526 | #: src/transitPlan.js:750 | |
1509 | 1527 | #, javascript-format |
1510 | 1528 | msgid "%s–%s" |
1511 | 1529 | msgstr "%s–%s" |
1659 | 1677 | msgstr "pelayanan" |
1660 | 1678 | |
1661 | 1679 | #. Translators: Accuracy of user location information |
1662 | #: src/utils.js:220 | |
1680 | #: src/utils.js:229 | |
1663 | 1681 | msgid "Unknown" |
1664 | 1682 | msgstr "Tak diketahui" |
1665 | 1683 | |
1666 | 1684 | #. Translators: Accuracy of user location information |
1667 | #: src/utils.js:223 | |
1685 | #: src/utils.js:232 | |
1668 | 1686 | msgid "Exact" |
1669 | 1687 | msgstr "Eksak" |
1670 | 1688 | |
1671 | #: src/utils.js:281 | |
1689 | #: src/utils.js:290 | |
1672 | 1690 | #, javascript-format |
1673 | 1691 | msgid "%f h" |
1674 | 1692 | msgstr "%f j" |
1675 | 1693 | |
1676 | #: src/utils.js:283 | |
1694 | #: src/utils.js:292 | |
1677 | 1695 | #, javascript-format |
1678 | 1696 | msgid "%f min" |
1679 | 1697 | msgstr "%f men" |
1680 | 1698 | |
1681 | #: src/utils.js:285 | |
1699 | #: src/utils.js:294 | |
1682 | 1700 | #, javascript-format |
1683 | 1701 | msgid "%f s" |
1684 | 1702 | msgstr "%f s" |
1685 | 1703 | |
1686 | 1704 | #. Translators: This is a distance measured in kilometers |
1687 | #: src/utils.js:296 | |
1705 | #: src/utils.js:305 | |
1688 | 1706 | #, javascript-format |
1689 | 1707 | msgid "%s km" |
1690 | 1708 | msgstr "%s km" |
1691 | 1709 | |
1692 | 1710 | #. Translators: This is a distance measured in meters |
1693 | #: src/utils.js:299 | |
1711 | #: src/utils.js:308 | |
1694 | 1712 | #, javascript-format |
1695 | 1713 | msgid "%s m" |
1696 | 1714 | msgstr "%s m" |
1697 | 1715 | |
1698 | 1716 | #. Translators: This is a distance measured in miles |
1699 | #: src/utils.js:307 | |
1717 | #: src/utils.js:316 | |
1700 | 1718 | #, javascript-format |
1701 | 1719 | msgid "%s mi" |
1702 | 1720 | msgstr "%s mil" |
1703 | 1721 | |
1704 | 1722 | #. Translators: This is a distance measured in feet |
1705 | #: src/utils.js:310 | |
1723 | #: src/utils.js:319 | |
1706 | 1724 | #, javascript-format |
1707 | 1725 | msgid "%s ft" |
1708 | 1726 | msgstr "%s kaki" |
1709 | 1727 | |
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" |
96 | 96 | this.add_main_option('version', 'v'.charCodeAt(0), GLib.OptionFlags.NONE, GLib.OptionArg.NONE, |
97 | 97 | _("Show the version of the program"), null); |
98 | 98 | |
99 | this.add_main_option('force-online', | |
100 | 0, | |
101 | GLib.OptionFlags.NONE, | |
102 | GLib.OptionArg.NONE, | |
103 | _("Ignore network availability"), | |
104 | null); | |
105 | ||
99 | 106 | this.connect('handle-local-options', (app, options) => { |
100 | 107 | if (options.contains('local')) { |
101 | 108 | let variant = options.lookup_value('local', null); |
107 | 114 | * leaving the running instance unaffected |
108 | 115 | */ |
109 | 116 | return 0; |
117 | } else if (options.contains('force-online')) { | |
118 | this._forceOnline = true; | |
110 | 119 | } |
111 | 120 | |
112 | 121 | return -1; |
114 | 123 | } |
115 | 124 | |
116 | 125 | _checkNetwork() { |
117 | this.connected = networkMonitor.connectivity === Gio.NetworkConnectivity.FULL; | |
126 | this.connected = | |
127 | this._forceOnline || | |
128 | networkMonitor.connectivity === Gio.NetworkConnectivity.FULL; | |
118 | 129 | } |
119 | 130 | |
120 | 131 | _showContact(id) { |
19 | 19 | */ |
20 | 20 | |
21 | 21 | /* 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; | |
23 | 23 | |
24 | 24 | /** |
25 | 25 | * Parses a given color component index (0: red, 1: green, 2: blue) |
143 | 143 | |
144 | 144 | _readService() { |
145 | 145 | 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(); | |
149 | 147 | |
150 | 148 | if (graphHopperGeocode) { |
151 | 149 | 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 | } |
261 | 261 | |
262 | 262 | _connectRouteSignals() { |
263 | 263 | let route = Application.routingDelegator.graphHopper.route; |
264 | let transitPlan = Application.routingDelegator.openTripPlanner.plan; | |
264 | let transitPlan = Application.routingDelegator.transitRouter.plan; | |
265 | 265 | let query = Application.routeQuery; |
266 | 266 | |
267 | 267 | route.connect('update', () => { |
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 | }; |
23 | 23 | <file>gpxShapeLayer.js</file> |
24 | 24 | <file>graphHopper.js</file> |
25 | 25 | <file>graphHopperGeocode.js</file> |
26 | <file>graphHopperTransit.js</file> | |
26 | 27 | <file>hvt.js</file> |
27 | 28 | <file>http.js</file> |
28 | 29 | <file>instructionRow.js</file> |
38 | 39 | <file>mapSource.js</file> |
39 | 40 | <file>mapView.js</file> |
40 | 41 | <file>mapWalker.js</file> |
41 | <file>openTripPlanner.js</file> | |
42 | 42 | <file>osmAccountDialog.js</file> |
43 | 43 | <file>osmConnection.js</file> |
44 | 44 | <file>osmEdit.js</file> |
90 | 90 | <file>transitOptionsPanel.js</file> |
91 | 91 | <file>transitPlan.js</file> |
92 | 92 | <file>transitPrintLayout.js</file> |
93 | <file>transitRouter.js</file> | |
93 | 94 | <file>transitRouteLabel.js</file> |
94 | 95 | <file>transitStopRow.js</file> |
96 | <file>transitTweaks.js</file> | |
95 | 97 | <file>transitWalkMarker.js</file> |
96 | 98 | <file>translations.js</file> |
97 | 99 | <file>turnPointMarker.js</file> |
110 | 112 | <file alias="geojsonvt/tile.js">tile.js</file> |
111 | 113 | <file alias="geojsonvt/transform.js">transform.js</file> |
112 | 114 | <file alias="geojsonvt/wrap.js">wrap.js</file> |
115 | <file>transitplugins/openTripPlanner.js</file> | |
116 | <file>transitplugins/resrobot.js</file> | |
113 | 117 | </gresource> |
114 | 118 | </gresources> |
46 | 46 | var OSMConnection = class OSMConnection { |
47 | 47 | |
48 | 48 | constructor() { |
49 | this._session = new Soup.Session(); | |
49 | this._session = new Soup.Session({ user_agent : 'gnome-maps/' + pkg.version }); | |
50 | 50 | |
51 | 51 | /* OAuth proxy used for making OSM uploads */ |
52 | 52 | this._callProxy = Rest.OAuthProxy.new(CONSUMER_KEY, CONSUMER_SECRET, |
69 | 69 | this.outputSortOrder = params.outputSortOrder || _DEFAULT_OUTPUT_SORT_ORDER; |
70 | 70 | |
71 | 71 | // HTTP Session Variables |
72 | this._session = new Soup.Session(); | |
72 | this._session = new Soup.Session({ user_agent : 'gnome-maps/' + pkg.version }); | |
73 | 73 | } |
74 | 74 | |
75 | 75 | addInfo(place) { |
148 | 148 | |
149 | 149 | _readService() { |
150 | 150 | 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(); | |
154 | 152 | let supportedLanguages; |
155 | 153 | |
156 | 154 | if (photon) { |
56 | 56 | _beginPrint(operation, context, data) { |
57 | 57 | let route = Application.routingDelegator.graphHopper.route; |
58 | 58 | let selectedTransitItinerary = |
59 | Application.routingDelegator.openTripPlanner.plan.selectedItinerary; | |
59 | Application.routingDelegator.transitRouter.plan.selectedItinerary; | |
60 | 60 | let width = context.get_width(); |
61 | 61 | let height = context.get_height(); |
62 | 62 |
37 | 37 | |
38 | 38 | // This one is not in GraphHopper, so choose |
39 | 39 | // a reasonably unlikely number for this |
40 | START: 10000 | |
40 | START: 10000, | |
41 | ELEVATOR: 10001, | |
42 | UTURN_LEFT: 10002, | |
43 | UTURN_RIGHT: 10003 | |
41 | 44 | }; |
42 | 45 | |
43 | 46 | /* countries/terrotories driving on the left |
238 | 238 | } |
239 | 239 | |
240 | 240 | isValid() { |
241 | if (this.filledPoints.length >= 2) | |
241 | if (this.filledPoints.length >= 2 && | |
242 | this.filledPoints.length === this.points.length) | |
242 | 243 | return true; |
243 | 244 | else |
244 | 245 | return false; |
19 | 19 | */ |
20 | 20 | |
21 | 21 | const GraphHopper = imports.graphHopper; |
22 | const OpenTripPlanner = imports.openTripPlanner; | |
22 | const TransitRouter = imports.transitRouter; | |
23 | 23 | const RouteQuery = imports.routeQuery; |
24 | 24 | |
25 | 25 | const _FALLBACK_TRANSPORTATION = RouteQuery.Transportation.PEDESTRIAN; |
31 | 31 | |
32 | 32 | this._transitRouting = false; |
33 | 33 | 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 }); | |
37 | 35 | this._query.connect('notify::points', this._onQueryChanged.bind(this)); |
38 | 36 | |
39 | 37 | /* if the query is set to transit mode when it's not available, revert |
40 | 38 | * to a fallback mode |
41 | 39 | */ |
42 | 40 | if (this._query.transportation === RouteQuery.Transportation.TRANSIT && |
43 | !this._openTripPlanner.enabled) { | |
41 | !this._transitRouter.enabled) { | |
44 | 42 | this._query.transportation = _FALLBACK_TRANSPORTATION; |
45 | 43 | } |
46 | 44 | } |
49 | 47 | return this._graphHopper; |
50 | 48 | } |
51 | 49 | |
52 | get openTripPlanner() { | |
53 | return this._openTripPlanner; | |
50 | get transitRouter() { | |
51 | return this._transitRouter; | |
54 | 52 | } |
55 | 53 | |
56 | 54 | set useTransit(useTransit) { |
59 | 57 | |
60 | 58 | reset() { |
61 | 59 | if (this._transitRouting) |
62 | this._openTripPlanner.plan.reset(); | |
60 | this._transitRouter.plan.reset(); | |
63 | 61 | else |
64 | 62 | this._graphHopper.route.reset(); |
65 | 63 | } |
67 | 65 | _onQueryChanged() { |
68 | 66 | if (this._query.isValid()) { |
69 | 67 | if (this._transitRouting) { |
70 | this._openTripPlanner.fetchFirstResults(); | |
68 | this._transitRouter.fetchFirstResults(); | |
71 | 69 | } else { |
72 | 70 | this._graphHopper.fetchRoute(this._query.filledPoints, |
73 | 71 | this._query.transportation); |
63 | 63 | 'transitItineraryListBox', |
64 | 64 | 'transitItineraryBackButton', |
65 | 65 | 'transitItineraryTimeLabel', |
66 | 'transitItineraryDurationLabel'] | |
66 | 'transitItineraryDurationLabel', | |
67 | 'transitAttributionLabel'] | |
67 | 68 | }, class Sidebar extends Gtk.Revealer { |
68 | 69 | |
69 | 70 | _init(mapView) { |
94 | 95 | this._query.addPoint(1); |
95 | 96 | this._switchRoutingMode(Application.routeQuery.transportation); |
96 | 97 | /* Enable/disable transit mode switch based on the presence of |
97 | * OpenTripPlanner. | |
98 | * public transit providers. | |
98 | 99 | * For some reason, setting visible to false in the UI file and |
99 | 100 | * dynamically setting visible false here doesn't work, maybe because |
100 | 101 | * it's part of a radio group? As a workaround, just remove the button |
101 | 102 | * instead. |
102 | 103 | */ |
103 | if (!Application.routingDelegator.openTripPlanner.enabled) | |
104 | if (!Application.routingDelegator.transitRouter.enabled) | |
104 | 105 | this._modeTransitToggle.destroy(); |
105 | 106 | } |
106 | 107 | |
146 | 147 | _switchRoutingMode(mode) { |
147 | 148 | if (mode === RouteQuery.Transportation.TRANSIT) { |
148 | 149 | Application.routingDelegator.useTransit = true; |
149 | this._linkButtonStack.visible_child_name = 'openTripPlanner'; | |
150 | this._linkButtonStack.visible_child_name = 'transit'; | |
150 | 151 | this._transitOptionsPanel.reset(); |
151 | 152 | this._transitRevealer.reveal_child = true; |
152 | 153 | } else { |
153 | 154 | Application.routingDelegator.useTransit = false; |
154 | this._linkButtonStack.visible_child_name = 'graphHopper'; | |
155 | this._linkButtonStack.visible_child_name = 'turnByTurn'; | |
155 | 156 | this._transitRevealer.reveal_child = false; |
156 | Application.routingDelegator.openTripPlanner.plan.deselectItinerary(); | |
157 | Application.routingDelegator.transitRouter.plan.deselectItinerary(); | |
157 | 158 | } |
158 | 159 | this._clearInstructions(); |
159 | 160 | } |
213 | 214 | |
214 | 215 | _initInstructionList() { |
215 | 216 | let route = Application.routingDelegator.graphHopper.route; |
216 | let transitPlan = Application.routingDelegator.openTripPlanner.plan; | |
217 | let transitPlan = Application.routingDelegator.transitRouter.plan; | |
217 | 218 | |
218 | 219 | route.connect('reset', () => { |
219 | 220 | this._clearInstructions(); |
231 | 232 | /* don't remove query points as with the turn-based routing, |
232 | 233 | * since we might get "no route" because of the time selected |
233 | 234 | * and so on */ |
235 | this._transitAttributionLabel.label = ''; | |
234 | 236 | }); |
235 | 237 | |
236 | 238 | transitPlan.connect('no-more-results', () => { |
247 | 249 | if (this._query.transportation === RouteQuery.Transportation.TRANSIT) { |
248 | 250 | this._clearTransitOverview(); |
249 | 251 | this._showTransitOverview(); |
252 | this._transitAttributionLabel.label = ''; | |
250 | 253 | } else { |
251 | 254 | this._clearInstructions(); |
252 | 255 | } |
299 | 302 | }); |
300 | 303 | |
301 | 304 | transitPlan.connect('update', () => { |
305 | this._updateTransitAttribution(); | |
302 | 306 | this._clearTransitOverview(); |
303 | 307 | this._showTransitOverview(); |
304 | 308 | this._populateTransitItineraryOverview(); |
339 | 343 | listBox.forall(listBox.remove.bind(listBox)); |
340 | 344 | } |
341 | 345 | |
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 | ||
342 | 364 | _showTransitOverview() { |
343 | let plan = Application.routingDelegator.openTripPlanner.plan; | |
365 | let plan = Application.routingDelegator.transitRouter.plan; | |
344 | 366 | |
345 | 367 | this._transitListStack.visible_child_name = 'overview'; |
346 | 368 | this._transitHeader.visible_child_name = 'options'; |
353 | 375 | } |
354 | 376 | |
355 | 377 | _populateTransitItineraryOverview() { |
356 | let plan = Application.routingDelegator.openTripPlanner.plan; | |
378 | let plan = Application.routingDelegator.transitRouter.plan; | |
357 | 379 | |
358 | 380 | plan.itineraries.forEach((itinerary) => { |
359 | 381 | let row = |
370 | 392 | } |
371 | 393 | |
372 | 394 | _onItineraryActivated(itinerary) { |
373 | let plan = Application.routingDelegator.openTripPlanner.plan; | |
395 | let plan = Application.routingDelegator.transitRouter.plan; | |
374 | 396 | |
375 | 397 | this._populateTransitItinerary(itinerary); |
376 | 398 | this._showTransitItineraryView(); |
379 | 401 | |
380 | 402 | _onMoreActivated(row) { |
381 | 403 | row.startLoading(); |
382 | Application.routingDelegator.openTripPlanner.fetchMoreResults(); | |
404 | Application.routingDelegator.transitRouter.fetchMoreResults(); | |
383 | 405 | } |
384 | 406 | |
385 | 407 | _onItineraryOverviewRowActivated(listBox, row) { |
22 | 22 | const Gtk = imports.gi.Gtk; |
23 | 23 | |
24 | 24 | 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; | |
25 | 28 | |
26 | 29 | var TransitItineraryRow = GObject.registerClass({ |
27 | 30 | Template: 'resource:///org/gnome/Maps/ui/transit-itinerary-row.ui', |
53 | 56 | */ |
54 | 57 | let useCompact = length > 2; |
55 | 58 | /* 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... | |
57 | 60 | */ |
58 | 61 | let estimatedSpace = this._calculateEstimatedSpace(); |
59 | let useContractedLabels = estimatedSpace > 28; | |
62 | let useContractedLabels = estimatedSpace > 26; | |
60 | 63 | |
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) => { | |
62 | 84 | this._summaryGrid.add(this._createLeg(leg, useCompact, |
63 | 85 | useContractedLabels)); |
64 | 86 | if (i !== length - 1) |
24 | 24 | const Gtk = imports.gi.Gtk; |
25 | 25 | |
26 | 26 | const Application = imports.application; |
27 | const HVT = imports.hvt; | |
27 | 28 | const Time = imports.time; |
28 | 29 | const TransitOptions = imports.transitOptions; |
29 | 30 | const TransitPlan = imports.transitPlan; |
45 | 46 | 'tramCheckButton', |
46 | 47 | 'trainCheckButton', |
47 | 48 | 'subwayCheckButton', |
48 | 'ferryCheckButton'] | |
49 | 'ferryCheckButton', | |
50 | 'airplaneCheckButton'] | |
49 | 51 | }, class TransitOptionsPanel extends Gtk.Grid { |
50 | 52 | |
51 | 53 | _init(params) { |
87 | 89 | this._transitTimeEntry.visible = false; |
88 | 90 | this._transitDateButton.visible = false; |
89 | 91 | this._query.arriveBy = false; |
92 | this._query.date = null; | |
90 | 93 | this._query.time = null; |
91 | this._query.date = null; | |
92 | 94 | this._timeSelected = null; |
93 | 95 | this._dateSelected = null; |
94 | 96 | } else { |
170 | 172 | let trainSelected = this._trainCheckButton.active; |
171 | 173 | let subwaySelected = this._subwayCheckButton.active; |
172 | 174 | let ferrySelected = this._ferryCheckButton.active; |
175 | let airplaneSelected = this._airplaneCheckButton.active; | |
173 | 176 | |
174 | 177 | if (busSelected && tramSelected && trainSelected && subwaySelected && |
175 | ferrySelected) { | |
178 | ferrySelected && airplaneSelected) { | |
176 | 179 | options.showAllTransitTypes = true; |
177 | 180 | } else { |
178 | 181 | if (busSelected) |
185 | 188 | options.addTransitType(TransitPlan.RouteType.SUBWAY); |
186 | 189 | if (ferrySelected) |
187 | 190 | options.addTransitType(TransitPlan.RouteType.FERRY); |
191 | if (airplaneSelected) | |
192 | options.addTransitType(HVT.AIR_SERVICE); | |
188 | 193 | } |
189 | 194 | |
190 | 195 | return options; |
95 | 95 | _init(params) { |
96 | 96 | super._init(params); |
97 | 97 | this.reset(); |
98 | this._attribution = null; | |
99 | this._attributionUrl = null; | |
98 | 100 | } |
99 | 101 | |
100 | 102 | get itineraries() { |
103 | 105 | |
104 | 106 | get selectedItinerary() { |
105 | 107 | 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; | |
106 | 124 | } |
107 | 125 | |
108 | 126 | update(itineraries) { |
111 | 129 | this.emit('update'); |
112 | 130 | } |
113 | 131 | |
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 | ||
114 | 155 | reset() { |
115 | 156 | this._itineraries = []; |
116 | 157 | this.bbox = null; |
117 | 158 | this._selectedItinerary = null; |
159 | this._attribution = null; | |
160 | this._attributionUrl = null; | |
118 | 161 | this.emit('reset'); |
119 | 162 | } |
120 | 163 | |
134 | 177 | |
135 | 178 | error(msg) { |
136 | 179 | 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.")); | |
137 | 196 | } |
138 | 197 | |
139 | 198 | _createBBox() { |
347 | 406 | get transitArrivalTimezoneOffset() { |
348 | 407 | return this._getTransitArrivalLeg().timezoneOffset; |
349 | 408 | } |
409 | ||
410 | get isWalkingOnly() { | |
411 | return this.legs.length === 1 && !this.legs[0].isTransit; | |
412 | } | |
350 | 413 | }); |
351 | 414 | |
352 | 415 | var Leg = class Leg { |
422 | 485 | |
423 | 486 | get route() { |
424 | 487 | return this._route; |
488 | } | |
489 | ||
490 | set route(route) { | |
491 | this._route = route; | |
425 | 492 | } |
426 | 493 | |
427 | 494 | // try to get a shortened route name, suitable for overview rendering |
463 | 530 | return this._routeType; |
464 | 531 | } |
465 | 532 | |
533 | set routeType(routeType) { | |
534 | this._routeType = routeType; | |
535 | } | |
536 | ||
466 | 537 | get departure() { |
467 | 538 | return this._departure; |
468 | 539 | } |
487 | 558 | return this._polyline; |
488 | 559 | } |
489 | 560 | |
561 | set polyline(polyline) { | |
562 | this._polyline = polyline; | |
563 | } | |
564 | ||
490 | 565 | get fromCoordinate() { |
491 | 566 | return this._fromCoordinate; |
492 | 567 | } |
507 | 582 | return this._intermediateStops; |
508 | 583 | } |
509 | 584 | |
585 | set intermediateStops(intermediateStops) { | |
586 | this._intermediateStops = intermediateStops; | |
587 | } | |
588 | ||
510 | 589 | get headsign() { |
511 | 590 | return this._headsign; |
512 | 591 | } |
519 | 598 | return this._distance; |
520 | 599 | } |
521 | 600 | |
601 | set distance(distance) { | |
602 | this._distance = distance; | |
603 | } | |
604 | ||
522 | 605 | get duration() { |
523 | 606 | return this._duration; |
524 | 607 | } |
543 | 626 | return this._color || DEFAULT_ROUTE_COLOR; |
544 | 627 | } |
545 | 628 | |
629 | set color(color) { | |
630 | this._color = color; | |
631 | } | |
632 | ||
546 | 633 | get textColor() { |
547 | 634 | return this._textColor || DEFAULT_ROUTE_TEXT_COLOR; |
635 | } | |
636 | ||
637 | set textColor(textColor) { | |
638 | this._textColor = textColor; | |
548 | 639 | } |
549 | 640 | |
550 | 641 | get tripShortName() { |
616 | 707 | case HVT.TAXI_SERVICE: |
617 | 708 | /* TODO: should we have a dedicated taxi icon? */ |
618 | 709 | return 'route-car-symbolic'; |
710 | ||
711 | case HVT.AIR_SERVICE: | |
712 | return 'route-transit-airplane-symbolic'; | |
713 | ||
619 | 714 | default: |
620 | 715 | /* use a fallback question mark icon in case of some future, |
621 | 716 | * for now unknown mode appears */ |
629 | 724 | |
630 | 725 | get walkingInstructions() { |
631 | 726 | return this._walkingInstructions; |
727 | } | |
728 | ||
729 | set walkingInstructions(walkingInstructions) { | |
730 | this._walkingInstructions = walkingInstructions; | |
632 | 731 | } |
633 | 732 | |
634 | 733 | /* Pretty print timing for a transit leg, set params.isStart: true when |
712 | 811 | }; |
713 | 812 | |
714 | 813 | 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; | |
716 | 823 | } |
717 | 824 | |
718 | 825 | 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 | } |
212 | 212 | return measurementSystem; |
213 | 213 | } |
214 | 214 | |
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 | ||
215 | 224 | function getAccuracyDescription(accuracy) { |
216 | 225 | switch(accuracy) { |
217 | 226 | case Geocode.LOCATION_ACCURACY_UNKNOWN: |
29 | 29 | let _soupSession = null; |
30 | 30 | function _getSoupSession() { |
31 | 31 | if (_soupSession === null) { |
32 | _soupSession = new Soup.Session (); | |
32 | _soupSession = new Soup.Session({ user_agent : 'gnome-maps/' + pkg.version }); | |
33 | 33 | } |
34 | 34 | |
35 | 35 | return _soupSession; |
42 | 42 | } |
43 | 43 | |
44 | 44 | function getArticle(wiki) { |
45 | return Soup.uri_encode(wiki.replace(/ /g, '_').split(':').splice(1).join(':'), | |
46 | '\''); | |
47 | } | |
48 | ||
49 | function getHtmlEntityEncodedArticle(wiki) { | |
45 | 50 | return GLib.markup_escape_text(wiki.split(':').splice(1).join(':'), -1); |
46 | 51 | } |
47 | 52 | |
52 | 57 | */ |
53 | 58 | function fetchArticleThumbnail(wiki, size, callback) { |
54 | 59 | let lang = getLanguage(wiki); |
55 | let title = getArticle(wiki); | |
60 | let title = getHtmlEntityEncodedArticle(wiki); | |
56 | 61 | let uri = Format.vprintf('https://%s.wikipedia.org/w/api.php', [ lang ]); |
57 | 62 | let msg = Soup.form_request_new_from_hash('GET', uri, { action: 'query', |
58 | 63 | titles: title, |