diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
index 94cc94f7179..f3de3aecfe4 100644
--- a/.github/pull_request_template.md
+++ b/.github/pull_request_template.md
@@ -4,9 +4,10 @@ To be completed by pull request submitter:
- [ ] **roadmap**: Check the [roadmap](https://github.com/orgs/opentripplanner/projects/1) for this feature or bug. If it is not already on the roadmap, PLC will discuss as part of the review process.
- [ ] **tests**: Have you added relevant test coverage? Are all the tests passing on [the continuous integration service (Travis CI)](https://github.com/opentripplanner/OpenTripPlanner/blob/master/docs/Developers-Guide.md#continuous-integration)?
- [ ] **formatting**: Have you followed the [suggested code style](https://github.com/opentripplanner/OpenTripPlanner/blob/master/docs/Developers-Guide.md#code-style)?
+- [ ] **documentation**: If you are adding a new configuration option, have you added an explanation to the [configuration documentation](docs/Configuration.md) tables and sections?
+- [ ] **changelog**: add a bullet point to the [changelog file](https://github.com/opentripplanner/OpenTripPlanner/blob/master/docs/Changelog.md) with description and link to the linked issue
To be completed by @opentripplanner/plc:
- [ ] reviews and approvals by 2 members, ideally from different organizations
-- [ ] **before merging**: add a bullet point to the [changelog file](https://github.com/opentripplanner/OpenTripPlanner/blob/master/docs/Changelog.md) with description and link to the linked issue
- [ ] **after merging**: update the relevant card on the [roadmap](https://github.com/orgs/opentripplanner/projects/1)
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index b19351f67ed..db6be4ef580 100644
--- a/.gitignore
+++ b/.gitignore
@@ -28,3 +28,4 @@ gen-py/
_site/
/otp
/otp-batch-analyst
+*.DS_Store
\ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
index 862fa8f0f31..5e65ed8f290 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,9 +1,9 @@
dist: trusty # jdk 8 not available on xenial
language: java
-# OpenTripPlanner requires Java 8 and Travis doesn't (yet) support OpenJDK 8
+
jdk:
- - oraclejdk8
-
+ - openjdk8
+
# Replace Travis's default Maven installation step with a no-op.
# This avoids redundantly pre-running 'mvn install -DskipTests' every time.
install: true
@@ -34,7 +34,7 @@ after_success:
# Secure envs are OSSRH_JIRA_USERNAME, OSSRH_JIRA_PASSWORD, GPG_KEY_NAME, GPG_PASSPHRASE
env:
global:
- - JAVA_OPTS=-Xmx2g
+ - JAVA_OPTS=-Xmx2g
# If sudo is disabled, CI runs on container based infrastructure (allows caching &c.)
sudo: false
@@ -72,4 +72,4 @@ deploy:
skip_cleanup: true
on:
repo: ibi-group/OpenTripPlanner
- all_branches: true
\ No newline at end of file
+ all_branches: true
diff --git a/README.md b/README.md
index d4e95eeee49..f842cfd3ba4 100644
--- a/README.md
+++ b/README.md
@@ -26,4 +26,4 @@ After seven years of hard work and almost 10,000 commits from over 100 contribut
## Mailing Lists
-The main forums through which the OpenTripPlanner community organizes development and provides mutual assistance are our two Google discussion groups. Changes and extensions to OTP are debated on the developers' list (opentripplanner-dev). More general questions and announcements of interest to non-developer OTP users should be directed to the opentripplanner-users list.
+The main forums through which the OpenTripPlanner community organizes development and provides mutual assistance are our two Google discussion groups. Changes and extensions to OTP are debated on the developers' list - [opentripplanner-dev](https://groups.google.com/forum/#!forum/opentripplanner-dev). More general questions and announcements of interest to non-developer OTP users should be directed to the [opentripplanner-users](https://groups.google.com/forum/#!forum/opentripplanner-users) list.
diff --git a/docs/Basic-Tutorial.md b/docs/Basic-Tutorial.md
index 9491a8ab4ea..5ce992ed635 100644
--- a/docs/Basic-Tutorial.md
+++ b/docs/Basic-Tutorial.md
@@ -80,4 +80,4 @@ There are a number of different resources available through the HTTP API. Beside
- Return all unique sequences of stops on the TriMet Green rail line: [http://localhost:8080/otp/routers/default/index/routes/TriMet:4/patterns](http://localhost:8080/otp/routers/default/index/routes/TriMet:4/patterns)
-We refer to this as the Index API. It is also documented [in the OTP HTTP API docs](http://otp-docs.ibi-transit.com/api/resource_IndexAPI.html).
+We refer to this as the Index API. It is also documented [in the OTP HTTP API docs](http://otp-docs.ibi-transit.com/api/resource_IndexAPI.html).
\ No newline at end of file
diff --git a/docs/Changelog.md b/docs/Changelog.md
index 24b2df031c1..d8ec379b5c5 100644
--- a/docs/Changelog.md
+++ b/docs/Changelog.md
@@ -1,10 +1,12 @@
# Changelog
-## 1.5 (in progress)
+## 1.5.0 (in progress)
+- Add application/x-protobuf to accepted protobuf content-types (#2839)
- Add Way Property Set for the UK (#2818)
- Fixes surefire test failure during build (#2816)
- Improve documentation for `mode` routing parameter (#2809)
+- Disable linking from already linked stops (#2372)
## 1.4 (2019-07-30)
@@ -12,11 +14,28 @@
- Improved configuration documentation
- Update onebusaway-gtfs to latest version from OBA project (#2636)
- Remove the coupling to OneBusAway GTFS within OTP's internal model by creating new classes replacing the external classes (#2494)
+- Allow OTP to search more service days for transit service (#2592)
- Allow itineraries in response to be sorted by duration (#2593)
- Add support for GTFS-flex services: flag stops, deviated-route service, and call-and-ride (#2603)
- Fix reverse optimization bug (#2653, #2411)
- Remove CarFreeAtoZ from list of deployments
- Fix bike rented though no bikes/spaces are available (#2735)
+- increase GTFS-realtime feeds size limit from 64MB to 2G (#2738)
+- Fix XML response serialization (#2685)
+- Refactor InterleavedBidirectionalHeuristic (#2671)
+- Add "Accept" headers to GTFS-RT HTTP requests (#2796)
+- Fix minor test failure against BANO geocoder (#2798)
+- Fix frequency bounds checking (#2540)
+- Fix JTS coordinate order for Polygons/Polylines (#2784)
+- Add JAXB API to allow compilation under Java 11
+- Remove dependency on Conveyal jackson2-geojson
+- Changed calculation of slope costs (#2579)
+- Replace Java built in serialization with faster Kryo (#2681)
+- Support OSM highway=razed tag (#2660)
+- Memory leak fix (#2655)
+- Add bicimad bike rental updater (#2503)
+- Add Smoove citybikes updater (#2515)
+- Switched to single license file, removing all OTP and OBA file license headers
## 1.3 (2018-08-03)
diff --git a/docs/Configuration.md b/docs/Configuration.md
index a211edc3ec6..d39a136e078 100644
--- a/docs/Configuration.md
+++ b/docs/Configuration.md
@@ -86,9 +86,10 @@ config key | description | value type | value default | notes
`elevationBucket` | If specified, download NED elevation tiles from the given AWS S3 bucket | object | null | provide an object with `accessKey`, `secretKey`, and `bucketName` for AWS S3
`readCachedElevations` | If true, reads in pre-calculated elevation data. | boolean | true | see [Elevation Data Calculation Optimizations](#elevation-data-calculation-optimizations)
`writeCachedElevations` | If true, writes the calculated elevation data. | boolean | false | see [Elevation Data Calculation Optimizations](#elevation-data-calculation-optimizations)
+`elevationUnitMultiplier` | Specify a multiplier to convert elevation units from source to meters | double | 1.0 | see [Elevation unit conversion](#elevation-unit-conversion)
`fares` | A specific fares service to use | object | null | see [fares configuration](#fares-configuration)
`osmNaming` | A custom OSM namer to use | object | null | see [custom naming](#custom-naming)
-`osmWayPropertySet` | Custom OSM way properties | string | `default` | options: `default`, `norway`
+`osmWayPropertySet` | Custom OSM way properties | string | `default` | options: `default`, `norway`, `uk`
`staticBikeRental` | Whether bike rental stations should be loaded from OSM, rather than periodically dynamically pulled from APIs | boolean | false |
`staticParkAndRide` | Whether we should create car P+R stations from OSM data | boolean | true |
`staticBikeParkAndRide` | Whether we should create bike P+R stations from OSM data | boolean | false |
@@ -140,7 +141,6 @@ yield very realistic transfer time expectations. This works particularly well in
the layering of non-intersecting ways is less prevalent. Here's an example in the Netherlands:
View Larger Map
-
When such micro-mapping data is not available, we need to rely on information from GTFS including how stops are grouped
into stations and a table of transfer timings where available. During the graph build, OTP can create preferential
connections between each pair of stops in the same station to favor in-station transfers:
@@ -244,6 +244,21 @@ order as the above-mentioned SRTM data, which is also the default for the popula
DEM files(USGS DEM) is not supported by OTP, but can be converted to GeoTIFF with tools like [GDAL](http://www.gdal.org/).
Use `gdal_merge.py -o merged.tiff *.dem` to merge a set of `dem` files into one `tif` file.
+See Interline [PlanetUtils](https://github.com/interline-io/planetutils) for a set of scripts to download, merge, and resample [Mapzen/Amazon Terrain Tiles](https://registry.opendata.aws/terrain-tiles/).
+
+### Elevation unit conversion
+
+By default, OTP expects the elevation data to use metres. However, by setting `elevationUnitMultiplier` in `build-config.json`,
+it is possible to define a multiplier that converts the elevation values from some other unit to metres.
+
+```JSON
+// build-config.json
+{
+ // Correct conversation multiplier when source data uses decimetres instead of metres
+ "elevationUnitMultiplier": 0.1
+}
+```
+
### Elevation Data Calculation Optimizations
Calculating elevations on all StreetEdges can take a dramatically long time. In a very large graph build for multiple Northeast US states, the time it took to download the elevation data and calculate all of the elevations took 5,509 seconds (roughly 1.5 hours).
@@ -333,10 +348,11 @@ It is possible to adjust how OSM data is interpreted by OpenTripPlanner when bui
OSM tags have different meanings in different countries, and how the roads in a particular country or region are tagged affects routing. As an example are roads tagged with `highway=trunk (mainly) walkable in Norway, but forbidden in some other countries. This might lead to OTP being unable to snap stops to these roads, or by giving you poor routing results for walking and biking.
You can adjust which road types that are accessible by foot, car & bicycle as well as speed limits, suitability for biking and walking.
-There are currently 2 wayPropertySets defined;
+There are currently 3 wayPropertySets defined;
- `default` which is based on California/US mapping standard
- `norway` which is adjusted to rules and speeds in Norway
+- `uk` which is adjusted to rules and speed in the UK
To add your own custom property set have a look at `org.opentripplanner.graph_builder.module.osm.NorwayWayPropertySet` and `org.opentripplanner.graph_builder.module.osm.DefaultWayPropertySet`. If you choose to mainly rely on the default rules, make sure you add your own rules first before applying the default ones. The mechanism is that for any two identical tags, OTP will use the first one.
@@ -384,6 +400,15 @@ or MultiPolygons. If any part of the geometry of a StreetEdge intersects any par
This section covers all options that can be set for each router using the `router-config.json` file.
These options can be applied by the OTP server without rebuilding the graph.
+config key | description | value type | value default | notes
+---------- | ----------- | ---------- | ------------- | -----
+`routingDefaults` | Default routing parameters, which will be applied to every request | object | | see [routing defaults](#routing-defaults)
+`timeout` | maximum time limit for route queries | double | null | units: seconds; see [timeouts](#timeouts)
+`timeouts` | when returning multiple itineraries, set different maximum time limits for the 1st, 2nd, etc. itinerary | array of doubles | `[5, 4, 2]` | units: seconds; see [timeouts](#timeouts)
+`requestLogFile` | Path to a plain-text file where requests will be logged | string | null | see [logging incoming requests](#logging-incoming-requests)
+`boardTimes` | change boarding times by mode | object | null | see [boarding and alighting times](#boarding-and-alighting-times)
+`alightTimes` | change alighting times by mode | object | null | see [boarding and alighting times](#boarding-and-alighting-times)
+`updaters` | configure real-time updaters, such as GTFS-realtime feeds | object | null | see [configuring real-time updaters](#configuring-real-time-updaters)
## Routing defaults
@@ -391,11 +416,7 @@ There are many trip planning options used in the OTP web API, and more exist
internally that are not exposed via the API. You may want to change the default value for some of these parameters,
i.e. the value which will be applied unless it is overridden in a web API request.
-A full list of them can be found in the RoutingRequest class
-
-[in the Javadoc](http://otp-docs.ibi-transit.com/JavaDoc/org/opentripplanner/routing/core/RoutingRequest.html).
-
-Any public field or setter method in this class can be given a default value using the routingDefaults section of
+A full list of them can be found in the RoutingRequest class [in the Javadoc](http://otp-docs.ibi-transit.com/JavaDoc/org/opentripplanner/routing/core/RoutingRequest.html). Any public field or setter method in this class can be given a default value using the routingDefaults section of
`router-config.json` as follows:
```JSON
@@ -413,8 +434,7 @@ Any public field or setter method in this class can be given a default value usi
The routing request parameter `mode` determines which transport modalities should be considered when calculating the list
of routes.
-Some modes (mostly bicycle and car) also have optional qualifiers `RENT`, `HAIL` or `PARK` to specify if vehicles are to be parked at a station or rented. In theory
-this can also apply to other modes but makes sense only in select cases which are listed below.
+Some modes (mostly bicycle and car) also have optional qualifiers `RENT`, `HAIL` or `PARK` to specify if vehicles are to be parked at a station or rented. In theory this can also apply to other modes but makes sense only in select cases which are listed below.
Whether a transport mode is available highly depends on the input feeds (GTFS, OSM, bike sharing feeds) and the graph building options supplied to OTP.
@@ -637,7 +657,7 @@ The generic KML needs to be in format like
```
-### Configuration
+### Configuring real-time updaters
Real-time data can be provided using either a pull or push system. In a pull configuration, the GTFS-RT consumer polls the
real-time provider over HTTP. That is to say, OTP fetches a file from a web server every few minutes. In the push
@@ -724,15 +744,6 @@ connect to a network resource is the `url` field.
// Streaming differential GTFS-RT TripUpdates over websockets
{
"type": "websocket-gtfs-rt-updater"
- },
-
- // OpenTraffic data
- {
- "type": "opentraffic-updater",
- "frequencySec": -1,
- // relative to OTP's working directory, where is traffic data stored.
- // Should have subdirectories z/x/y.traffic.pbf (i.e. a tile tree of traffic tiles)
- "tileDirectory": "traffic"
}
]
}
diff --git a/docs/Deployments.md b/docs/Deployments.md
index 70c975d404f..bca9d49259e 100644
--- a/docs/Deployments.md
+++ b/docs/Deployments.md
@@ -12,12 +12,12 @@ The following are known deployments of OTP in a government- or agency-sponsored
* **Atlanta, Georgia** The Metropolitan Atlanta Rapid Transit Authority's (MARTA) [trip planner](http://itsmarta.com/planatrip.aspx) and the Atlanta region's transit information hub [atltransit.org](https://atltransit.org/) both use OTP to power their website trip planners.
* **Boston, Massachusetts** The [Massachusetts Bay Transportation Authority trip planner](https://www.mbta.com/trip-planner).
* **Seattle, Washington** The [Sound Transit Trip Planner](https://www.soundtransit.org/tripplanner) is based on OTP. OTP also powers the trip planning feature of the [OneBusAway native apps](http://onebusaway.org/) in the Puget Sound region. Technical details are [here](https://github.com/OneBusAway/onebusaway-android/blob/master/SYSTEM_ARCHITECTURE.md#add-trip-planning-andor-bike-share-optional).
-* **Arlington, Virginia** The [commute planning site](http://www.carfreeatoz.com/) for the Washington, DC metropolitan area depends on OpenTripPlanner to weigh the costs and benefits of various travel options, making use of profile routing.
* **Tampa, Florida** Hillsoborough Area Regional Transit uses an OpenTripPlanner server to power the trip planning feature of the [OneBusAway native apps](http://onebusaway.org/) in their region. Technical details are [here](https://github.com/OneBusAway/onebusaway-android/blob/master/SYSTEM_ARCHITECTURE.md#add-trip-planning-andor-bike-share-optional).
* [**Piemonte Region, Italy**](https://map.muoversinpiemonte.it/#planner) and the [**City of Torino**](https://www.muoversiatorino.it/) built on OpenTripPlanner by [5T](http://www.5t.torino.it/).
* [**Valencia, Spain**](http://www.emtvalencia.es/geoportal/?lang=en_otp) from the Municipal Transport Company of Valencia S.A.U.
* [**Grenoble, France**](http://www.metromobilite.fr/) from SMTC, Grenoble Alpes métropole, l'État Français, the Rhône-alpes region, the Isère council and the City of Grenoble.
* **Rennes, France** where the STAR network provides an OTP client for [iOS](https://itunes.apple.com/us/app/starbusmetro/id899970416?mt=8), [Android](https://play.google.com/store/apps/details?id=com.bookbeo.starbusmetro), Windows Phone et Web.
+* **Alençon, France** integrated urban and school bus network [planner from Réunir Alençon](https://altobus.com/mon-itineraire/).
* [**Poznań, Poland**](http://ztm.poznan.pl/#planner) from Urban Transport Authority of Poznań (ZTM Poznan).
* **Trento Province, Italy** - [ViaggiaTrento](https://play.google.com/store/apps/details?id=eu.trentorise.smartcampus.viaggiatrento) and [ViaggiaRovereto](https://play.google.com/store/apps/details?id=eu.trentorise.smartcampus.viaggiarovereto)
were implemented as part of the [SmartCampus Project](http://www.smartcampuslab.it), a research project founded by [TrentoRise](http://trentorise.eu), [UNITN](http://www.unitn.it), and [FBK](http://www.fbk.eu).
diff --git a/docs/Developers-Guide.md b/docs/Developers-Guide.md
index e01436b9258..e63e1a86d6c 100644
--- a/docs/Developers-Guide.md
+++ b/docs/Developers-Guide.md
@@ -46,6 +46,11 @@ There are several ways to get involved:
* Submit patches. If you're not yet a committer, please provide patches as pull requests citing the relevant issue.
Even when you do have push access to the repository, pull requests are a good way to get feedback on changes.
+### Branches and Branch Protection
+
+As of January 2019, we have begun work on OTP 2.x and are using a Git branching model derived from [Gitflow](https://nvie.com/posts/a-successful-git-branching-model/). All development will occur on the `dev-1.x` and `dev-2.x` branches. Only release commits setting the Maven artifact version to a non-snapshot number should be pushed to the `master` branch of OTP. All other changes to master should result from fast-forward merges of a Github pull request from the `dev-1.x` branch. In turn, all changes to `dev-1.x` should result from a fast-forward merge of a Github pull request for a single feature, fix, or other change. These pull requests are subject to code review. We require two pull request approvals from OTP leadership committee members or designated code reviewers from two different organizations. We also have validation rules ensuring that the code compiles and all tests pass before pull requests can be merged.
+
+The `dev-2.x` branch is managed similarly to `dev-1.x` but because it's rapidly changing experimental code worked on by relatively few people, we require only one pull request approval from a different organization than the author. Merges will not occur into `master` from `dev-2.x` until that branch is sufficiently advanced and receives approval from the OTP project leadership committee.
### Issues and commits
@@ -185,7 +190,7 @@ are not included in mainline OTP.
OpenTripPlanner uses the same code formatting and style as the [GeoTools](http://www.geotools.org/) and
[GeoServer](http://geoserver.org) projects. It's a minor variant of the
-[Sun coding convention](http://www.oracle.com/technetwork/java/codeconv-138413.html). Notably, **we do not use tabs**
+[Sun coding convention](https://www.oracle.com/technetwork/java/codeconventions-150003.pdf). Notably, **we do not use tabs**
for indentation and we allow for lines up to 100 characters wide.
The Eclipse formatter configuration supplied by the GeoTools project allows comments up to 150 characters wide.
@@ -242,61 +247,125 @@ is pushed to the main OpenTripPlanner repository on GitHub, this server will com
## Release Process
-This section serves as a checklist for the person responsible for performing releases (currently Andrew Byrd).
-Note that much of this mimics the actions taken by the Maven release plugin. Based on past experience,
-the release plugin can fail at various points in the process leaving the repo in a confusing state.
-Taking each action manually is more annoying but keeps eyes on each step and is less prone to failure.
+This section serves as a checklist for the person performing releases. Note that much of this mimics
+the actions taken by the Maven release plugin. Based on past experience, the Maven release plugin can
+fail at various points in the process leaving the repo in a confusing state. Taking each action
+manually is more tedious, but keeps eyes on each step and is less prone to failure. Releases are
+performed off the master branch, and are tagged with git annotated tags.
-* Check that you are at the HEAD of master branch with no uncommitted changes
+* Check that your local copy of the dev branch is up to date with no uncommitted changes
* `git status`
+ * `git checkout dev-1.x`
* `git clean -df`
* `git pull`
* Verify that all dependencies in the POM are non-SNAPSHOT versions
+ * Currently we do have one SNAPSHOT dependency on `crosby.binary.osmpbf` which we are working to eliminate
* Update `docs/Changelog.md`
- * Items should have been added/updated as each pull request was merged
- * Update the header at the top of the list from x.y.z-SNAPSHOT to just x.y.z (current date)
- * Add a new section header at the top of the list for the next iteration: x.y+1.0-SNAPSHOT
+ * Lines should have been added or updated as each pull request was merged
+ * If you suspect any changes are not reflected in the Changelog, review the commit log and add any missing items
+ * Update the header at the top of the list from `x.y.z-SNAPSHOT` to just `x.y.z (current date)`
* Check in any changes, and push to Github
* Check on Travis that the build is currently passing
- * https://travis-ci.org/opentripplanner/OpenTripPlanner/builds
+ * [Link to OTP builds on Travis CI](https://travis-ci.org/opentripplanner/OpenTripPlanner/builds)
+* Switch to the HEAD of master branch, and ensure it's up to date with no uncommitted changes
+ * `git checkout master`
+ * `git status`
+ * `git clean -df`
+ * `git pull`
+* Merge the dev branch into master
+ * `git merge dev-1.x`
* Bump the SNAPSHOT version in the POM to the release version
- * Edit version in POM, removing SNAPSHOT and increasing version numbers as needed (semantic-release)
+ * Edit version in POM, removing SNAPSHOT and increasing version numbers as needed (following semantic versioning)
* `git add pom.xml`
* `git commit -m "prepare release x.y.z"`
* Run a test build of the release locally, without deploying it
- * `mvn clean package site`
- * This will sign the Maven artifacts so you need the signing certificate set up
+ * `mvn clean install site`
+ * The `install` goal will sign the Maven artifacts so you need the GPG signing certificate set up
+ * You can also use the `package` goal instead of the `install` goal to avoid signing if you don't have the GPG certificate installed.
* All tests should pass
- * This will also create Enunciate API docs and Javadoc containing the correct non-snapshot version number
-* Deploy documentation to AWS S3
+ * This build will also create Enunciate API docs and Javadoc with the correct non-snapshot version number
+* Deploy the documentation to AWS S3
* You have to do this right after the test release build to ensure the right version number in the docs
* You will need AWSCLI tools (`sudo pip install -U awscli`)
+ * You will need AWS credentials with write access to the bucket `s3://dev.opentripplanner.org`
* `aws s3 cp --recursive target/site/apidocs s3://dev.opentripplanner.org/javadoc/x.y.z --acl public-read`
* `aws s3 cp --recursive target/site/enunciate/apidocs s3://dev.opentripplanner.org/apidoc/x.y.z --acl public-read`
- * Check that docs are readable and show the correct version via the Javadoc landing page at `dev.opentripplanner.org`
-* Finally, tag and push this release to make it official
- * `git tag -a vX.Y.Z -m "release X.Y.X"`
- * `git push origin vX.Y.Z` (will trigger a Travis CI build and deployment)
-* Begin development on the next iteration
- * Edit minor version in POM to X.(Y+1).0-SNAPSHOT
- * `git add pom.xml`
- * `git commit -m "Prepare next development iteration X.(Y+1).0-SNAPSHOT"`
+ * Check that docs are readable and show the correct version via the [development documentation landing page](http://dev.opentripplanner.org).
+* Finally, if everything looks good, tag and push this release to make it official and trigger deployment
+ * `git tag -a vX.Y.Z -m "release X.Y.Z"`
+ * `git push origin vX.Y.Z`
+ * Pushing the tag will trigger a Travis CI build and deployment of release Maven artifacts
+ * Note that **only one** commit may have a particular non-snapshot version in the POM (this is the commit that
+ should be tagged as the release)
+* Set up next development iteration
+ * Add a new section header to `docs/Changelog.md` like `x.y+1.0-SNAPSHOT (in progress)`
+ * Edit minor version in `pom.xml` to `x.y+1.0-SNAPSHOT`
+ * `git add pom.xml docs/Changelog.md`
+ * `git commit -m "Prepare next development iteration x.y+1.0-SNAPSHOT"`
* `git push`
* Check that Travis CI build of the release tag succeeded
- * https://travis-ci.org/opentripplanner/OpenTripPlanner/builds
- * Check the end of the build log to make sure the Maven artifacts were uploaded
-* Check that Maven artifact appears on Maven Central
- * OTP artifacts are at http://repo2.maven.org/maven2/org/opentripplanner/otp/1.3.0/
- * This can sometimes take half an hour after Travis uploads the artifacts
+ * [Link to OTP builds on Travis CI](https://travis-ci.org/opentripplanner/OpenTripPlanner/builds)
+ * Check the end of the build log to make sure the Maven artifacts were staged for release
+* Check that Maven artifact appears on Maven Central (deployment succeeded)
+ * [Directory listing of OTP releases on Maven Central](https://repo1.maven.org/maven2/org/opentripplanner/otp/)
+ * It may take a while (half an hour) for releases to show up in the central repo after Travis uploads the artifacts
+* Merge master back into dev (to sync up the Maven artifact version from the POM)
+ * `git checkout dev-1.x`
+ * `git merge master`
+ * `git push`
+* Make sure the main documentation is built
+ * For some reason it doesn't always build automatically
+ * Go to [builds of docs.opentripplanner.org](http://readthedocs.org/projects/opentripplanner/builds/)
+ * Click "build version: latest"
* Email the OTP dev and users mailing lists
+ * Mention the new version number.
+ * Provide links to the new developer documentation.
+ * Provide links to the artifacts directory on Maven Central.
+ * Trigger build of latest OTP documentation on Readthedocs.
+
+## Additional Information on Releases
+
+OpenTripPlanner is released as Maven artifacts to Maven Central. These include compiled and source code JARs as well as
+a "shaded" JAR containing all dependencies, allowing stand-alone usage. This release process is handled by the
+Sonatype Nexus Staging plugin, configured in the OpenTripPlanner POM. Typically this final Maven deployment action is
+performed automatically when the Travis CI build succeeds in building a non-SNAPSHOT version.
+
+### Artifact Signing
+
+Maven release artifacts must be digitally signed to prove their origin. This is a safeguard against compromised code
+from a malicious third party being disguised as a trusted library.
+
+The OTP artifact signing key was created by Conveyal. We export only that signing subkey, with our company's main key
+blanked out. Therefore, even if someone managed to acquire the decrypted key file and the associated GPG passphrase,
+they would not have the main key. We could deactivate the signing key and create a new one, without the main key being
+compromised.
+
+The exported signing key is present in the root of the git repo as the encrypted file `maven-artifact-signing-key.asc.enc`.
+When building a tagged release, Travis CI will decrypt this file and import it into GPG on the build machine. The signing
+key ID and GPG passphrase are also present as encrypted environment variables in the Travis configuration YAML. This only
+happens on code from non-fork, non-pull-request commits, ensuring that no unreviewed third-party code has access to
+these files or variables.
+
+OpenTripPlanner's POM is set up to sign artifacts in the verify phase, which means signing will happen for the `install`
+and `deploy` targets, but not the `package` target.
+When performing a local test build, if you do `mvn clean install site` it will test the signing process.
+If you do not have the certificate installed, you can instead to `mvn clean package site` to bypass signing, but this
+provides less certainty that everything is set up correctly for the CI-driven final release.
+
+### Documentation Build and Hosting
+
+Three kinds of documentation are built for OTP, all based on information present in the OTP repo itself.
+
+The REST API docs are built by Enunciate from the OTP REST interface. My sense is that this auto-generated
+documentation has become harder to read and less useful over time, perhaps because of incorrect handling of REST
+parameters inherited from superclasses.
+
+The Javadoc is built from Javadoc comments in the source code itself.
-TODO Elaborate on:
-- Signing Certificate
-- AWS IAM credentials
-- Sonatype-OSS staging / Maven artifact deployment
-- Merging dev branches into master ("git flow")
-- Release from master should happen _automatically_ once version is changed
-- ONLY one commit must have a particular non-snapshot commit (the tagged commit that constitutes the release)
-- Add final steps: `git checkout dev-1.x`; `git merge master` (primarily to update the POM version)
+The main OTP user documentation is built from Markdown files in the `/docs` directory of the repo.
+The REST API docs and Javadoc are built by Maven, then uploaded manually to AWS S3, from which they are served as a web
+page at dev.opentripplanner.org. The main OTP user documentation is built by Readthedocs and served at docs.opentripplanner.org.
+Upload to the S3 bucket `dev.opentripplanner.org` requires AWS IAM credentials that can be created by Conveyal (which
+owns the `dev.opentripplanner.org` bucket).
\ No newline at end of file
diff --git a/docs/Flex.md b/docs/Flex.md
new file mode 100644
index 00000000000..b48bb6b0bb2
--- /dev/null
+++ b/docs/Flex.md
@@ -0,0 +1,70 @@
+# GTFS-Flex routing
+
+Many agencies run flexible services to complement their fixed-route service. "Flexible" service does
+not follow a strict timetable or route. It may include any of the following features: boardings
+or alightings outside its scheduled timetable and route; booking and scheduling in advance; or
+transit parameters which depend on customer requests ("demand-responsive transit" or DRT). These
+services are typically used in rural areas or for mobility-impaired riders.
+
+A GTFS extension called [GTFS-Flex](https://github.com/MobilityData/gtfs-flex/blob/master/spec/reference.md) defines
+how to model some kinds of flexible transit. A subset of GTFS-Flex has been implemented in
+OpenTripPlanner as part of US DOT's [Mobility-on-Demand Sandbox Grant](https://www.transit.dot.gov/research-innovation/fiscal-year-2016-mobility-demand-mod-sandbox-program-projects).
+
+In particular, OTP now has support for these modes of GTFS-Flex:
+
+- "flag stops", in which a passenger can flag down the a vehicle along its route to board, or
+alight in between stops
+- "deviated-route service", in which a vehicle can deviate from its route within an area or radius to
+do a dropoff or pickup
+- "call-and-ride", which is an entirely deviated, point-to-point segment.
+
+These modes can co-exist with fixed-route transit, and with each other. For example, some agencies
+have fixed-route services that start in urban areas, where passengers must board at designated
+stops, but end in rural areas where passengers can board and alight wherever they please. A
+fixed-route service may terminate in an defined area where it can drop off passengers anywhere --
+or have such an area at the beginning or middle of its route. A vehicle may be able to deviate a
+certain radius outside its scheduled route to pick up or drop off passengers. If both a pickup and
+dropoff occur in between scheduled timepoints, from the passenger's perspective, the service may
+look like a call-and-ride trip. Other call-and-ride services may operate more like taxis, in which
+all rides are independently scheduled.
+
+## Configuration
+
+In order to use flexible routing, an OTP graph must be built with a GTFS-Flex dataset and
+OpenStreetMap data. The GTFS data must include `shapes.txt`.
+
+In addition, the parameter `useFlexService: true` must be added to `router-config.json`.
+
+A number of routing parameters can be used to control aspects of flexible service. These parameters
+typically change the relative cost of using various flexible services relative to fixed-route
+transit. All flex-related parameters begin with the prefix "flex" and can be found in the Javadocs
+for `RoutingRequest.java`.
+
+The following example `router-config.json` enables flexible routing and sets some parameters:
+
+ {
+ "useFlexService": true,
+ "routingDefaults": {
+ "flexCallAndRideReluctance": 3,
+ "flexMaxCallAndRideSeconds": 7200,
+ "flexFlagStopExtraPenalty": 180
+ }
+ }
+
+## Implementation
+
+The general approach of the GTFS-Flex implementation is as follows: prior to the main graph search,
+special searches are run around the origin and destination to discover possible flexible options.
+One search is with the WALK mode, to find flag stops, and the other is in the CAR mode, to find
+deviated-route and call-and-ride options. These searches result in the creation of temporary,
+request-specific vertices and edges. Then, the graph search proceeds as normal. Temporary graph
+structures are disposed at the end of the request's lifecycle.
+
+For flag stops and deviated-route service, timepoints in between scheduled locations are determined
+via linear interpolation. For example, say a particular trip departs stop A at 9:00am and arrives
+at stop B at 9:30am. A passenger would be able to board 20% of the way in between stop A and stop B
+at 9:06am, since 20% of 30 minutes is 6 minutes.
+
+For deviated-route service and call-and-ride service, the most pessimistic assumptions of vehicle
+travel time are used -- e.g. vehicle travel time is calculated via the `drt_max_travel_time`
+formula in the GTFS-Flex (see the spec [here](https://github.com/MobilityData/gtfs-flex/blob/master/spec/reference.md#defining-service-parameters)).
\ No newline at end of file
diff --git a/docs/Governance.md b/docs/Governance.md
index 9c54a98bdef..d7cc14a7dfa 100644
--- a/docs/Governance.md
+++ b/docs/Governance.md
@@ -2,17 +2,19 @@
OpenTripPlanner is a member project of the [Software Freedom Conservancy](https://sfconservancy.org/members/current/). Development of OpenTripPlanner is managed by a Project Leadership Committee (PLC) which makes decisions by simple majority vote. The current members of this committee are (in alphabetical order):
-|Name | Affiliation |
-|-----|-------------|
+| Name | Affiliation |
+|-------------------|-----------------------------|
| Sean Barbeau | University of South Florida |
-| Torbjørn Barslett | Ruter Oslo |
-| Sheldon Brown | Cambridge Systematics |
-| Andrew Byrd | PlannerStack Foundation |
-| Drew Dara-Abrams | Interline |
-| David Emory | Conveyal |
-| Tuukka Hastrup | Helsingin Seudun Liikenne |
-| Frank Purcell | TriMet |
-| David Turner | ex-OpenPlans |
+| Sheldon Brown | Cambridge Systematics |
+| Andrew Byrd | Conveyal |
+| Thomas Craig | Trillium |
+| Drew Dara-Abrams | Interline |
+| David Emory | MARTA (Atlanta, Georgia) |
+| Thomas Gran | Ruter & Entur (Norway) |
+| Tuukka Hastrup | Maanteeamet (Estonia) |
+| Frank Purcell | TriMet (Portland, Oregon) |
+| Evan Siroky | IBI Group |
+| David Turner | ex-OpenPlans |
The PLC holds a quarterly video conference on the first Thursday of June, September, December, and March. An agenda is prepared as a collaborative document in advance of each quarterly meeting. These meetings are held at 9AM US Pacific time to accommodate members in the US Pacific, US Eastern, and Central European time zones.
diff --git a/docs/Intermediate-Tutorial.md b/docs/Intermediate-Tutorial.md
index 32fb1b0679c..6c2a2674bb8 100644
--- a/docs/Intermediate-Tutorial.md
+++ b/docs/Intermediate-Tutorial.md
@@ -17,7 +17,7 @@ The above query makes a request to the locally running server `http://localhost:
- **mode=TRANSIT,WALK**, transport modes to consider, in this case a combination of walking and transit
- **maxWalkDistance=500**, the maximum distance in meters that you are willing to walk
-If you run this query as is you will very likely get a response saying that a trip has not been found. Try changing the fromPlace, toPlace, time and date parameters to match the location and time period of the data you loaded when you initially built the graph. More (optional) parameters for the planner resource are documented [here](http://otp-docs.ibi-transit.com/api/resource_PlannerResource.html).
+If you run this query as is you will very likely get a response saying that a trip has not been found. Try changing the fromPlace, toPlace, time and date parameters to match the location and time period of the data you loaded when you initially built the graph. More (optional) parameters for the planner resource are documented [here](http://otp-docs.ibi-transit.com/api/resource_PlannerResource.html).
## Calculating travel time isochrones
@@ -45,4 +45,4 @@ See the [configuration](Configuration.md) page for configuration settings which
It may also be helpful to try running the OTP .jar file with the `--help` option for a full list of command line parameters.
-Full documentation for the API is available [here](http://otp-docs.ibi-transit.com/api/index.html#resources).
+Full documentation for the API is available [here](http://otp-docs.ibi-transit.com/api/index.html#resources).
\ No newline at end of file
diff --git a/docs/index.md b/docs/index.md
index 9eaf2fc5322..1766413ad42 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -7,7 +7,7 @@ _This documentation is targeted primarily at the OTP development community and m
**OpenTripPlanner** (OTP) is an open source multi-modal trip planner, which runs on Linux, Mac, Windows, or potentially any platform with a Java virtual machine. OTP is released under the [LGPL license](https://opensource.org/licenses/LGPL-3.0). The code is under active development with a variety of [deployments](Deployments) around the world.
-If you want to get started right away running your own OTP instance, the best place to start is the [Basic Usage](Basic-Usage) page.
+If you want to get started right away running your own OTP instance, the best place to start is the [Basic Tutorial](Basic-Tutorial) page.
## External Technical Documentation
diff --git a/mkdocs.yml b/mkdocs.yml
index f66d1035095..4fc632c6a59 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -32,6 +32,7 @@ pages:
- Scripting: 'Scripting.md'
- Security: 'Security.md'
- Troubleshooting: 'Troubleshooting-Routing.md'
+ - GTFS-Flex Routing: 'Flex.md'
- Development:
- "Developers' Guide": 'Developers-Guide.md'
- Architecture: 'Architecture.md'
diff --git a/pom.xml b/pom.xml
index 8353c365bab..5f51651385d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -8,7 +8,7 @@
org.opentripplannerotp
- 1.4.0-SNAPSHOT
+ 1.5.0-SNAPSHOTjar
@@ -78,10 +78,11 @@
- 10.5
- 2.5.3
+ 20.1
+ 16.5
+ 2.9.7
+ 2.18UTF-8
- 1.9.39
@@ -261,6 +262,7 @@
2.21.0-Xmx2G
+ -Dfile.encoding=UTF-8false
@@ -415,15 +417,10 @@
check central first to avoid a lot of not found warningshttps://repo1.maven.org/maven2
-
- download.java.net
- Java.net Repository for Maven
- http://download.java.net/maven/2/
- osgeoOpen Source Geospatial Foundation Repository
- http://download.osgeo.org/webdav/geotools/
+ https://download.osgeo.org/webdav/geotools/axis
@@ -431,9 +428,9 @@
https://people.apache.org/repo/m1-ibiblio-rsync-repository/org.apache.axis2/
- sonatype-oss
- Sonatype OSS
- https://oss.sonatype.org/content/repositories/snapshots/
+ sonatype-oss
+ Sonatype OSS
+ https://oss.sonatype.org/content/repositories/snapshots/conveyal
@@ -448,28 +445,6 @@
-
-
- com.amazonaws
- aws-java-sdk-s3
- ${aws.version}
-
-
- com.amazonaws
- aws-java-sdk-ec2
- ${aws.version}
-
-
- com.amazonaws
- aws-java-sdk-sqs
- ${aws.version}
-
-
-
- de.ruedigermoeller
- fst
- 2.34
- ch.qos.logback
@@ -482,26 +457,6 @@
jul-to-slf4j1.7.6
-
-
-
- org.slf4j
- jcl-over-slf4j
- 1.7.6
-
-
-
- com.google.guava
- guava
- 18.0
-
-
-
- net.sf.trove4j
- trove4j
- 3.0.3
-
-
@@ -541,9 +496,14 @@
org.geotools
- gt-wfs
+ gt-opengis${geotools.version}
+
+ org.geotools
+ gt-wfs
+ ${geotools.wfs.version}
+ org.geotools
@@ -556,7 +516,7 @@
de.grundid.opendatalabgeojson-jackson1.2
-
@@ -571,12 +531,6 @@
-
- com.conveyal
- jackson2-geojson
- 0.8
-
-
junit
@@ -598,33 +552,40 @@
test
+
+
+
+ com.conveyal
+ kryo-tools
+ 1.2.0
+ org.glassfish.jersey.corejersey-server
- 2.18
+ ${jersey.version}org.glassfish.jersey.mediajersey-media-multipart
- 2.18
+ ${jersey.version}org.glassfish.jersey.containersjersey-container-grizzly2-http
- 2.18
+ ${jersey.version}
- com.fasterxml.jackson.core
- jackson-core
+ com.fasterxml.jackson.core
+ jackson-core${jackson.version}
- com.fasterxml.jackson.core
- jackson-databind
+ com.fasterxml.jackson.core
+ jackson-databind${jackson.version}
@@ -719,7 +680,7 @@
org.apache.commonscommons-compress
- 1.17
+ 1.18commons-discovery
@@ -756,9 +717,9 @@
4.5.5
- commons-codec
- commons-codec
- 1.11
+ commons-codec
+ commons-codec
+ 1.11org.apache.commons
@@ -792,45 +753,17 @@
graphql-java2.2.0
+
+
+ javax.xml.bind
+ jaxb-api
+ 2.3.1
+ bsfbsf2.4.0
-
-
-
- io.opentraffic
- traffic-engine
- 0.2
-
-
-
- com.conveyal
- r5
- 4.1.0
-
-
- slf4j-simple
- org.slf4j
-
-
-
-
diff --git a/src/main/java/com/google/transit/realtime/GtfsRealtime.java b/src/main/java/com/google/transit/realtime/GtfsRealtime.java
index 1f60cafd5bc..4c487832297 100644
--- a/src/main/java/com/google/transit/realtime/GtfsRealtime.java
+++ b/src/main/java/com/google/transit/realtime/GtfsRealtime.java
@@ -17,11 +17,13 @@
*
*
*
Enum value TripDescriptor.ScheduleRelationship.MODIFIED = 5
* Version 2.5.0 of protoc.exe has been used to generate this file.
*
*/
+
public final class GtfsRealtime {
private GtfsRealtime() {}
public static void registerAllExtensions(
@@ -221,6 +223,10 @@ public FeedMessage parsePartialFrom(
com.google.protobuf.CodedInputStream input,
com.google.protobuf.ExtensionRegistryLite extensionRegistry)
throws com.google.protobuf.InvalidProtocolBufferException {
+ // Increase input stream size limit to 2G (instead of the 64M default).
+ // Mimics a change that was supposed to be released in protobuf 2.7.0,
+ // see https://git.io/fjfg5.
+ input.setSizeLimit(Integer.MAX_VALUE);
return new FeedMessage(input, extensionRegistry);
}
};
diff --git a/src/main/java/org/opensphere/geometry/algorithm/ConcaveHull.java b/src/main/java/org/opensphere/geometry/algorithm/ConcaveHull.java
index 43819f21311..0b2bdef3da6 100644
--- a/src/main/java/org/opensphere/geometry/algorithm/ConcaveHull.java
+++ b/src/main/java/org/opensphere/geometry/algorithm/ConcaveHull.java
@@ -13,22 +13,22 @@
import org.opensphere.geometry.triangulation.model.Triangle;
import org.opensphere.geometry.triangulation.model.Vertex;
-import com.vividsolutions.jts.geom.Coordinate;
-import com.vividsolutions.jts.geom.Geometry;
-import com.vividsolutions.jts.geom.GeometryCollection;
-import com.vividsolutions.jts.geom.GeometryFactory;
-import com.vividsolutions.jts.geom.LineSegment;
-import com.vividsolutions.jts.geom.LineString;
-import com.vividsolutions.jts.geom.LinearRing;
-import com.vividsolutions.jts.geom.Point;
-import com.vividsolutions.jts.geom.Polygon;
-import com.vividsolutions.jts.geom.impl.CoordinateArraySequence;
-import com.vividsolutions.jts.operation.linemerge.LineMerger;
-import com.vividsolutions.jts.triangulate.ConformingDelaunayTriangulationBuilder;
-import com.vividsolutions.jts.triangulate.quadedge.QuadEdge;
-import com.vividsolutions.jts.triangulate.quadedge.QuadEdgeSubdivision;
-import com.vividsolutions.jts.triangulate.quadedge.QuadEdgeTriangle;
-import com.vividsolutions.jts.util.UniqueCoordinateArrayFilter;
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.Geometry;
+import org.locationtech.jts.geom.GeometryCollection;
+import org.locationtech.jts.geom.GeometryFactory;
+import org.locationtech.jts.geom.LineSegment;
+import org.locationtech.jts.geom.LineString;
+import org.locationtech.jts.geom.LinearRing;
+import org.locationtech.jts.geom.Point;
+import org.locationtech.jts.geom.Polygon;
+import org.locationtech.jts.geom.impl.CoordinateArraySequence;
+import org.locationtech.jts.operation.linemerge.LineMerger;
+import org.locationtech.jts.triangulate.ConformingDelaunayTriangulationBuilder;
+import org.locationtech.jts.triangulate.quadedge.QuadEdge;
+import org.locationtech.jts.triangulate.quadedge.QuadEdgeSubdivision;
+import org.locationtech.jts.triangulate.quadedge.QuadEdgeTriangle;
+import org.locationtech.jts.util.UniqueCoordinateArrayFilter;
/**
* Computes a concave hull of a {@link Geometry} which is
@@ -179,11 +179,11 @@ private Geometry concaveHull() {
Collection quadEdges = qes.getEdges();
List qeTriangles = QuadEdgeTriangle.createOn(qes);
- Collection qeVertices =
+ Collection qeVertices =
qes.getVertices(false);
int iV = 0;
- for (com.vividsolutions.jts.triangulate.quadedge.Vertex v : qeVertices) {
+ for (org.locationtech.jts.triangulate.quadedge.Vertex v : qeVertices) {
this.coordinates.put(v.getCoordinate(), iV);
this.vertices.put(iV, new Vertex(iV, v.getCoordinate()));
iV++;
diff --git a/src/main/java/org/opensphere/geometry/triangulation/DoubleComparator.java b/src/main/java/org/opensphere/geometry/triangulation/DoubleComparator.java
index 5aa1405b035..149312fa35c 100644
--- a/src/main/java/org/opensphere/geometry/triangulation/DoubleComparator.java
+++ b/src/main/java/org/opensphere/geometry/triangulation/DoubleComparator.java
@@ -4,7 +4,7 @@
import java.util.Comparator;
import java.util.Map;
-import com.vividsolutions.jts.triangulate.quadedge.QuadEdge;
+import org.locationtech.jts.triangulate.quadedge.QuadEdge;
/**
* Comparator of a map containing QuadEdge as key
diff --git a/src/main/java/org/opensphere/geometry/triangulation/model/Edge.java b/src/main/java/org/opensphere/geometry/triangulation/model/Edge.java
index 8352673939d..4fd5035f6d5 100644
--- a/src/main/java/org/opensphere/geometry/triangulation/model/Edge.java
+++ b/src/main/java/org/opensphere/geometry/triangulation/model/Edge.java
@@ -4,7 +4,7 @@
import java.util.ArrayList;
import java.util.List;
-import com.vividsolutions.jts.geom.LineSegment;
+import org.locationtech.jts.geom.LineSegment;
/**
* Edge.
diff --git a/src/main/java/org/opensphere/geometry/triangulation/model/Vertex.java b/src/main/java/org/opensphere/geometry/triangulation/model/Vertex.java
index 5018bfac078..32c2ea05e0f 100644
--- a/src/main/java/org/opensphere/geometry/triangulation/model/Vertex.java
+++ b/src/main/java/org/opensphere/geometry/triangulation/model/Vertex.java
@@ -1,7 +1,7 @@
/* This file is based on code copied from project OpenSphere, see the LICENSE file for further information. */
package org.opensphere.geometry.triangulation.model;
-import com.vividsolutions.jts.geom.Coordinate;
+import org.locationtech.jts.geom.Coordinate;
/**
* Vertex.
diff --git a/src/main/java/org/opentripplanner/analyst/PointFeature.java b/src/main/java/org/opentripplanner/analyst/PointFeature.java
index 5df920eccbb..119d95637dd 100644
--- a/src/main/java/org/opentripplanner/analyst/PointFeature.java
+++ b/src/main/java/org/opentripplanner/analyst/PointFeature.java
@@ -10,10 +10,10 @@
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
-import com.vividsolutions.jts.geom.Geometry;
-import com.vividsolutions.jts.geom.MultiPolygon;
-import com.vividsolutions.jts.geom.Point;
-import com.vividsolutions.jts.geom.Polygon;
+import org.locationtech.jts.geom.Geometry;
+import org.locationtech.jts.geom.MultiPolygon;
+import org.locationtech.jts.geom.Point;
+import org.locationtech.jts.geom.Polygon;
public class PointFeature implements Serializable {
diff --git a/src/main/java/org/opentripplanner/analyst/PointSet.java b/src/main/java/org/opentripplanner/analyst/PointSet.java
index 859b57d30fd..263d0d2d3c2 100644
--- a/src/main/java/org/opentripplanner/analyst/PointSet.java
+++ b/src/main/java/org/opentripplanner/analyst/PointSet.java
@@ -9,10 +9,10 @@
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.MappingJsonFactory;
import com.fasterxml.jackson.databind.ObjectMapper;
-import com.vividsolutions.jts.geom.Coordinate;
-import com.vividsolutions.jts.geom.Envelope;
-import com.vividsolutions.jts.geom.Geometry;
-import com.vividsolutions.jts.geom.Polygon;
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.Envelope;
+import org.locationtech.jts.geom.Geometry;
+import org.locationtech.jts.geom.Polygon;
import gnu.trove.map.TObjectIntMap;
import gnu.trove.map.hash.TObjectIntHashMap;
import org.geojson.LngLatAlt;
diff --git a/src/main/java/org/opentripplanner/analyst/RepeatedRaptorComparison.java b/src/main/java/org/opentripplanner/analyst/RepeatedRaptorComparison.java
deleted file mode 100644
index 8edc1dc1277..00000000000
--- a/src/main/java/org/opentripplanner/analyst/RepeatedRaptorComparison.java
+++ /dev/null
@@ -1,269 +0,0 @@
-package org.opentripplanner.analyst;
-
-import com.vividsolutions.jts.geom.Coordinate;
-import org.joda.time.LocalDate;
-import org.mapdb.*;
-import org.opentripplanner.analyst.cluster.ResultEnvelope;
-import org.opentripplanner.analyst.cluster.TaskStatistics;
-import org.opentripplanner.api.parameter.QualifiedModeSet;
-import org.opentripplanner.common.MavenVersion;
-import org.opentripplanner.graph_builder.GraphBuilder;
-import org.opentripplanner.profile.ProfileRequest;
-import org.opentripplanner.profile.RaptorWorker;
-import org.opentripplanner.profile.RaptorWorkerData;
-import org.opentripplanner.profile.RepeatedRaptorProfileRouter;
-import org.opentripplanner.routing.core.TraverseModeSet;
-import org.opentripplanner.routing.graph.Graph;
-import org.opentripplanner.routing.graph.Vertex;
-import org.opentripplanner.standalone.CommandLineParameters;
-import org.opentripplanner.common.Histogram;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.File;
-import java.util.Collection;
-import java.util.List;
-import java.util.stream.Collectors;
-import java.util.stream.IntStream;
-
-/**
- * Compare results from Repeated RAPTOR runs. This is used to detect algorithmic changes that have
- * had an effect on output.
- *
- * Usage:
- * graph_directory [compare_file] [output_file]
- *
- * Graph directory is the directory from which to build the graph. Compare file is the previously saved
- * file; if omitted it will not be used. Output file is the MapDB file in which
- * to save output. If left blank it defaults to the OTP commit hash.
- *
- */
-public class RepeatedRaptorComparison {
- private static final Logger LOG = LoggerFactory.getLogger(RepeatedRaptorComparison.class);
-
- // amounts to offset origins from vertices to test linking
- public static final double OFFSET_X = 1e-4, OFFSET_Y = 1e-4;
-
- public static void main (String... args) {
- if (args.length == 0) {
- System.err.println("too few arguments.");
- return;
- }
-
- // build a graph
- File graphDir = new File(args[0]);
- Graph graph = buildGraph(graphDir);
-
- DB comparisonDb = null;
- BTreeMap, Integer> comparison = null;
-
- // open the comparison file, if we have one.
- if (args.length > 1) {
- comparisonDb = DBMaker.newFileDB(new File(args[1]))
- .readOnly()
- .transactionDisable()
- .closeOnJvmShutdown()
- .cacheSize(24)
- .asyncWriteEnable()
- .make();
-
- comparison = comparisonDb.getTreeMap("results");
- }
-
- String outputName = args.length > 2 ? args[2] : MavenVersion.VERSION.commit + ".db";
-
- DB outputDb = DBMaker.newFileDB(new File(outputName))
- .transactionDisable()
- .cacheSize(48)
- .closeOnJvmShutdown()
- .make();
-
- final BTreeMap, Integer> output = outputDb.createTreeMap("results")
- .valueSerializer(Serializer.JAVA)
- .makeOrGet();
-
- // if we have a comparison file, get the pointset from it. Otherwise choose some randomly.
- Collection vertexLabels;
- PointSet pset;
-
- if (comparison != null) {
- // clooge, pointset is stored in its own map in db.
- pset = comparisonDb.getTreeMap("pointset").get("pointset");
- }
- else {
- // choose some vertices
- List vertices = graph.getVertices().stream()
- // only use OSM nodes, because they are stable. e.g. splittervertices may not have stable identifiers between builds.
- .filter(v -> v.getLabel().startsWith("osm:node:"))
- .limit(1000)
- .collect(Collectors.toList());
-
- // make a pointset
- pset = new PointSet(vertices.size());
- int featIdx = 0;
- for (Vertex v : vertices) {
- PointFeature pf = new PointFeature();
- pf.setId(v.getLabel());
- pf.setLat(v.getLat() + OFFSET_Y);
- pf.setLon(v.getLon() + OFFSET_X);
- pset.addFeature(pf, featIdx++);
- }
-
- outputDb.createTreeMap("pointset")
- .make().put("pointset", pset);
- }
-
- SampleSet ss = new SampleSet(pset, graph.getSampleFactory());
-
- final BTreeMap, Integer> comparisonResults = comparison;
-
- Histogram bestCaseHisto = new Histogram("Best case");
- Histogram avgCaseHisto = new Histogram("Average");
- Histogram worstCaseHisto = new Histogram("Worst case");
-
- ProfileRequest template = new ProfileRequest();
- template.accessModes = new QualifiedModeSet("WALK");
- template.analyst = true;
- template.maxWalkTime = 20 * 60;
- template.walkSpeed = 1.3f;
- template.fromTime = 7 * 3600;
- template.toTime = 9 * 3600;
-
- template.date = new LocalDate(2015, 8, 4);
-
- RaptorWorkerData data = RepeatedRaptorProfileRouter.getRaptorWorkerData(template, graph, ss, new TaskStatistics());
-
- // do the computation and comparison
- IntStream.range(0, pset.featureCount()).parallel()
- .forEach(idx -> {
-
- if (idx % 100 == 0)
- System.out.println(idx + " points complete");
-
- Coordinate coord = pset.getCoordinate(idx);
- String origin = pset.getFeature(idx).getId();
-
- ProfileRequest req;
- try {
- req = template.clone();
- } catch (CloneNotSupportedException e) {
- /* can't happen */
- throw new RuntimeException(e);
- }
-
- req.maxWalkTime = 20 * 60;
- req.fromLat = req.toLat = coord.y;
- req.fromLon = req.toLon = coord.x;
- // 7 to 9 AM
- req.fromTime = 7 * 3600;
- req.toTime = 9 * 3600;
- req.transitModes = new TraverseModeSet("TRANSIT");
-
- RepeatedRaptorProfileRouter rrpr = new RepeatedRaptorProfileRouter(graph, req, ss);
- rrpr.raptorWorkerData = data;
- rrpr.includeTimes = true;
- // TODO we really want to disable both isochrone and accessibility generation here.
- // Because a sampleSet is provided it's going to make accessibility information (not isochrones).
-
- ResultEnvelope results = new ResultEnvelope();
- try {
- results = rrpr.route();
- } catch (Exception e) {
- LOG.error("Exception during routing", e);
- return;
- }
-
- for (ResultEnvelope.Which which : new ResultEnvelope.Which[] {
- ResultEnvelope.Which.BEST_CASE, ResultEnvelope.Which.AVERAGE,
- ResultEnvelope.Which.WORST_CASE }) {
- Histogram histogram;
- ResultSet resultSet;
-
- switch (which) {
- case BEST_CASE:
- histogram = bestCaseHisto;
- resultSet = results.bestCase;
- break;
- case WORST_CASE:
- histogram = worstCaseHisto;
- resultSet = results.worstCase;
- break;
- case AVERAGE:
- histogram = avgCaseHisto;
- resultSet = results.avgCase;
- break;
- default:
- histogram = null;
- resultSet = null;
- }
-
- // now that we have the proper histogram and result set, save them and do the
- // comparison.
- for (int i = 0; i < resultSet.times.length; i++) {
- int time = resultSet.times[i];
- // TODO this is creating a PointFeature obj to hold the id at each call
- // Cache?
- String dest = pset.getFeature(i).getId();
-
- Fun.Tuple3 key = new Fun.Tuple3<>(
- origin, dest, which);
- output.put(key, time);
-
- if (time < 0) {
- LOG.error("Path from {} to {} has negative time {}", origin, dest,
- time);
- }
-
- if (comparisonResults != null) {
- int time0 = comparisonResults.get(key);
-
- int deltaMinutes;
-
- if (time0 == RaptorWorker.UNREACHED && time != RaptorWorker.UNREACHED)
- deltaMinutes = (time / 60) - 120;
- else if (time == RaptorWorker.UNREACHED
- && time0 != RaptorWorker.UNREACHED)
- deltaMinutes = 120 - (time0 / 60);
- else
- deltaMinutes = (time - time0) / 60;
-
- // histograms are not threadsafe
- synchronized (histogram) {
- histogram.add(deltaMinutes);
- }
- }
- }
- }
- });
-
- output.close();
- if (comparisonDb != null) {
- comparisonDb.close();
-
- bestCaseHisto.displayHorizontal();
- System.out.println("mean: " + bestCaseHisto.mean());
-
- avgCaseHisto.displayHorizontal();
- System.out.println("mean: " + avgCaseHisto.mean());
-
- worstCaseHisto.displayHorizontal();
- System.out.println("mean: " + worstCaseHisto.mean());
- }
- }
-
- private static Graph buildGraph(File directory) {
- CommandLineParameters params = new CommandLineParameters();
- params.build = directory;
- params.inMemory = true;
- GraphBuilder graphBuilder = GraphBuilder.forDirectory(params, params.build);
- graphBuilder.run();
- Graph graph = graphBuilder.getGraph();
- graph.routerId = "GRAPH";
- // re-index the graph to ensure all data is added and recreate a new streetIndex. It's ok to recreate the
- // streetIndex because the previous one created during graph build is not needed anymore and isn't able to be
- // used outside of the graphBuilder.
- graph.index(true);
- graph.index.clusterStopsAsNeeded();
- return graph;
- }
-}
diff --git a/src/main/java/org/opentripplanner/analyst/TimeSurface.java b/src/main/java/org/opentripplanner/analyst/TimeSurface.java
index 245c1f8a1ca..37a5a17d7a2 100644
--- a/src/main/java/org/opentripplanner/analyst/TimeSurface.java
+++ b/src/main/java/org/opentripplanner/analyst/TimeSurface.java
@@ -1,6 +1,6 @@
package org.opentripplanner.analyst;
-import com.vividsolutions.jts.geom.Coordinate;
+import org.locationtech.jts.geom.Coordinate;
import gnu.trove.iterator.TObjectIntIterator;
import gnu.trove.map.TObjectIntMap;
import gnu.trove.map.hash.TObjectIntHashMap;
diff --git a/src/main/java/org/opentripplanner/analyst/batch/GraphGeographicFilter.java b/src/main/java/org/opentripplanner/analyst/batch/GraphGeographicFilter.java
index babc0f5c8f3..bbd6e55eb89 100644
--- a/src/main/java/org/opentripplanner/analyst/batch/GraphGeographicFilter.java
+++ b/src/main/java/org/opentripplanner/analyst/batch/GraphGeographicFilter.java
@@ -13,11 +13,11 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import com.vividsolutions.jts.geom.Coordinate;
-import com.vividsolutions.jts.geom.Envelope;
-import com.vividsolutions.jts.geom.Geometry;
-import com.vividsolutions.jts.geom.GeometryFactory;
-import com.vividsolutions.jts.geom.Point;
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.Envelope;
+import org.locationtech.jts.geom.Geometry;
+import org.locationtech.jts.geom.GeometryFactory;
+import org.locationtech.jts.geom.Point;
public class GraphGeographicFilter implements IndividualFilter {
diff --git a/src/main/java/org/opentripplanner/analyst/batch/ShapefilePopulation.java b/src/main/java/org/opentripplanner/analyst/batch/ShapefilePopulation.java
index 9ff3d498dee..4778176a66c 100644
--- a/src/main/java/org/opentripplanner/analyst/batch/ShapefilePopulation.java
+++ b/src/main/java/org/opentripplanner/analyst/batch/ShapefilePopulation.java
@@ -14,10 +14,10 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import com.vividsolutions.jts.geom.Geometry;
-import com.vividsolutions.jts.geom.MultiPolygon;
-import com.vividsolutions.jts.geom.Point;
-import com.vividsolutions.jts.geom.Polygon;
+import org.locationtech.jts.geom.Geometry;
+import org.locationtech.jts.geom.MultiPolygon;
+import org.locationtech.jts.geom.Point;
+import org.locationtech.jts.geom.Polygon;
public class ShapefilePopulation extends BasicPopulation {
diff --git a/src/main/java/org/opentripplanner/analyst/broker/Broker.java b/src/main/java/org/opentripplanner/analyst/broker/Broker.java
deleted file mode 100644
index c5210453055..00000000000
--- a/src/main/java/org/opentripplanner/analyst/broker/Broker.java
+++ /dev/null
@@ -1,750 +0,0 @@
-package org.opentripplanner.analyst.broker;
-
-import com.amazonaws.regions.Regions;
-import com.amazonaws.services.ec2.AmazonEC2;
-import com.amazonaws.services.ec2.AmazonEC2Client;
-import com.amazonaws.services.ec2.model.*;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.google.common.collect.ArrayListMultimap;
-import com.google.common.collect.HashMultimap;
-import com.google.common.collect.Multimap;
-import com.google.common.collect.TreeMultimap;
-import gnu.trove.map.TIntIntMap;
-import gnu.trove.map.TIntObjectMap;
-import gnu.trove.map.TObjectLongMap;
-import gnu.trove.map.hash.TIntIntHashMap;
-import gnu.trove.map.hash.TIntObjectHashMap;
-import gnu.trove.map.hash.TObjectLongHashMap;
-import org.glassfish.grizzly.http.server.Request;
-import org.glassfish.grizzly.http.server.Response;
-import org.glassfish.grizzly.http.util.HttpStatus;
-import org.opentripplanner.analyst.cluster.AnalystClusterRequest;
-import org.opentripplanner.analyst.cluster.AnalystWorker;
-import org.opentripplanner.api.model.FeedScopedIdSerializer;
-import org.opentripplanner.api.model.JodaLocalDateSerializer;
-import org.opentripplanner.api.model.QualifiedModeSetSerializer;
-import org.opentripplanner.api.model.TraverseModeSetSerializer;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.*;
-import java.time.LocalDateTime;
-import java.time.format.DateTimeFormatter;
-import java.util.*;
-
-/**
- * This class tracks incoming requests from workers to consume Analyst tasks, and attempts to match those
- * requests to enqueued tasks. It aims to draw tasks fairly from all users, and fairly from all jobs within each user,
- * while attempting to respect the graph affinity of each worker (give it tasks that require the same graph it has been
- * working on recently).
- *
- * When no work is available or no workers are available, the polling functions return immediately, avoiding spin-wait.
- * When they are receiving no work, workers are expected to disconnect and re-poll occasionally, on the order of 30
- * seconds. This serves as a signal to the broker that they are still alive and waiting.
- *
- * TODO if there is a backlog of work (the usual case when jobs are lined up) workers will constantly change graphs.
- * Because (at least currently) two users never share the same graph, we can get by with pulling tasks cyclically or
- * randomly from all the jobs, and just actively shaping the number of workers with affinity for each graph by forcing
- * some of them to accept tasks on graphs other than the one they have declared affinity for.
- *
- * This could be thought of as "affinity homeostasis". We will constantly keep track of the ideal proportion of workers
- * by graph (based on active queues), and the true proportion of consumers by graph (based on incoming requests) then
- * we can decide when a worker's graph affinity should be ignored and what it should be forced to.
- *
- * It may also be helpful to mark jobs every time they are skipped in the LRU queue. Each time a job is serviced,
- * it is taken out of the queue and put at its end. Jobs that have not been serviced float to the top.
- */
-public class Broker implements Runnable {
-
- // TODO catalog of recently seen consumers by affinity with IP: response.getRequest().getRemoteAddr();
-
- private static final Logger LOG = LoggerFactory.getLogger(Broker.class);
-
- /* How often we should check for delivered tasks that have timed out. */
- private static final int REDELIVERY_INTERVAL_SEC = 10;
-
- public final CircularList jobs = new CircularList<>();
-
- /** the most tasks to deliver to a worker at a time */
- public final int MAX_TASKS_PER_WORKER = 8;
-
- /**
- * How long to give workers to start up (in ms) before assuming that they have started (and starting more
- * on a given graph if they haven't.
- */
- public static final long WORKER_STARTUP_TIME = 60 * 60 * 1000;
-
- private int nUndeliveredTasks = 0; // Including normal priority jobs and high-priority tasks.
-
- private int nWaitingConsumers = 0; // including some that might be closed
-
- private int nextTaskId = 0;
-
- /** Maximum number of workers allowed */
- private int maxWorkers;
-
- private static final ObjectMapper mapper = new ObjectMapper();
-
- private long nextRedeliveryCheckTime = System.currentTimeMillis();
-
- static {
- mapper.registerModule(FeedScopedIdSerializer.makeModule());
- mapper.registerModule(QualifiedModeSetSerializer.makeModule());
- mapper.registerModule(JodaLocalDateSerializer.makeModule());
- mapper.registerModule(TraverseModeSetSerializer.makeModule());
- }
-
- /** broker configuration */
- private final Properties config;
-
- /** configuration for workers launched by this broker */
- private final Properties workerConfig;
-
- private WorkerCatalog workerCatalog = new WorkerCatalog();
-
- /** The time at which each task was delivered to a worker, to allow re-delivery. */
- TIntIntMap deliveryTimes = new TIntIntHashMap();
-
- /**
- * Requests that are not part of a job and can "cut in line" in front of jobs for immediate execution.
- * When a high priority task is first received, we attempt to send it to a worker right away via
- * the side channels. If that doesn't work, we put them here to be picked up the next time a worker
- * is available via normal task distribution channels.
- */
- private ArrayListMultimap stalledHighPriorityTasks = ArrayListMultimap.create();
-
- /**
- * High priority requests that have just come and are about to be sent down a single point channel.
- * They put here for just 100 ms so that any that arrive together are batched to the same worker.
- * If we didn't do this, two requests arriving at basically the same time could get fanned out to
- * two different workers because the second came in in between closing the side channel and the worker
- * reopening it.
- */
- private Multimap newHighPriorityTasks = ArrayListMultimap.create();
-
- /** Priority requests that have already been farmed out to workers, and are awaiting a response. */
- private TIntObjectMap highPriorityResponses = new TIntObjectHashMap<>();
-
- /** Outstanding requests from workers for tasks, grouped by worker graph affinity. */
- Map> consumersByGraph = new HashMap<>();
-
- /**
- * Side channels used to send single point requests to workers, cutting in front of any other work on said workers.
- * We use a TreeMultimap because it is ordered, and the wrapped response defines an order based on
- * machine ID. This way, the same machine will tend to get all single point work for a graph,
- * so multiple machines won't stay alive to do single point work.
- */
- private Multimap singlePointChannels = TreeMultimap.create();
-
- /** should we work offline */
- private boolean workOffline;
-
- private AmazonEC2 ec2;
-
- private Timer timer = new Timer();
-
- private String workerName, project;
-
- /**
- * keep track of which graphs we have launched workers on and how long ago we launched them,
- * so that we don't re-request workers which have been requested.
- */
- private TObjectLongMap recentlyRequestedWorkers = new TObjectLongHashMap<>();
-
- // Queue of tasks to complete Delete, Enqueue etc. to avoid synchronizing all the functions ?
- public Broker (Properties brokerConfig, String addr, int port) {
- // print out date on startup so that CloudWatch logs has a unique fingerprint
- LOG.info("Analyst worker starting at {}", LocalDateTime.now().format(DateTimeFormatter.ISO_DATE_TIME));
-
- this.config = brokerConfig;
-
- // create a config for the AWS workers
- workerConfig = new Properties();
-
- // load the base worker configuration if specified
- if (config.getProperty("worker-config") != null) {
- try {
- File f = new File(config.getProperty("worker-config"));
- FileInputStream fis = new FileInputStream(f);
- workerConfig.load(fis);
- fis.close();
- } catch (IOException e) {
- LOG.error("Error loading base worker configuration", e);
- }
- }
-
- workerConfig.setProperty("broker-address", addr);
- workerConfig.setProperty("broker-port", "" + port);
-
- if (brokerConfig.getProperty("statistics-queue") != null)
- workerConfig.setProperty("statistics-queue", brokerConfig.getProperty("statistics-queue"));
-
- workerConfig.setProperty("graphs-bucket", brokerConfig.getProperty("graphs-bucket"));
- workerConfig.setProperty("pointsets-bucket", brokerConfig.getProperty("pointsets-bucket"));
-
- // Tell the workers to shut themselves down automatically
- workerConfig.setProperty("auto-shutdown", "true");
-
- Boolean workOffline = Boolean.parseBoolean(brokerConfig.getProperty("work-offline"));
- if (workOffline == null) workOffline = true;
- this.workOffline = workOffline;
-
- workerName = brokerConfig.getProperty("worker-name") != null ? brokerConfig.getProperty("worker-name") : "analyst-worker";
- project = brokerConfig.getProperty("project") != null ? brokerConfig.getProperty("project") : "analyst";
-
- this.maxWorkers = brokerConfig.getProperty("max-workers") != null ? Integer.parseInt(brokerConfig.getProperty("max-workers")) : 4;
-
- ec2 = new AmazonEC2Client();
-
- // default to current region when running in EC2
- com.amazonaws.regions.Region r = Regions.getCurrentRegion();
-
- if (r != null)
- ec2.setRegion(r);
- }
-
- /**
- * Enqueue a task for execution ASAP, planning to return the response over the same HTTP connection.
- * Low-reliability, no re-delivery.
- */
- public synchronized void enqueuePriorityTask (AnalystClusterRequest task, Response response) {
- boolean workersAvailable = workersAvailableForGraph(task.graphId);
-
- if (!workersAvailable) {
- createWorkersForGraph(task.graphId);
- // chances are it won't be done in 30 seconds, but we want to poll frequently to avoid issues with phasing
- try {
- response.setHeader("Retry-After", "30");
- response.sendError(503,
- "No workers available with this graph affinity, please retry shortly.");
- } catch (IOException e) {
- LOG.error("Could not finish high-priority 503 response", e);
- }
- }
-
- // if we're in offline mode, enqueue anyhow to kick the cluster to build the graph
- // note that this will mean that requests get delivered multiple times in offline mode,
- // so some unnecessary computation takes place
- if (workersAvailable || workOffline) {
- task.taskId = nextTaskId++;
- newHighPriorityTasks.put(task.graphId, task);
- highPriorityResponses.put(task.taskId, response);
-
- // wait 100ms to deliver to workers in case another request comes in almost simultaneously
- timer.schedule(new TimerTask() {
- @Override
- public void run() {
- deliverHighPriorityTasks(task.graphId);
- }
- }, 100);
- }
-
- // do not notify task delivery thread just yet as we haven't put anything in the task delivery queue yet.
- }
-
- /** attempt to deliver high priority tasks via side channels, or move them into normal channels if need be */
- public synchronized void deliverHighPriorityTasks (String graphId) {
- Collection tasks = newHighPriorityTasks.get(graphId);
-
- if (tasks.isEmpty())
- // someone got here first
- return;
-
- // try to deliver via side channels
- Collection wrs = singlePointChannels.get(graphId);
-
- if (!wrs.isEmpty()) {
- // there is (probably) a single point machine waiting to receive this
- WrappedResponse wr = wrs.iterator().next();
-
- try {
- wr.response.setContentType("application/json");
- OutputStream os = wr.response.getOutputStream();
- mapper.writeValue(os, tasks);
- os.close();
- wr.response.resume();
-
- newHighPriorityTasks.removeAll(graphId);
-
- return;
- } catch (Exception e) {
- LOG.info("Failed to deliver single point job via side channel, reverting to normal channel", e);
- } finally {
- // remove responses whether they are dead or alive
- removeSinglePointChannel(graphId, wr);
- }
- }
-
- // if we got here we didn't manage to send it via side channel, put it in the rotation for normal channels
- // not using putAll as it retains a link to the original collection and then we get a concurrent modification exception later.
- tasks.forEach(t -> stalledHighPriorityTasks.put(graphId, t));
- LOG.info("No side channel available for graph {}, delivering {} tasks via normal channel",
- graphId, tasks.size());
- nUndeliveredTasks += tasks.size();
- newHighPriorityTasks.removeAll(graphId);
-
- // wake up delivery thread
- notify();
- }
-
- /** Enqueue some tasks for queued execution possibly much later. Results will be saved to S3. */
- public synchronized void enqueueTasks (List tasks) {
- Job job = findJob(tasks.get(0)); // creates one if it doesn't exist
-
- if (!workersAvailableForGraph(job.graphId))
- createWorkersForGraph(job.graphId);
-
- for (AnalystClusterRequest task : tasks) {
- task.taskId = nextTaskId++;
- job.addTask(task);
- nUndeliveredTasks += 1;
- LOG.debug("Enqueued task id {} in job {}", task.taskId, job.jobId);
- if ( ! task.graphId.equals(job.graphId)) {
- LOG.warn("Task graph ID {} does not match job graph ID {}.", task.graphId, job.graphId);
- }
- }
- // Wake up the delivery thread if it's waiting on input.
- // This wakes whatever thread called wait() while holding the monitor for this Broker object.
- notify();
- }
-
- public boolean workersAvailableForGraph (String graphId) {
- // make sure that we don't assign work to dead workers
- workerCatalog.purgeDeadWorkers();
-
- return !workerCatalog.workersByGraph.get(graphId).isEmpty();
- }
-
- /** Create workers for a given job, if need be */
- public void createWorkersForGraph (String graphId) {
- String clientToken = UUID.randomUUID().toString().replaceAll("-", "");
-
- if (workOffline) {
- LOG.info("Work offline enabled, not creating workers for graph {}", graphId);
- return;
- }
-
- if (workerCatalog.observationsByWorkerId.size() >= maxWorkers) {
- LOG.warn("{} workers already started, not starting more; jobs on graph {} will not complete", maxWorkers, graphId);
- return;
- }
-
- // don't re-request workers
- if (recentlyRequestedWorkers.containsKey(graphId)
- && recentlyRequestedWorkers.get(graphId) >= System.currentTimeMillis() - WORKER_STARTUP_TIME){
- LOG.info("workers still starting on graph {}, not starting more", graphId);
- return;
- }
-
-
- // TODO: compute
- int nWorkers = 1;
-
- LOG.info("Starting {} workers as there are none on graph {}", nWorkers, graphId);
- // there are no workers on this graph, start one
- RunInstancesRequest req = new RunInstancesRequest();
- req.setImageId(config.getProperty("ami-id"));
- req.setInstanceType(InstanceType.valueOf(config.getProperty("worker-type")));
- req.setSubnetId(config.getProperty("subnet-id"));
-
- // even if we can't get all the workers we want at least get some
- req.setMinCount(1);
- req.setMaxCount(nWorkers);
-
- // it's fine to just modify the worker config as this method is synchronized
- workerConfig.setProperty("initial-graph-id", graphId);
-
- ByteArrayOutputStream cfg = new ByteArrayOutputStream();
- try {
- workerConfig.store(cfg, "Worker config");
- cfg.close();
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
-
- // send the config as user data
- String userData = new String(Base64.getEncoder().encode(cfg.toByteArray()));
- req.setUserData(userData);
-
- if (config.getProperty("worker-iam-role") != null)
- req.setIamInstanceProfile(new IamInstanceProfileSpecification().withArn(config.getProperty("worker-iam-role")));
-
- // launch into a VPC if desired
- if (config.getProperty("subnet") != null)
- req.setSubnetId(config.getProperty("subnet"));
-
- // allow us to retry request at will
- req.setClientToken(clientToken);
- // allow machine to shut itself completely off
- req.setInstanceInitiatedShutdownBehavior(ShutdownBehavior.Terminate);
- RunInstancesResult res = ec2.runInstances(req);
- res.getReservation().getInstances().forEach(i -> {
- Collection tags = Arrays.asList(
- new Tag("name", workerName),
- new Tag("project", project)
- );
- i.setTags(tags);
- });
- recentlyRequestedWorkers.put(graphId, System.currentTimeMillis());
- LOG.info("Requesting {} workers", nWorkers);
- }
-
- /** Consumer long-poll operations are enqueued here. */
- public synchronized void registerSuspendedResponse(String graphId, Response response) {
- // Add this worker to our catalog, tracking its graph affinity and the last time it was seen.
- String workerId = response.getRequest().getHeader(AnalystWorker.WORKER_ID_HEADER);
- if (workerId != null && !workerId.isEmpty()) {
- workerCatalog.catalog(workerId, graphId);
- } else {
- LOG.error("Worker did not supply a unique ID for itself . Ignoring it.");
- return;
- }
- // Shelf this suspended response in a queue grouped by graph affinity.
- Deque deque = consumersByGraph.get(graphId);
- if (deque == null) {
- deque = new ArrayDeque<>();
- consumersByGraph.put(graphId, deque);
- }
- deque.addLast(response);
- nWaitingConsumers += 1;
- // Wake up the delivery thread if it's waiting on consumers.
- // This is whatever thread called wait() while holding the monitor for this Broker object.
- notify();
- }
-
- /** When we notice that a long poll connection has closed, we remove it here. */
- public synchronized boolean removeSuspendedResponse(String graphId, Response response) {
- Deque deque = consumersByGraph.get(graphId);
- if (deque == null) {
- return false;
- }
- if (deque.remove(response)) {
- nWaitingConsumers -= 1;
- LOG.debug("Removed closed connection from queue.");
- logQueueStatus();
- return true;
- }
- return false;
- }
-
- /**
- * Register an HTTP connection that can be used to send single point requests directly to
- * workers, bypassing normal task distribution channels.
- */
- public synchronized void registerSinglePointChannel (String graphAffinity,WrappedResponse response) {
- singlePointChannels.put(graphAffinity, response);
- // no need to notify as the side channels are not used by the normal task delivery loop
- }
-
- /**
- * Remove a single point channel because the connection was closed.
- */
- public synchronized boolean removeSinglePointChannel (String graphAffinity, WrappedResponse response) {
- return singlePointChannels.remove(graphAffinity, response);
- }
-
- private void logQueueStatus() {
- LOG.info("{} undelivered, of which {} high-priority", nUndeliveredTasks,
- stalledHighPriorityTasks.size());
- LOG.info("{} producers waiting, {} consumers waiting", highPriorityResponses.size(), nWaitingConsumers);
- LOG.info("{} total workers", workerCatalog.size());
- }
-
- /**
- * Check whether there are any delivered tasks that have reached their invisibility timeout but have not yet been
- * marked complete. Enqueue those tasks for redelivery.
- */
- private void redeliver() {
- if (System.currentTimeMillis() > nextRedeliveryCheckTime) {
- nextRedeliveryCheckTime += REDELIVERY_INTERVAL_SEC * 1000;
- LOG.info("Scanning for redelivery...");
- int nRedelivered = 0;
- int nInvisible = 0;
- for (Job job : jobs) {
- nInvisible += job.invisibleUntil.size();
- nRedelivered += job.redeliver();
- }
- LOG.info("{} tasks enqueued for redelivery out of {} invisible tasks.", nRedelivered, nInvisible);
- nUndeliveredTasks += nRedelivered;
- }
- }
-
- /**
- * This method checks whether there are any high-priority tasks or normal job tasks and attempts to match them with
- * waiting workers. It blocks until there are tasks or workers available.
- */
- public synchronized void deliverTasks() throws InterruptedException {
-
- // Wait until there are some undelivered tasks.
- while (nUndeliveredTasks == 0) {
- LOG.debug("Task delivery thread is going to sleep, there are no tasks waiting for delivery.");
- logQueueStatus();
- wait();
- redeliver();
- }
- LOG.debug("Task delivery thread is awake and there are some undelivered tasks.");
- logQueueStatus();
-
- while (nWaitingConsumers == 0) {
- LOG.debug("Task delivery thread is going to sleep, there are no consumers waiting.");
- // Thread will be notified when there are new incoming consumer connections.
- wait();
- }
-
- LOG.debug("Task delivery thread awake; consumers are waiting and tasks are available");
-
- // Loop over all jobs and send them to consumers
- // This makes for an as-fair-as-possible allocation: jobs are fairly allocated between
- // workers on their graph.
-
- // start with high-priority tasks
- HIGHPRIORITY: for (Map.Entry> e : stalledHighPriorityTasks
- .asMap().entrySet()) {
- // the collection is an arraylist with the most recently added at the end
- String graphId = e.getKey();
- Collection tasks = e.getValue();
-
- // see if there are any consumers for this
- // don't respect graph affinity when working offline; we can't arbitrarily start more workers
- Deque consumers;
- if (!workOffline)
- consumers = consumersByGraph.get(graphId);
- else {
- Optional> opt = consumersByGraph.values().stream().filter(c -> !c.isEmpty()).findFirst();
- if (opt.isPresent()) consumers = opt.get();
- else consumers = null;
- }
-
- if (consumers == null || consumers.isEmpty()) {
- LOG.warn("No consumer found for graph {}, needed for {} high-priority tasks", graphId, tasks.size());
- continue HIGHPRIORITY;
- }
-
- Iterator taskIt = tasks.iterator();
- while (taskIt.hasNext() && !consumers.isEmpty()) {
- Response consumer = consumers.pop();
-
- // package tasks into a job
- Job job = new Job("HIGH PRIORITY");
- job.graphId = graphId;
- for (int i = 0; i < MAX_TASKS_PER_WORKER && taskIt.hasNext(); i++) {
- job.addTask(taskIt.next());
- taskIt.remove();
- }
-
- // TODO inefficiency here: we should mix single point and multipoint in the same response
- deliver(job, consumer);
- nWaitingConsumers--;
- }
- }
-
- // deliver low priority tasks
- while (nWaitingConsumers > 0) {
- // ensure we advance at least one; advanceToElement will not advance if the predicate passes
- // for the first element.
- jobs.advance();
-
- // find a job that both has visible tasks and has available workers
- // We don't respect graph affinity when working offline, because we can't start more workers
- Job current;
- if (!workOffline) {
- current = jobs.advanceToElement(e -> !e.tasksAwaitingDelivery.isEmpty() &&
- consumersByGraph.containsKey(e.graphId) &&
- !consumersByGraph.get(e.graphId).isEmpty());
- }
- else {
- current = jobs.advanceToElement(e -> !e.tasksAwaitingDelivery.isEmpty());
- }
-
- // nothing to see here
- if (current == null) break;
-
- Deque consumers;
- if (!workOffline)
- consumers = consumersByGraph.get(current.graphId);
- else {
- Optional> opt = consumersByGraph.values().stream().filter(c -> !c.isEmpty()).findFirst();
- if (opt.isPresent()) consumers = opt.get();
- else consumers = null;
- }
- // deliver this job to only one consumer
- // This way if there are multiple workers and multiple jobs the jobs will be fairly distributed, more or less
- deliver(current, consumers.pop());
- nWaitingConsumers--;
- }
-
- // TODO: graph switching
-
- // we've delivered everything we can, prevent anything else from happening until something changes
- wait();
- }
-
- /**
- * This uses a linear search through jobs, which should not be problematic unless there are thousands of
- * simultaneous jobs.
- * @return a Job object that contains the given task ID.
- */
- public Job getJobForTask (int taskId) {
- for (Job job : jobs) {
- if (job.containsTask(taskId)) {
- return job;
- }
- }
- return null;
- }
-
- /**
- * Attempt to hand some tasks from the given job to a waiting consumer connection.
- * The write will fail if the consumer has closed the connection but it hasn't been removed from the connection
- * queue yet. This can happen because the Broker methods are synchronized, and the removal action may be waiting
- * to get the monitor while we are trying to distribute tasks here.
- * @return whether the handoff succeeded.
- */
- public synchronized boolean deliver (Job job, Response response) {
-
- // Check up-front whether the connection is still open.
- if (!response.getRequest().getRequest().getConnection().isOpen()) {
- LOG.debug("Consumer connection was closed. It will be removed.");
- return false;
- }
-
- // Get up to N tasks from the tasksAwaitingDelivery deque
- List tasks = new ArrayList<>();
- while (tasks.size() < MAX_TASKS_PER_WORKER && !job.tasksAwaitingDelivery.isEmpty()) {
- tasks.add(job.tasksAwaitingDelivery.poll());
- }
-
- // Attempt to deliver the tasks to the given consumer.
- try {
- response.setStatus(HttpStatus.OK_200);
- OutputStream out = response.getOutputStream();
- mapper.writeValue(out, tasks);
- response.resume();
- } catch (IOException e) {
- // The connection was probably closed by the consumer, but treat it as a server error.
- LOG.debug("Consumer connection caused IO error, it will be removed.");
- response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR_500);
- response.resume();
- // Delivery failed, put tasks back on (the end of) the queue.
- job.tasksAwaitingDelivery.addAll(tasks);
- return false;
- }
-
- // Delivery succeeded, move tasks from undelivered to delivered status
- LOG.debug("Delivery of {} tasks succeeded.", tasks.size());
- nUndeliveredTasks -= tasks.size();
- job.markTasksDelivered(tasks);
-
- return true;
-
- }
-
- /**
- * Take a normal (non-priority) task out of a job queue, marking it as completed so it will not be re-delivered.
- * TODO maybe use unique delivery receipts instead of task IDs to handle redelivered tasks independently
- * @return whether the task was found and removed.
- */
- public synchronized boolean markTaskCompleted (int taskId) {
- Job job = getJobForTask(taskId);
- if (job == null) {
- LOG.error("Could not find a job containing task {}, and therefore could not mark the task as completed.");
- return false;
- }
- job.markTaskCompleted(taskId);
- return true;
- }
-
- /**
- * Marks the specified priority request as completed, and returns the suspended Response object for the connection
- * that submitted the priority request (the UI), which probably still waiting to receive a result back over the
- * same connection. A HttpHandler thread can then pump data from the DELETE body back to the origin of the request,
- * without blocking the broker thread.
- * TODO rename to "deregisterSuspendedProducer" and "deregisterSuspendedConsumer" ?
- */
- public synchronized Response deletePriorityTask (int taskId) {
- return highPriorityResponses.remove(taskId);
- }
-
- // TODO: occasionally purge closed connections from consumersByGraph
- // TODO: worker catalog and graph affinity homeostasis
-
- @Override
- public void run() {
- while (true) {
- try {
- deliverTasks();
- } catch (InterruptedException e) {
- LOG.info("Task pump thread was interrupted.");
- return;
- }
- }
- }
-
- /** find the job for a task, creating it if it does not exist */
- public Job findJob (AnalystClusterRequest task) {
- Job job = findJob(task.jobId);
-
- if (job != null)
- return job;
-
- job = new Job(task.jobId);
- job.graphId = task.graphId;
- jobs.insertAtTail(job);
- return job;
- }
-
- /** find the job for a jobId, or null if it does not exist */
- public Job findJob (String jobId) {
- for (Job job : jobs) {
- if (job.jobId.equals(jobId)) {
- return job;
- }
- }
- return null;
- }
-
- /** delete a job */
- public synchronized boolean deleteJob (String jobId) {
- Job job = findJob(jobId);
- if (job == null) return false;
- nUndeliveredTasks -= job.tasksAwaitingDelivery.size();
- return jobs.remove(job);
- }
-
- private Multimap activeJobsPerGraph = HashMultimap.create();
-
- public synchronized boolean anyJobsActive() {
- for (Job job : jobs) {
- if (!job.isComplete()) return true;
- }
- return false;
- }
-
- void activateJob (Job job) {
- activeJobsPerGraph.put(job.graphId, job.jobId);
- }
-
- void deactivateJob (Job job) {
- activeJobsPerGraph.remove(job.graphId, job.jobId);
- }
-
- /**
- * We wrap responses in a class that has a machine ID, and then put them in a TreeSet so that
- * the machine with the lowest ID on a given graph always gets single-point work. The reason
- * for this is so that a single machine will tend to get single-point work and thus we don't
- * unnecessarily keep multiple multipoint machines alive.
- */
- public static class WrappedResponse implements Comparable {
- public final Response response;
- public final String machineId;
-
- public WrappedResponse(Request request, Response response) {
- this.response = response;
- this.machineId = request.getHeader(AnalystWorker.WORKER_ID_HEADER);
- }
-
- @Override public int compareTo(WrappedResponse wrappedResponse) {
- return this.machineId.compareTo(wrappedResponse.machineId);
- }
- }
-}
diff --git a/src/main/java/org/opentripplanner/analyst/broker/BrokerHttpHandler.java b/src/main/java/org/opentripplanner/analyst/broker/BrokerHttpHandler.java
deleted file mode 100644
index 4524c0b592c..00000000000
--- a/src/main/java/org/opentripplanner/analyst/broker/BrokerHttpHandler.java
+++ /dev/null
@@ -1,229 +0,0 @@
-package org.opentripplanner.analyst.broker;
-
-import com.conveyal.geojson.GeoJsonModule;
-import com.fasterxml.jackson.annotation.JsonInclude;
-import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.core.type.TypeReference;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.google.common.io.ByteStreams;
-import org.glassfish.grizzly.http.Method;
-import org.glassfish.grizzly.http.server.HttpHandler;
-import org.glassfish.grizzly.http.server.Request;
-import org.glassfish.grizzly.http.server.Response;
-import org.glassfish.grizzly.http.util.HttpStatus;
-import org.opentripplanner.analyst.cluster.AnalystClusterRequest;
-import org.opentripplanner.api.model.FeedScopedIdSerializer;
-import org.opentripplanner.api.model.JodaLocalDateSerializer;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.IOException;
-import java.io.OutputStream;
-import java.util.Arrays;
-import java.util.List;
-import java.util.stream.Collectors;
-
-/**
-* A Grizzly Async Http Service (uses reponse suspend/resume)
- * https://blogs.oracle.com/oleksiys/entry/grizzly_2_0_httpserver_api1
- *
- * When resuming a response object, "The only reliable way to check the socket status is to try to read or
- * write something." Though you also have:
- *
- * response.getRequest().getRequest().getConnection().isOpen()
- * response.getRequest().getRequest().getConnection().addCloseListener();
- * But none of these work, I've tried all three of them. You can even write to the outputstream after the connection
- * is closed.
- * Solution: networkListener.getTransport().setIOStrategy(SameThreadIOStrategy.getInstance());
- * This makes all three work! isOpen, CloseListener, and IOExceptions from flush();
- *
- * Grizzly has Comet support, but this seems geared toward subscriptions to broadcast events.
- *
-*/
-class BrokerHttpHandler extends HttpHandler {
- private static final Logger LOG = LoggerFactory.getLogger(BrokerHttpHandler.class);
-
- // TODO we should really just make one static mapper somewhere and use it throughout OTP
- private ObjectMapper mapper = new ObjectMapper()
- .registerModule(FeedScopedIdSerializer.makeModule())
- .registerModule(JodaLocalDateSerializer.makeModule())
- .registerModule(new GeoJsonModule())
- .setSerializationInclusion(JsonInclude.Include.NON_NULL);;
-
- private Broker broker;
-
- public BrokerHttpHandler(Broker broker) {
- this.broker = broker;
- }
-
- @Override
- public void service(Request request, Response response) throws Exception {
-
- response.setContentType("application/json");
-
- // request.getRequestURI(); // without protocol or server, only request path
- // request.getPathInfo(); // without handler base path
- String[] pathComponents = request.getPathInfo().split("/");
- // Path component 0 is empty since the path always starts with a slash.
- if (pathComponents.length < 2) {
- response.setStatus(HttpStatus.BAD_REQUEST_400);
- response.setDetailMessage("path should have at least one part");
- }
-
- try {
- if (request.getMethod() == Method.HEAD) {
- /* Let the client know server is alive and URI + request are valid. */
- mapper.readTree(request.getInputStream());
- response.setStatus(HttpStatus.OK_200);
- return;
- } else if (request.getMethod() == Method.GET && "status".equals(pathComponents[1])) {
- /* fetch job status */
- String[] jobIds = pathComponents[2].split(",");
-
- List ret = Arrays.asList(jobIds).stream()
- .map(id -> broker.findJob(id))
- .filter(job -> job != null)
- .map(job -> new JobStatus(job))
- .collect(Collectors.toList());
-
- if (ret.isEmpty()) {
- response.setStatus(HttpStatus.NOT_FOUND_404);
- response.setDetailMessage("no job IDs were found");
- }
- else {
- response.setStatus(HttpStatus.OK_200);
- OutputStream os = response.getOutputStream();
- mapper.writeValue(os, ret);
- os.close();
- }
- return;
- } else if (request.getMethod() == Method.POST) {
- /* dequeue messages. */
- String command = pathComponents[1];
-
- if ("dequeue".equals(command)) {
- String graphAffinity = pathComponents[2];
- request.getRequest().getConnection()
- .addCloseListener((closeable, iCloseType) -> {
- broker.removeSuspendedResponse(graphAffinity, response);
- });
- response.suspend(); // The request should survive after the handler function exits.
- broker.registerSuspendedResponse(graphAffinity, response);
- }
-
- /* not dequeueing, enqueuing */
- else if ("enqueue".equals(command)) {
- String context = pathComponents[2];
- if ("priority".equals(context)) {
- // Enqueue a single priority task
- AnalystClusterRequest task = mapper.readValue(request.getInputStream(),
- AnalystClusterRequest.class);
- broker.enqueuePriorityTask(task, response);
- // Enqueueing the priority task has set its internal taskId.
- // TODO move all removal listener registration into the broker functions.
- request.getRequest().getConnection()
- .addCloseListener((closeable, iCloseType) -> {
- broker.deletePriorityTask(task.taskId);
- });
- response.suspend(); // The request should survive after the handler function exits.
- return;
-
- } else if ("jobs".equals(context)) {
- // Enqueue a list of tasks that belong to jobs
- List tasks = mapper
- .readValue(request.getInputStream(),
- new TypeReference>() {
- });
- // Pre-validate tasks checking that they are all on the same job
- AnalystClusterRequest exemplar = tasks.get(0);
- for (AnalystClusterRequest task : tasks) {
- if (task.jobId != exemplar.jobId || task.graphId != exemplar.graphId) {
- response.setStatus(HttpStatus.BAD_REQUEST_400);
- response.setDetailMessage(
- "All tasks must be for the same graph and job.");
- }
- }
- broker.enqueueTasks(tasks);
- response.setStatus(HttpStatus.ACCEPTED_202);
- } else {
- response.setStatus(HttpStatus.NOT_FOUND_404);
- response.setDetailMessage(
- "Context not found; should be either 'jobs' or 'priority'");
- }
- }
- else if ("complete".equals(command)) {
- // Mark a specific high-priority task as completed, and record its result.
- // We were originally planning to do this with a DELETE request that has a body,
- // but that is nonstandard enough to anger many libraries including Grizzly.
- int taskId = Integer.parseInt(pathComponents[3]);
- Response suspendedProducerResponse = broker.deletePriorityTask(taskId);
- if (suspendedProducerResponse == null) {
- response.setStatus(HttpStatus.NOT_FOUND_404);
- return;
- }
- // Copy the result back to the connection that was the source of the task.
- try {
- ByteStreams.copy(request.getInputStream(),
- suspendedProducerResponse.getOutputStream());
- } catch (IOException ioex) {
- // Apparently the task producer did not wait to retrieve its result. Priority task result delivery
- // is not guaranteed, we don't need to retry, this is not considered an error by the worker.
- }
- response.setStatus(HttpStatus.OK_200);
- suspendedProducerResponse.setStatus(HttpStatus.OK_200);
- suspendedProducerResponse.resume();
- return;
- }
- else if ("single".equals(command)) {
- // await single point responses
- String graphAffinity = pathComponents[2];
- Broker.WrappedResponse wr = new Broker.WrappedResponse(request, response);
- request.getRequest().getConnection().addCloseListener((c, i) -> {
- broker.removeSinglePointChannel(graphAffinity, wr);
- });
- response.suspend();
- broker.registerSinglePointChannel(graphAffinity, wr);
- }
- } else if (request.getMethod() == Method.DELETE) {
- /* Acknowledge completion of a task and remove it from queues, avoiding re-delivery. */
- if ("tasks".equalsIgnoreCase(pathComponents[1])) {
- int taskId = Integer.parseInt(pathComponents[2]);
- // This must not have been a priority task. Try to delete it as a normal job task.
- if (broker.markTaskCompleted(taskId)) {
- response.setStatus(HttpStatus.OK_200);
- } else {
- response.setStatus(HttpStatus.NOT_FOUND_404);
- }
- } else if ("jobs".equals((pathComponents[1]))) {
- if (broker.deleteJob(pathComponents[2])) {
- response.setStatus(HttpStatus.OK_200);
- response.setDetailMessage("job deleted");
- }
- else {
- response.setStatus(HttpStatus.NOT_FOUND_404);
- response.setDetailMessage("job not found");
- }
- } else {
- response.setStatus(HttpStatus.BAD_REQUEST_400);
- response.setDetailMessage("Delete is only allowed for tasks and jobs.");
- }
- } else {
- response.setStatus(HttpStatus.BAD_REQUEST_400);
- response.setDetailMessage("Unrecognized HTTP method.");
- }
- } catch (JsonProcessingException jpex) {
- response.setStatus(HttpStatus.BAD_REQUEST_400);
- response.setDetailMessage("Could not decode/encode JSON payload. " + jpex.getMessage());
- LOG.info("Error processing JSON from client", jpex);
- } catch (Exception ex) {
- response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR_500);
- response.setDetailMessage(ex.toString());
- LOG.info("Error processing client request", ex);
- }
- }
-
- public void writeJson (Response response, Object object) throws IOException {
- mapper.writeValue(response.getOutputStream(), object);
- }
-
-}
diff --git a/src/main/java/org/opentripplanner/analyst/broker/BrokerMain.java b/src/main/java/org/opentripplanner/analyst/broker/BrokerMain.java
deleted file mode 100644
index 255ffc504a4..00000000000
--- a/src/main/java/org/opentripplanner/analyst/broker/BrokerMain.java
+++ /dev/null
@@ -1,90 +0,0 @@
-package org.opentripplanner.analyst.broker;
-
-import org.glassfish.grizzly.http.server.HttpServer;
-import org.glassfish.grizzly.http.server.NetworkListener;
-import org.glassfish.grizzly.strategies.SameThreadIOStrategy;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.net.BindException;
-import java.util.Properties;
-
-// benchmark: $ ab -n 2000 -k -c 100 http://localhost:9001/
-
-// TODO Merge with Broker
-public class BrokerMain implements Runnable {
-
- private static final Logger LOG = LoggerFactory.getLogger(BrokerMain.class);
-
- private static final int DEFAULT_PORT = 9001;
-
- private static final String DEFAULT_BIND_ADDRESS = "0.0.0.0";
-
- Properties config = new Properties();
-
- public Broker broker;
-
- public static void main(String[] args) {
-
- File cfg;
- if (args.length > 0)
- cfg = new File(args[0]);
- else
- cfg = new File("broker.conf");
-
- if (!cfg.exists()) {
- LOG.error("Broker configuration file {} not found", cfg);
- return;
- }
-
- Properties brokerConfig = new Properties();
- try {
- FileInputStream is = new FileInputStream(cfg);
- brokerConfig.load(is);
- is.close();
- } catch (IOException e) {
- LOG.error("Error reading config file {}", e);
- return;
- }
-
- // Create instance and run in the current thread.
- new BrokerMain(brokerConfig).run();
-
- }
-
- public BrokerMain(Properties brokerConfig) {
- this.config = brokerConfig;
- }
-
- public void run() {
- int port = config.getProperty("port") != null ? Integer.parseInt(config.getProperty("port")) : DEFAULT_PORT;
- String addr = config.getProperty("bind-address") != null ? config.getProperty("bind-address") : DEFAULT_BIND_ADDRESS;
- LOG.info("Starting analyst broker on port {} of interface {}", port, addr);
- HttpServer httpServer = new HttpServer();
- NetworkListener networkListener = new NetworkListener("broker", addr, port);
- // We avoid blocking IO, and the following line allows us to see closed connections.
- networkListener.getTransport().setIOStrategy(SameThreadIOStrategy.getInstance());
- httpServer.addListener(networkListener);
- // Bypass Jersey etc. and add a low-level Grizzly handler.
- // As in servlets, * is needed in base path to identify the "rest" of the path.
- broker = new Broker(config, addr, port);
- httpServer.getServerConfiguration().addHttpHandler(new BrokerHttpHandler(broker), "/*");
- try {
- httpServer.start();
- LOG.info("Broker running.");
- broker.run(); // run queue broker task pump in this thread
- Thread.currentThread().join();
- } catch (BindException be) {
- LOG.error("Cannot bind to port {}. Is it already in use?", port);
- } catch (IOException ioe) {
- LOG.error("IO exception while starting server.");
- } catch (InterruptedException ie) {
- LOG.info("Interrupted, shutting down.");
- }
- httpServer.shutdown();
- }
-
-}
diff --git a/src/main/java/org/opentripplanner/analyst/cluster/AnalystWorker.java b/src/main/java/org/opentripplanner/analyst/cluster/AnalystWorker.java
deleted file mode 100644
index 6cdddde56ba..00000000000
--- a/src/main/java/org/opentripplanner/analyst/cluster/AnalystWorker.java
+++ /dev/null
@@ -1,661 +0,0 @@
-package org.opentripplanner.analyst.cluster;
-
-import com.amazonaws.regions.Region;
-import com.amazonaws.regions.Regions;
-import com.amazonaws.services.s3.AmazonS3;
-import com.amazonaws.services.s3.AmazonS3Client;
-import com.conveyal.geojson.GeoJsonModule;
-import com.fasterxml.jackson.core.JsonParser;
-import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.core.type.TypeReference;
-import com.fasterxml.jackson.databind.DeserializationFeature;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.google.common.cache.Cache;
-import com.google.common.cache.CacheBuilder;
-import org.apache.http.HttpEntity;
-import org.apache.http.HttpResponse;
-import org.apache.http.client.HttpClient;
-import org.apache.http.client.config.RequestConfig;
-import org.apache.http.client.methods.HttpDelete;
-import org.apache.http.client.methods.HttpGet;
-import org.apache.http.client.methods.HttpPost;
-import org.apache.http.config.SocketConfig;
-import org.apache.http.conn.HttpHostConnectException;
-import org.apache.http.entity.ByteArrayEntity;
-import org.apache.http.impl.client.HttpClients;
-import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
-import org.apache.http.message.BasicHeader;
-import org.apache.http.util.EntityUtils;
-import org.opentripplanner.analyst.PointSet;
-import org.opentripplanner.analyst.SampleSet;
-import org.opentripplanner.api.model.FeedScopedIdSerializer;
-import org.opentripplanner.api.model.JodaLocalDateSerializer;
-import org.opentripplanner.api.model.QualifiedModeSetSerializer;
-import org.opentripplanner.api.model.TraverseModeSetSerializer;
-import org.opentripplanner.common.MavenVersion;
-import org.opentripplanner.profile.RaptorWorkerData;
-import org.opentripplanner.profile.RepeatedRaptorProfileRouter;
-import org.opentripplanner.routing.graph.Graph;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.OutputStream;
-import java.io.PipedInputStream;
-import java.io.PipedOutputStream;
-import java.net.SocketTimeoutException;
-import java.net.URI;
-import java.time.LocalDateTime;
-import java.time.format.DateTimeFormatter;
-import java.util.List;
-import java.util.Properties;
-import java.util.Random;
-import java.util.UUID;
-import java.util.concurrent.ArrayBlockingQueue;
-import java.util.concurrent.RejectedExecutionException;
-import java.util.concurrent.ThreadPoolExecutor;
-import java.util.concurrent.TimeUnit;
-import java.util.zip.GZIPOutputStream;
-
-/**
- *
- */
-public class AnalystWorker implements Runnable {
-
- /**
- * worker ID - just a random ID so we can differentiate machines used for computation.
- * Useful to isolate the logs from a particular machine, as well as to evaluate any
- * variation in performance coming from variation in the performance of the underlying
- * VMs.
- *
- * This needs to be static so the logger can access it; see the static member class
- * WorkerIdDefiner. A side effect is that only one worker can run in a given JVM. It also
- * needs to be defined before the logger is defined, so that it is initialized before the
- * logger is.
- */
- public static final String machineId = UUID.randomUUID().toString().replaceAll("-", "");
-
- private static final Logger LOG = LoggerFactory.getLogger(AnalystWorker.class);
-
- public static final String WORKER_ID_HEADER = "X-Worker-Id";
-
- public static final int POLL_TIMEOUT = 10 * 1000;
-
- /**
- * If this value is non-negative, the worker will not actually do any work. It will just report all tasks
- * as completed immediately, but will fail to do so on the given percentage of tasks. This is used in testing task
- * re-delivery and overall broker sanity.
- */
- public int dryRunFailureRate = -1;
-
- /** How long (minimum, in milliseconds) should this worker stay alive after receiving a single point request? */
- public static final int SINGLE_POINT_KEEPALIVE = 15 * 60 * 1000;
-
- /** should this worker shut down automatically */
- public final boolean autoShutdown;
-
- public static final Random random = new Random();
-
- private TaskStatisticsStore statsStore;
-
- /** is there currently a channel open to the broker to receive single point jobs? */
- private volatile boolean sideChannelOpen = false;
-
- ObjectMapper objectMapper;
-
- String BROKER_BASE_URL = "http://localhost:9001";
-
- static final HttpClient httpClient;
-
- /** Cache RAPTOR data by Job ID */
- private Cache workerDataCache = CacheBuilder.newBuilder()
- .maximumSize(200)
- .build();
-
- static {
- PoolingHttpClientConnectionManager mgr = new PoolingHttpClientConnectionManager();
- mgr.setDefaultMaxPerRoute(20);
-
- int timeout = 10 * 1000;
- SocketConfig cfg = SocketConfig.custom()
- .setSoTimeout(timeout)
- .build();
- mgr.setDefaultSocketConfig(cfg);
-
- httpClient = HttpClients.custom()
- .setConnectionManager(mgr)
- .build();
- }
-
- // Of course this will eventually need to be shared between multiple AnalystWorker threads.
- ClusterGraphBuilder clusterGraphBuilder;
-
- // Of course this will eventually need to be shared between multiple AnalystWorker threads.
- PointSetDatastore pointSetDatastore;
-
- // Clients for communicating with Amazon web services
- AmazonS3 s3;
-
- String graphId = null;
- long startupTime, nextShutdownCheckTime;
-
- // Region awsRegion = Region.getRegion(Regions.EU_CENTRAL_1);
- Region awsRegion = Region.getRegion(Regions.US_EAST_1);
-
- /** aws instance type, or null if not running on AWS */
- private String instanceType;
-
- long lastHighPriorityRequestProcessed = 0;
-
- /**
- * Queue for high-priority tasks. Should be plenty long enough to hold all that have come in -
- * we don't need to block on polling the manager.
- */
- private ThreadPoolExecutor highPriorityExecutor, batchExecutor;
-
- public AnalystWorker(Properties config) {
- // print out date on startup so that CloudWatch logs has a unique fingerprint
- LOG.info("Analyst worker starting at {}", LocalDateTime.now().format(DateTimeFormatter.ISO_DATE_TIME));
-
- // parse the configuration
- // set up the stats store
- String statsQueue = config.getProperty("statistics-queue");
- if (statsQueue != null)
- this.statsStore = new SQSTaskStatisticsStore(statsQueue);
- else
- // a stats store that does nothing.
- this.statsStore = s -> {};
-
- String addr = config.getProperty("broker-address");
- String port = config.getProperty("broker-port");
-
- if (addr != null) {
- if (port != null)
- this.BROKER_BASE_URL = String.format("http://%s:%s", addr, port);
- else
- this.BROKER_BASE_URL = String.format("http://%s", addr);
- }
-
- // set the initial graph affinity of this worker (if it is not in the config file it will be
- // set to null, i.e. no graph affinity)
- // we don't actually build the graph now; this is just a hint to the broker as to what
- // graph this machine was intended to analyze.
- this.graphId = config.getProperty("initial-graph-id");
-
- this.pointSetDatastore = new PointSetDatastore(10, null, false, config.getProperty("pointsets-bucket"));
- this.clusterGraphBuilder = new ClusterGraphBuilder(config.getProperty("graphs-bucket"));
-
- Boolean autoShutdown = Boolean.parseBoolean(config.getProperty("auto-shutdown"));
- this.autoShutdown = autoShutdown == null ? false : autoShutdown;
-
- // Consider shutting this worker down once per hour, starting 55 minutes after it started up.
- startupTime = System.currentTimeMillis();
- nextShutdownCheckTime = startupTime + 55 * 60 * 1000;
-
- // When creating the S3 and SQS clients use the default credentials chain.
- // This will check environment variables and ~/.aws/credentials first, then fall back on
- // the auto-assigned IAM role if this code is running on an EC2 instance.
- // http://docs.aws.amazon.com/AWSSdkDocsJava/latest/DeveloperGuide/java-dg-roles.html
- s3 = new AmazonS3Client();
- s3.setRegion(awsRegion);
-
- /* The ObjectMapper (de)serializes JSON. */
- objectMapper = new ObjectMapper();
- objectMapper.configure(JsonParser.Feature.ALLOW_COMMENTS, true);
- objectMapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
- objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); // ignore JSON fields that don't match target type
-
- /* Tell Jackson how to (de)serialize AgencyAndIds, which appear as map keys in routing requests. */
- objectMapper.registerModule(FeedScopedIdSerializer.makeModule());
-
- /* serialize/deserialize qualified mode sets */
- objectMapper.registerModule(QualifiedModeSetSerializer.makeModule());
-
- /* serialize/deserialize Joda dates */
- objectMapper.registerModule(JodaLocalDateSerializer.makeModule());
-
- /* serialize/deserialize traversemodesets */
- objectMapper.registerModule(TraverseModeSetSerializer.makeModule());
-
- objectMapper.registerModule(new GeoJsonModule());
-
- instanceType = getInstanceType();
- }
-
- /**
- * This is the main worker event loop which fetches tasks from a broker and schedules them for execution.
- * It maintains a small local queue on the worker so that it doesn't idle while fetching new tasks.
- */
- @Override
- public void run() {
- // create executors with up to one thread per processor
- int nP = Runtime.getRuntime().availableProcessors();
- highPriorityExecutor = new ThreadPoolExecutor(1, nP, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(255));
- highPriorityExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
- batchExecutor = new ThreadPoolExecutor(1, nP, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(nP * 2));
- batchExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
-
- // Build a graph on startup, iff a graph ID was provided.
- if (graphId != null) {
- LOG.info("Prebuilding graph {}", graphId);
- Graph graph = clusterGraphBuilder.getGraph(graphId);
- // also prebuild the stop tree cache
- graph.index.getStopTreeCache();
- LOG.info("Done prebuilding graph {}", graphId);
- }
-
- // Start filling the work queues.
- boolean idle = false;
- while (true) {
- long now = System.currentTimeMillis();
- // Consider shutting down if enough time has passed
- if (now > nextShutdownCheckTime && autoShutdown) {
- if (idle && now > lastHighPriorityRequestProcessed + SINGLE_POINT_KEEPALIVE) {
- LOG.warn("Machine is idle, shutting down.");
- try {
- Process process = new ProcessBuilder("sudo", "/sbin/shutdown", "-h", "now")
- .start();
- process.waitFor();
- } catch (Exception ex) {
- LOG.error("Unable to terminate worker", ex);
- } finally {
- System.exit(0);
- }
- }
- nextShutdownCheckTime += 60 * 60 * 1000;
- }
- LOG.info("Long-polling for work ({} second timeout).", POLL_TIMEOUT / 1000.0);
- // Long-poll (wait a few seconds for messages to become available)
- List tasks = getSomeWork(WorkType.BATCH);
- if (tasks == null) {
- LOG.info("Didn't get any work. Retrying.");
- idle = true;
- continue;
- }
-
- // run through high-priority tasks first to ensure they are enqueued even if the batch
- // queue blocks.
- tasks.stream().filter(t -> t.outputLocation == null)
- .forEach(t -> highPriorityExecutor.execute(() -> {
- LOG.warn(
- "Handling single point request via normal channel, side channel should open shortly.");
- this.handleOneRequest(t);
- }));
-
- logQueueStatus();
-
- // enqueue low-priority tasks; note that this may block anywhere in the process
- tasks.stream().filter(t -> t.outputLocation != null)
- .forEach(t -> {
- // attempt to enqueue, waiting if the queue is full
- while (true) {
- try {
- batchExecutor.execute(() -> this.handleOneRequest(t));
- break;
- } catch (RejectedExecutionException e) {
- // queue is full, wait 200ms and try again
- try {
- Thread.sleep(200);
- } catch (InterruptedException e1) { /* nothing */}
- }
- }
- });
-
- logQueueStatus();
-
- idle = false;
- }
- }
-
- /**
- * This is the callback that processes a single task and returns the results upon completion.
- * It may be called several times simultaneously on different executor threads.
- */
- private void handleOneRequest(AnalystClusterRequest clusterRequest) {
-
- if (dryRunFailureRate >= 0) {
- // This worker is running in test mode.
- // It should report all work as completed without actually doing anything,
- // but will fail a certain percentage of the time.
- if (random.nextInt(100) >= dryRunFailureRate) {
- // Pretend to succeed.
- deleteRequest(clusterRequest);
- } else {
- LOG.info("Intentionally failing on task {}", clusterRequest.taskId);
- }
- return;
- }
-
- try {
- long startTime = System.currentTimeMillis();
- LOG.info("Handling message {}", clusterRequest.toString());
-
- // We need to distinguish between and handle four different types of requests here:
- // Either vector isochrones or accessibility to a pointset,
- // as either a single-origin priority request (where the result is returned immediately)
- // or a job task (where the result is saved to output location on S3).
- boolean isochrone = (clusterRequest.destinationPointsetId == null);
- boolean singlePoint = (clusterRequest.outputLocation == null);
- boolean transit = (clusterRequest.profileRequest.transitModes != null && clusterRequest.profileRequest.transitModes.isTransit());
-
- if (singlePoint) {
- lastHighPriorityRequestProcessed = startTime;
- if (!sideChannelOpen) {
- openSideChannel();
- }
- }
-
- TaskStatistics ts = new TaskStatistics();
- ts.pointsetId = clusterRequest.destinationPointsetId;
- ts.graphId = clusterRequest.graphId;
- ts.awsInstanceType = instanceType;
- ts.jobId = clusterRequest.jobId;
- ts.workerId = machineId;
- ts.single = singlePoint;
-
- // Get the graph object for the ID given in the request, fetching inputs and building as needed.
- // All requests handled together are for the same graph, and this call is synchronized so the graph will
- // only be built once.
- long graphStartTime = System.currentTimeMillis();
- Graph graph = clusterGraphBuilder.getGraph(clusterRequest.graphId);
- graphId = clusterRequest.graphId; // Record graphId so we "stick" to this same graph on subsequent polls
- ts.graphBuild = (int) (System.currentTimeMillis() - graphStartTime);
- ts.graphTripCount = graph.index.patternForTrip.size();
- ts.graphStopCount = graph.index.stopForId.size();
- ts.lon = clusterRequest.profileRequest.fromLon;
- ts.lat = clusterRequest.profileRequest.fromLat;
-
- final SampleSet sampleSet;
-
- // If this one-to-many request is for accessibility information based on travel times to a pointset,
- // fetch the set of points we will use as destinations.
- if (isochrone) {
- // This is an isochrone request, tell the RepeatedRaptorProfileRouter there are no targets.
- sampleSet = null;
- } else {
- // This is not an isochrone request. There is necessarily a destination point set supplied.
- PointSet pointSet = pointSetDatastore.get(clusterRequest.destinationPointsetId);
- sampleSet = pointSet.getOrCreateSampleSet(graph); // TODO this breaks if graph has been rebuilt
- }
-
- // Note that all parameters to create the Raptor worker data are passed in the constructor except ts.
- // Why not pass in ts as well since this is a throwaway calculator?
- RepeatedRaptorProfileRouter router =
- new RepeatedRaptorProfileRouter(graph, clusterRequest.profileRequest, sampleSet);
- router.ts = ts;
-
- // Produce RAPTOR data tables, going through a cache where relevant.
- // This is only used for multi-point requests. Single-point requests are assumed to be continually
- // changing, so we create throw-away RAPTOR tables for them.
- // Ideally we'd want this cacheing to happen transparently inside the RepeatedRaptorProfileRouter,
- // but the RepeatedRaptorProfileRouter doesn't know the job ID or other information from the cluster request.
- // It would be possible to just supply the cache _key_ as a way of saying that the cache should be used.
- // But then we'd need to pass in both the cache and the key, which is weird.
- if (transit && !singlePoint) {
- long dataStart = System.currentTimeMillis();
- router.raptorWorkerData = workerDataCache.get(clusterRequest.jobId, () -> RepeatedRaptorProfileRouter
- .getRaptorWorkerData(clusterRequest.profileRequest, graph, sampleSet, ts));
- ts.raptorData = (int) (System.currentTimeMillis() - dataStart);
- } else {
- // The worker will generate a one-time throw-away table.
- router.raptorWorkerData = null;
- }
-
- // Run the core repeated-raptor analysis.
- // This result envelope will contain the results of the one-to-many profile or single-departure-time search.
- ResultEnvelope envelope = new ResultEnvelope();
- try {
- // TODO when router runs, if there are no transit modes defined it should just skip the transit work.
- router.includeTimes = clusterRequest.includeTimes;
- envelope = router.route();
- envelope.id = clusterRequest.id;
- ts.success = true;
- } catch (Exception ex) {
- // An error occurred. Leave the envelope empty and TODO include error information.
- LOG.error("Error occurred in profile request", ex);
- ts.success = false;
- }
-
- // Send the ResultEnvelope back to the user.
- // The results are either stored on S3 (for multi-origin jobs) or sent back through the broker (for
- // immediate interactive display of isochrones).
- envelope.id = clusterRequest.id;
- envelope.jobId = clusterRequest.jobId;
- envelope.destinationPointsetId = clusterRequest.destinationPointsetId;
- if (clusterRequest.outputLocation != null) {
- // Convert the result envelope and its contents to JSON and gzip it in this thread.
- // Transfer the results to Amazon S3 in another thread, piping between the two.
- String s3key = String.join("/", clusterRequest.jobId, clusterRequest.id + ".json.gz");
- PipedInputStream inPipe = new PipedInputStream();
- PipedOutputStream outPipe = new PipedOutputStream(inPipe);
- new Thread(() -> {
- s3.putObject(clusterRequest.outputLocation, s3key, inPipe, null);
- }).start();
- OutputStream gzipOutputStream = new GZIPOutputStream(outPipe);
- // We could do the writeValue() in a thread instead, in which case both the DELETE and S3 options
- // could consume it in the same way.
- objectMapper.writeValue(gzipOutputStream, envelope);
- gzipOutputStream.close();
- // Tell the broker the task has been handled and should not be re-delivered to another worker.
- deleteRequest(clusterRequest);
- } else {
- // No output location was provided. Instead of saving the result on S3,
- // return the result immediately via a connection held open by the broker and mark the task completed.
- finishPriorityTask(clusterRequest, envelope);
- }
-
- // Record information about the current task so we can analyze usage and efficiency over time.
- ts.total = (int) (System.currentTimeMillis() - startTime);
- statsStore.store(ts);
-
- } catch (Exception ex) {
- LOG.error("An error occurred while routing", ex);
- }
-
- }
-
- /** Open a single point channel to the broker to receive high-priority requests immediately */
- private synchronized void openSideChannel () {
- if (sideChannelOpen) {
- return;
- }
- LOG.info("Opening side channel for single point requests.");
- new Thread(() -> {
- sideChannelOpen = true;
- // don't keep single point connections alive forever
- while (System.currentTimeMillis() < lastHighPriorityRequestProcessed + SINGLE_POINT_KEEPALIVE) {
- LOG.info("Awaiting high-priority work");
- try {
- List tasks = getSomeWork(WorkType.HIGH_PRIORITY);
-
- if (tasks != null)
- tasks.stream().forEach(t -> highPriorityExecutor.execute(
- () -> this.handleOneRequest(t)));
-
- logQueueStatus();
- } catch (Exception e) {
- LOG.error("Unexpected exception getting single point work", e);
- }
- }
- sideChannelOpen = false;
- }).start();
- }
-
- public List getSomeWork(WorkType type) {
-
- // Run a POST request (long-polling for work) indicating which graph this worker prefers to work on
- String url;
- if (type == WorkType.HIGH_PRIORITY) {
- // this is a side-channel request for single point work
- url = BROKER_BASE_URL + "/single/" + graphId;
- } else {
- url = BROKER_BASE_URL + "/dequeue/" + graphId;
- }
- HttpPost httpPost = new HttpPost(url);
- httpPost.setHeader(new BasicHeader(WORKER_ID_HEADER, machineId));
- HttpResponse response = null;
- try {
- response = httpClient.execute(httpPost);
- HttpEntity entity = response.getEntity();
- if (entity == null) {
- return null;
- }
- if (response.getStatusLine().getStatusCode() != 200) {
- EntityUtils.consumeQuietly(entity);
- return null;
- }
- return objectMapper.readValue(entity.getContent(), new TypeReference>() {
- });
- } catch (JsonProcessingException e) {
- LOG.error("JSON processing exception while getting work", e);
- } catch (SocketTimeoutException stex) {
- LOG.error("Socket timeout while waiting to receive work.");
- } catch (HttpHostConnectException ce) {
- LOG.error("Broker refused connection. Sleeping before retry.");
- try {
- Thread.currentThread().sleep(5000);
- } catch (InterruptedException e) {
- }
- } catch (IOException e) {
- LOG.error("IO exception while getting work", e);
- }
- return null;
-
- }
-
- /**
- * Signal the broker that the given high-priority task is completed, providing a result.
- */
- public void finishPriorityTask(AnalystClusterRequest clusterRequest, Object result) {
- String url = BROKER_BASE_URL + String.format("/complete/priority/%s", clusterRequest.taskId);
- HttpPost httpPost = new HttpPost(url);
- try {
- // TODO reveal any errors etc. that occurred on the worker.
- // Really this should probably be done with an InputStreamEntity and a JSON writer thread.
- byte[] serializedResult = objectMapper.writeValueAsBytes(result);
- httpPost.setEntity(new ByteArrayEntity(serializedResult));
- HttpResponse response = httpClient.execute(httpPost);
- // Signal the http client library that we're done with this response object, allowing connection reuse.
- EntityUtils.consumeQuietly(response.getEntity());
- if (response.getStatusLine().getStatusCode() == 200) {
- LOG.info("Successfully marked task {} as completed.", clusterRequest.taskId);
- } else if (response.getStatusLine().getStatusCode() == 404) {
- LOG.info("Task {} was not marked as completed because it doesn't exist.", clusterRequest.taskId);
- } else {
- LOG.info("Failed to mark task {} as completed, ({}).", clusterRequest.taskId,
- response.getStatusLine());
- }
- } catch (Exception e) {
- LOG.warn("Failed to mark task {} as completed.", clusterRequest.taskId, e);
- }
- }
-
- /**
- * Tell the broker that the given message has been successfully processed by a worker (HTTP DELETE).
- */
- public void deleteRequest(AnalystClusterRequest clusterRequest) {
- String url = BROKER_BASE_URL + String.format("/tasks/%s", clusterRequest.taskId);
- HttpDelete httpDelete = new HttpDelete(url);
- try {
- HttpResponse response = httpClient.execute(httpDelete);
- // Signal the http client library that we're done with this response object, allowing connection reuse.
- EntityUtils.consumeQuietly(response.getEntity());
- if (response.getStatusLine().getStatusCode() == 200) {
- LOG.info("Successfully deleted task {}.", clusterRequest.taskId);
- } else {
- LOG.info("Failed to delete task {} ({}).", clusterRequest.taskId, response.getStatusLine());
- }
- } catch (Exception e) {
- LOG.warn("Failed to delete task {}", clusterRequest.taskId, e);
- }
- }
-
- /** Get the AWS instance type if applicable */
- public String getInstanceType () {
- try {
- HttpGet get = new HttpGet();
- // see http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-metadata.html
- // This seems very much not EC2-like to hardwire an IP address for getting instance metadata,
- // but that's how it's done.
- get.setURI(new URI("http://169.254.169.254/latest/meta-data/instance-type"));
- get.setConfig(RequestConfig.custom()
- .setConnectTimeout(2000)
- .setSocketTimeout(2000)
- .build()
- );
-
- HttpResponse res = httpClient.execute(get);
-
- InputStream is = res.getEntity().getContent();
- BufferedReader reader = new BufferedReader(new InputStreamReader(is));
- String type = reader.readLine().trim();
- reader.close();
- return type;
- } catch (Exception e) {
- LOG.info("could not retrieve EC2 instance type, you may be running outside of EC2.");
- return null;
- }
- }
-
- /** log queue status */
- private void logQueueStatus() {
- LOG.info("Waiting tasks: high priority: {}, batch: {}", highPriorityExecutor.getQueue().size(), batchExecutor.getQueue().size());
- }
-
- /**
- * Requires a worker configuration, which is a Java Properties file with the following
- * attributes.
- *
- * broker-address address of the broker, without protocol or port
- * broker port port broker is running on, default 80.
- * graphs-bucket S3 bucket in which graphs are stored.
- * pointsets-bucket S3 bucket in which pointsets are stored
- * auto-shutdown Should this worker shut down its machine if it is idle (e.g. on throwaway cloud instances)
- * statistics-queue SQS queue to which to send statistics (optional)
- * initial-graph-id The graph ID for this worker to start on
- */
- public static void main(String[] args) {
- LOG.info("Starting analyst worker");
- LOG.info("OTP commit is {}", MavenVersion.VERSION.commit);
-
- Properties config = new Properties();
-
- try {
- File cfg;
- if (args.length > 0)
- cfg = new File(args[0]);
- else
- cfg = new File("worker.conf");
-
- InputStream cfgis = new FileInputStream(cfg);
- config.load(cfgis);
- cfgis.close();
- } catch (Exception e) {
- LOG.info("Error loading worker configuration", e);
- return;
- }
-
- if (Boolean.parseBoolean(config.getProperty("use-transport-networks", "false"))) {
- // start R5 to work with transport networks
- LOG.info("Transport network support enabled, deferring computation to R5");
- com.conveyal.r5.analyst.cluster.AnalystWorker.main(args);
- }
- else {
- try {
- new AnalystWorker(config).run();
- } catch (Exception e) {
- LOG.error("Error in analyst worker", e);
- return;
- }
- }
- }
-
- public static enum WorkType {
- HIGH_PRIORITY, BATCH;
- }
-}
\ No newline at end of file
diff --git a/src/main/java/org/opentripplanner/analyst/cluster/ClusterGraphBuilder.java b/src/main/java/org/opentripplanner/analyst/cluster/ClusterGraphBuilder.java
deleted file mode 100644
index 2918981cf82..00000000000
--- a/src/main/java/org/opentripplanner/analyst/cluster/ClusterGraphBuilder.java
+++ /dev/null
@@ -1,104 +0,0 @@
-package org.opentripplanner.analyst.cluster;
-
-import com.amazonaws.services.s3.AmazonS3Client;
-import com.amazonaws.services.s3.model.S3Object;
-import org.apache.commons.io.IOUtils;
-import org.opentripplanner.graph_builder.GraphBuilder;
-import org.opentripplanner.routing.graph.Graph;
-import org.opentripplanner.standalone.CommandLineParameters;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.OutputStream;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipInputStream;
-
-/**
- * Builds and caches graphs as well as the inputs they are built from for use in Analyst Cluster workers.
- */
-public class ClusterGraphBuilder {
-
- private static final Logger LOG = LoggerFactory.getLogger(ClusterGraphBuilder.class);
-
- private AmazonS3Client s3 = new AmazonS3Client();
-
- private static final String GRAPH_CACHE_DIR = "graph_cache";
-
- private final String graphBucket;
-
- String currGraphId = null;
-
- Graph currGraph = null;
-
- public ClusterGraphBuilder (String graphBucket) {
- this.graphBucket = graphBucket;
- }
-
- /**
- * Return the graph for the given unique identifier for graph builder inputs on S3.
- * If this is the same as the last graph built, just return the pre-built graph.
- * If not, build the graph from the inputs, fetching them from S3 to the local cache as needed.
- */
- public synchronized Graph getGraph(String graphId) {
-
- LOG.info("Finding a graph for ID {}", graphId);
-
- if (graphId.equals(currGraphId)) {
- LOG.info("GraphID has not changed. Reusing the last graph that was built.");
- return currGraph;
- }
-
- // The location of the inputs that will be used to build this graph
- File graphDataDirectory = new File(GRAPH_CACHE_DIR, graphId);
-
- // If we don't have a local copy of the inputs, fetch graph data as a ZIP from S3 and unzip it
- if( ! graphDataDirectory.exists() || graphDataDirectory.list().length == 0) {
- LOG.info("Downloading graph input files.");
- graphDataDirectory.mkdirs();
- S3Object graphDataZipObject = s3.getObject(graphBucket, graphId + ".zip");
- ZipInputStream zis = new ZipInputStream(graphDataZipObject.getObjectContent());
- try {
- ZipEntry entry;
- while ((entry = zis.getNextEntry()) != null) {
- File entryDestination = new File(graphDataDirectory, entry.getName());
- // Are both these mkdirs calls necessary?
- entryDestination.getParentFile().mkdirs();
- if (entry.isDirectory())
- entryDestination.mkdirs();
- else {
- OutputStream entryFileOut = new FileOutputStream(entryDestination);
- IOUtils.copy(zis, entryFileOut);
- entryFileOut.close();
- }
- }
- zis.close();
- } catch (Exception e) {
- // TODO delete graph cache dir which is probably corrupted
- LOG.info("Error retrieving graph files", e);
- }
- } else {
- LOG.info("Graph input files were found locally. Using these files from the cache.");
- }
-
- // Now we have a local copy of these graph inputs. Make a graph out of them.
- CommandLineParameters params = new CommandLineParameters();
- params.build = new File(GRAPH_CACHE_DIR, graphId);
- params.inMemory = true;
- GraphBuilder graphBuilder = GraphBuilder.forDirectory(params, params.build);
- graphBuilder.run();
- Graph graph = graphBuilder.getGraph();
- graph.routerId = graphId;
- // re-index the graph to ensure all data is added and recreate a new streetIndex. It's ok to recreate the
- // streetIndex because the previous one created during graph build is not needed anymore and isn't able to be
- // used outside of the graphBuilder.
- graph.index(true);
- graph.index.clusterStopsAsNeeded();
- this.currGraphId = graphId;
- this.currGraph = graph;
- return graph;
-
- }
-
-}
diff --git a/src/main/java/org/opentripplanner/analyst/cluster/ClusterGraphService.java b/src/main/java/org/opentripplanner/analyst/cluster/ClusterGraphService.java
deleted file mode 100644
index dc57ca137a1..00000000000
--- a/src/main/java/org/opentripplanner/analyst/cluster/ClusterGraphService.java
+++ /dev/null
@@ -1,334 +0,0 @@
-package org.opentripplanner.analyst.cluster;
-
-import com.amazonaws.AmazonServiceException;
-import com.amazonaws.auth.AWSCredentials;
-import com.amazonaws.auth.profile.ProfileCredentialsProvider;
-import com.amazonaws.services.s3.AmazonS3Client;
-import com.amazonaws.services.s3.model.S3Object;
-import com.google.common.collect.Maps;
-import org.apache.commons.io.FileUtils;
-import org.apache.commons.io.IOUtils;
-import org.opentripplanner.graph_builder.GraphBuilder;
-import org.opentripplanner.routing.graph.Graph;
-import org.opentripplanner.routing.services.GraphService;
-import org.opentripplanner.routing.services.GraphSource;
-import org.opentripplanner.routing.services.GraphSource.Factory;
-import org.opentripplanner.standalone.CommandLineParameters;
-import org.opentripplanner.standalone.Router;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.*;
-import java.util.Collection;
-import java.util.Enumeration;
-import java.util.Map;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipException;
-import java.util.zip.ZipFile;
-import java.util.zip.ZipOutputStream;
-
-// TODO does not really need to extend GraphService
-public class ClusterGraphService extends GraphService {
-
- static File GRAPH_DIR = new File("cache", "graphs");
-
- private String graphBucket;
-
- private Boolean workOffline = false;
- private AmazonS3Client s3;
-
- private static final Logger LOG = LoggerFactory.getLogger(GraphService.class);
-
- // don't use more than 60% of free memory to cache graphs
- private Map graphMap = Maps.newConcurrentMap();
-
- @Override
- public synchronized Router getRouter(String graphId) {
-
- GRAPH_DIR.mkdirs();
-
- if(!graphMap.containsKey(graphId)) {
-
- try {
- if (!bucketCached(graphId)) {
- if(!workOffline) {
- downloadGraphSourceFiles(graphId, GRAPH_DIR);
- }
- }
- } catch (IOException e) {
- LOG.error("exception finding graph {}", graphId, e);
- }
-
- CommandLineParameters params = new CommandLineParameters();
- params.build = new File(GRAPH_DIR, graphId);
- params.inMemory = true;
- GraphBuilder gbt = GraphBuilder.forDirectory(params, params.build);
- gbt.run();
-
- Graph g = gbt.getGraph();
-
- g.routerId = graphId;
-
- // re-index the graph to ensure all data is added and recreate a new streetIndex. It's ok to recreate the
- // streetIndex because the previous one created during graph build is not needed anymore and isn't able to
- // be used outside of the graphBuilder.
- g.index(true);
-
- g.index.clusterStopsAsNeeded();
-
- Router r = new Router(graphId, g);
-
- // temporarily disable graph caching so we don't run out of RAM.
- // Long-term we will use an actual cache for this.
- //graphMap.put(graphId,r);
-
- return r;
-
- }
- else {
- return graphMap.get(graphId);
- }
- }
-
- public ClusterGraphService(String s3CredentialsFilename, Boolean workOffline, String bucket) {
-
- if(!workOffline) {
- if (s3CredentialsFilename != null) {
- AWSCredentials creds = new ProfileCredentialsProvider(s3CredentialsFilename, "default").getCredentials();
- s3 = new AmazonS3Client(creds);
- }
- else {
- // This will first check for credentials in environment variables or ~/.aws/credentials
- // then fall back on S3 credentials propagated to EC2 instances via IAM roles.
- s3 = new AmazonS3Client();
- }
-
- this.graphBucket = bucket;
- }
-
- this.workOffline = workOffline;
- }
-
- // adds either a zip file or graph directory to S3, or local cache for offline use
- public void addGraphFile(File graphFile) throws IOException {
-
- String graphId = graphFile.getName();
-
- if(graphId.endsWith(".zip"))
- graphId = graphId.substring(0, graphId.length() - 4);
-
- File graphDir = new File(GRAPH_DIR, graphId);
-
- if (graphDir.exists()) {
- if (graphDir.list().length == 0) {
- graphDir.delete();
- }
- else {
- return;
- }
- }
-
- // if we're here the directory has either been deleted or never existed
- graphDir.mkdirs();
-
- File graphDataZip = new File(GRAPH_DIR, graphId + ".zip");
-
- // if directory zip contents store as zip
- // either way ensure there is an extracted copy in the local cache
- if(graphFile.isDirectory()) {
- FileUtils.copyDirectory(graphFile, graphDir);
-
- zipGraphDir(graphDir, graphDataZip);
- }
- else if(graphFile.getName().endsWith(".zip")) {
- FileUtils.copyFile(graphFile, graphDataZip);
- unpackGraphZip(graphDataZip, graphDir, false);
- }
- else {
- graphDataZip = null;
- }
-
- if(!workOffline && graphDataZip != null) {
- // only upload if it's not there already
- try {
- s3.getObject(graphBucket, graphId + ".zip");
- } catch (AmazonServiceException e) {
- s3.putObject(graphBucket, graphId+".zip", graphDataZip);
- }
- }
-
- graphDataZip.delete();
-
- }
-
- public synchronized File getZippedGraph(String graphId) throws IOException {
-
- File graphDataDir = new File(GRAPH_DIR, graphId);
-
- File graphZipFile = new File(GRAPH_DIR, graphId + ".zip");
-
- if(!graphDataDir.exists() && graphDataDir.isDirectory()) {
-
- FileOutputStream fileOutputStream = new FileOutputStream(graphZipFile);
- ZipOutputStream zipOutputStream = new ZipOutputStream(fileOutputStream);
-
- byte[] buffer = new byte[1024];
-
- for(File f : graphDataDir.listFiles()) {
- ZipEntry zipEntry = new ZipEntry(f.getName());
- zipOutputStream.putNextEntry(zipEntry);
- FileInputStream fileInput = new FileInputStream(f);
-
- int len;
- while ((len = fileInput.read(buffer)) > 0) {
- zipOutputStream.write(buffer, 0, len);
- }
-
- fileInput.close();
- zipOutputStream.closeEntry();
- }
-
- zipOutputStream.close();
-
- return graphZipFile;
-
- }
-
- return null;
-
- }
-
- private static boolean bucketCached(String graphId) throws IOException {
- File graphData = new File(GRAPH_DIR, graphId);
-
- // check if cached but only as zip
- if(!graphData.exists()) {
- File graphDataZip = new File(GRAPH_DIR, graphId + ".zip");
-
- if(graphDataZip.exists()) {
- zipGraphDir(graphData, graphDataZip);
- }
- }
-
-
- return graphData.exists() && graphData.isDirectory();
- }
-
- private void downloadGraphSourceFiles(String graphId, File dir) throws IOException {
-
- File graphCacheDir = dir;
- if (!graphCacheDir.exists())
- graphCacheDir.mkdirs();
-
- File graphZipFile = new File(graphCacheDir, graphId + ".zip");
-
- File extractedGraphDir = new File(graphCacheDir, graphId);
-
- if (extractedGraphDir.exists()) {
- FileUtils.deleteDirectory(extractedGraphDir);
- }
-
- extractedGraphDir.mkdirs();
-
- S3Object graphZip = s3.getObject(graphBucket, graphId+".zip");
-
- InputStream zipFileIn = graphZip.getObjectContent();
-
- OutputStream zipFileOut = new FileOutputStream(graphZipFile);
-
- IOUtils.copy(zipFileIn, zipFileOut);
- IOUtils.closeQuietly(zipFileIn);
- IOUtils.closeQuietly(zipFileOut);
-
- unpackGraphZip(graphZipFile, extractedGraphDir);
- }
-
- private static void unpackGraphZip(File graphZipFile, File extractedGraphDir) throws ZipException, IOException {
- // delete by default
- unpackGraphZip(graphZipFile, extractedGraphDir, true);
- }
-
- private static void unpackGraphZip(File graphZipFile, File extractedGraphDir, boolean delete) throws ZipException, IOException {
-
- ZipFile zipFile = new ZipFile(graphZipFile);
-
- Enumeration extends ZipEntry> entries = zipFile.entries();
-
- while (entries.hasMoreElements()) {
-
- ZipEntry entry = entries.nextElement();
- File entryDestination = new File(extractedGraphDir, entry.getName());
-
- entryDestination.getParentFile().mkdirs();
-
- if (entry.isDirectory())
- entryDestination.mkdirs();
- else {
- InputStream entryFileIn = zipFile.getInputStream(entry);
- OutputStream entryFileOut = new FileOutputStream(entryDestination);
- IOUtils.copy(entryFileIn, entryFileOut);
- IOUtils.closeQuietly(entryFileIn);
- IOUtils.closeQuietly(entryFileOut);
- }
- }
-
- zipFile.close();
-
- if (delete) {
- graphZipFile.delete();
- }
- }
-
- private static void zipGraphDir(File graphDirectory, File zipGraphFile) throws IOException {
-
- FileOutputStream fileOutputStream = new FileOutputStream(zipGraphFile);
- ZipOutputStream zipOutputStream = new ZipOutputStream(fileOutputStream);
-
- byte[] buffer = new byte[1024];
-
- for(File f : graphDirectory.listFiles()) {
- if (f.isDirectory())
- continue;
-
- ZipEntry zipEntry = new ZipEntry(f.getName());
- zipOutputStream.putNextEntry(zipEntry);
- FileInputStream fileInput = new FileInputStream(f);
-
- int len;
- while ((len = fileInput.read(buffer)) > 0) {
- zipOutputStream.write(buffer, 0, len);
- }
-
- fileInput.close();
- zipOutputStream.closeEntry();
- }
-
- zipOutputStream.close();
- }
-
-
- @Override
- public int evictAll() {
- graphMap.clear();
- return 0;
- }
-
- @Override
- public Collection getRouterIds() {
- return graphMap.keySet();
- }
-
- @Override
- public Factory getGraphSourceFactory() {
- return null;
- }
-
- @Override
- public boolean registerGraph(String arg0, GraphSource arg1) {
- return false;
- }
-
- @Override
- public void setDefaultRouterId(String arg0) {
- }
-}
diff --git a/src/main/java/org/opentripplanner/analyst/cluster/PointSetDatastore.java b/src/main/java/org/opentripplanner/analyst/cluster/PointSetDatastore.java
deleted file mode 100644
index 645c835d137..00000000000
--- a/src/main/java/org/opentripplanner/analyst/cluster/PointSetDatastore.java
+++ /dev/null
@@ -1,163 +0,0 @@
-package org.opentripplanner.analyst.cluster;
-
-import com.amazonaws.AmazonServiceException;
-import com.amazonaws.auth.AWSCredentials;
-import com.amazonaws.auth.profile.ProfileCredentialsProvider;
-import com.amazonaws.services.s3.AmazonS3Client;
-import com.amazonaws.services.s3.model.ObjectMetadata;
-import com.amazonaws.services.s3.model.S3Object;
-import com.google.common.cache.CacheBuilder;
-import com.google.common.cache.CacheLoader;
-import com.google.common.io.ByteStreams;
-import org.apache.commons.io.FileUtils;
-import org.opentripplanner.analyst.PointSet;
-import org.opentripplanner.analyst.PointSetCache;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.util.List;
-import java.util.zip.GZIPInputStream;
-import java.util.zip.GZIPOutputStream;
-
-/**
- * TODO what does this do? Does it really need to be a subclass?
- */
-public class PointSetDatastore extends PointSetCache {
-
- static private File POINT_DIR = new File("cache", "pointsets");
- private String pointsetBucket;
-
- private AmazonS3Client s3;
- private final Boolean workOffline;
-
- public PointSetDatastore(Integer maxCacheSize, String s3CredentialsFilename,
- Boolean workOffline, String pointsetBucket){
-
- super();
-
- // allow the data store to work offline with cached data and skip S3 connection
- this.workOffline = workOffline;
-
- this.pointsetBucket = pointsetBucket;
-
- if(!this.workOffline) {
- if (s3CredentialsFilename != null) {
- AWSCredentials creds = new ProfileCredentialsProvider(s3CredentialsFilename, "default").getCredentials();
- s3 = new AmazonS3Client(creds);
- }
- else {
- // default credentials providers, e.g. IAM role
- s3 = new AmazonS3Client();
- }
- }
-
- // set up the cache
- this.pointSets = CacheBuilder.newBuilder()
- .maximumSize(maxCacheSize)
- .build(new S3PointSetLoader(workOffline, s3, pointsetBucket));
- }
-
- // adds file to S3 Data store or offline cache (if working offline)
- public String addPointSet(File pointSetFile, String pointSetId) throws IOException {
- if (pointSetId == null)
- throw new NullPointerException("null point set id");
-
- File renamedPointSetFile = new File(POINT_DIR, pointSetId + ".json");
-
- if (renamedPointSetFile.exists())
- return pointSetId;
-
- FileUtils.copyFile(pointSetFile, renamedPointSetFile);
-
- if(!this.workOffline) {
- // only upload if it doesn't exist
- try {
- s3.getObjectMetadata(pointsetBucket, pointSetId + ".json.gz");
- } catch (AmazonServiceException e) {
- // gzip compression in storage, not because we're worried about file size but to speed file transfer
- FileInputStream fis = new FileInputStream(pointSetFile);
- File tempFile = File.createTempFile(pointSetId, ".json.gz");
- FileOutputStream fos = new FileOutputStream(tempFile);
- GZIPOutputStream gos = new GZIPOutputStream(fos);
-
- try {
- ByteStreams.copy(fis, gos);
- } finally {
- gos.close();
- fis.close();
- }
-
- s3.putObject(pointsetBucket, pointSetId + ".json.gz", tempFile);
- tempFile.delete();
- }
- }
-
- return pointSetId;
-
- }
-
- /** does this pointset exist in local cache? */
- public boolean isCached (String pointsetId) {
- return new File(POINT_DIR, pointsetId + ".json").exists();
- }
-
- /**
- * Load pointsets from S3.
- */
- protected static class S3PointSetLoader extends CacheLoader {
-
- private Boolean workOffline;
- private AmazonS3Client s3;
- private String pointsetBucket;
-
- /**
- * Construct a new point set loader. S3 clients are generally threadsafe, so it's fine to share them.
- */
- public S3PointSetLoader(Boolean workOffline, AmazonS3Client s3, String pointsetBucket) {
- this.workOffline = workOffline;
- this.s3 = s3;
- this.pointsetBucket = pointsetBucket;
- }
-
- @Override
- public PointSet load (String pointSetId) throws Exception {
-
- File cachedFile;
-
- if(!workOffline) {
- // get pointset metadata from S3
- cachedFile = new File(POINT_DIR, pointSetId + ".json");
- if(!cachedFile.exists()){
- POINT_DIR.mkdirs();
-
- S3Object obj = s3.getObject(pointsetBucket, pointSetId + ".json.gz");
- ObjectMetadata objMet = obj.getObjectMetadata();
- FileOutputStream fos = new FileOutputStream(cachedFile);
- GZIPInputStream gis = new GZIPInputStream(obj.getObjectContent());
- try {
- ByteStreams.copy(gis, fos);
- } finally {
- fos.close();
- gis.close();
- }
- }
- }
- else
- cachedFile = new File(POINT_DIR, pointSetId + ".json");
-
-
-
- // grab it from the cache
-
- return PointSet.fromGeoJson(cachedFile);
- }
- }
-
- @Override
- public List getPointSetIds() {
- // we have no clue what is in the S3 bucket.
- throw new UnsupportedOperationException("S3-backed point set datastore does not know what pointsets are available.");
- }
-}
diff --git a/src/main/java/org/opentripplanner/analyst/cluster/SQSTaskStatisticsStore.java b/src/main/java/org/opentripplanner/analyst/cluster/SQSTaskStatisticsStore.java
deleted file mode 100644
index d2599cc3803..00000000000
--- a/src/main/java/org/opentripplanner/analyst/cluster/SQSTaskStatisticsStore.java
+++ /dev/null
@@ -1,66 +0,0 @@
-package org.opentripplanner.analyst.cluster;
-
-import com.amazonaws.handlers.AsyncHandler;
-import com.amazonaws.regions.Region;
-import com.amazonaws.regions.Regions;
-import com.amazonaws.services.sqs.AmazonSQSAsync;
-import com.amazonaws.services.sqs.AmazonSQSAsyncClient;
-import com.amazonaws.services.sqs.buffered.AmazonSQSBufferedAsyncClient;
-import com.amazonaws.services.sqs.model.SendMessageRequest;
-import com.amazonaws.services.sqs.model.SendMessageResult;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * A Task Statistics Store that throws things into SQS.
- */
-public class SQSTaskStatisticsStore implements TaskStatisticsStore {
- private static final Logger LOG = LoggerFactory.getLogger(SQSTaskStatisticsStore.class);
-
- private String queueUrl;
-
- private ObjectMapper objectMapper = new ObjectMapper();
-
- private AmazonSQSAsync sqs = new AmazonSQSBufferedAsyncClient(new AmazonSQSAsyncClient());
-
- /** create a task statistics store for the given queue name */
- public SQSTaskStatisticsStore(String queueName) {
- Region current = Regions.getCurrentRegion();
-
- if (current != null) {
- LOG.info("Assuming statistics queue is in region {}", current);
- sqs.setEndpoint("sqs." + current.toString() + ".amazonaws.com");
- }
-
- try {
- queueUrl = sqs.getQueueUrl(queueName).getQueueUrl();
- } catch (Exception e) {
- LOG.error("Unable to initialize statistics store", e);
- }
- LOG.info("Sending statistics to SQS queue {}", queueName);
- }
-
- public void store (TaskStatistics ts) {
- try {
- String json = objectMapper.writeValueAsString(ts);
-
- SendMessageRequest req = new SendMessageRequest();
- req.setMessageBody(json);
- req.setQueueUrl(queueUrl);
-
- sqs.sendMessageAsync(req, new AsyncHandler() {
- @Override public void onError(Exception e) {
- LOG.error("Error saving stats to SQS", e);
- }
-
- @Override public void onSuccess(SendMessageRequest request,
- SendMessageResult sendMessageResult) {
- /* do nothing */
- }
- });
- } catch (Exception e) {
- LOG.error("Error saving stats to SQS", e);
- }
- }
-}
diff --git a/src/main/java/org/opentripplanner/analyst/cluster/WorkerIdDefiner.java b/src/main/java/org/opentripplanner/analyst/cluster/WorkerIdDefiner.java
deleted file mode 100644
index 51fc86f38ef..00000000000
--- a/src/main/java/org/opentripplanner/analyst/cluster/WorkerIdDefiner.java
+++ /dev/null
@@ -1,22 +0,0 @@
-package org.opentripplanner.analyst.cluster;
-
-import ch.qos.logback.core.PropertyDefinerBase;
-
-/**
- * A class that allows the logging framework to access the worker ID; with a custom logback config
- * this can be used to print the machine ID with each log message. This is useful if you have multiple
- * workers logging to the same log aggregation service.
- *
- * This does seem like it should be a static class of AnalystWorker, but AnalystWorker needs this
- * class loaded to initialize its logger which is a static field, so it would have to be at the top
- * of the file, above the logger definition, which is ugly and confusing so we leave it as its own
- * bona fide class.
- *
- * It would seem that Mapped Diagnostic Contexts would be ideal for this purpose, but they are
- * thread-scoped, and computation takes place in multiple threads; we need this to be JVM-scoped.
- */
-public class WorkerIdDefiner extends PropertyDefinerBase {
- @Override public String getPropertyValue() {
- return AnalystWorker.machineId;
- }
-}
\ No newline at end of file
diff --git a/src/main/java/org/opentripplanner/analyst/core/GeometryIndex.java b/src/main/java/org/opentripplanner/analyst/core/GeometryIndex.java
index 8c5325c6e56..d582cbcc9c9 100644
--- a/src/main/java/org/opentripplanner/analyst/core/GeometryIndex.java
+++ b/src/main/java/org/opentripplanner/analyst/core/GeometryIndex.java
@@ -19,9 +19,9 @@
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
-import com.vividsolutions.jts.geom.Envelope;
-import com.vividsolutions.jts.geom.LineString;
-import com.vividsolutions.jts.index.strtree.STRtree;
+import org.locationtech.jts.geom.Envelope;
+import org.locationtech.jts.geom.LineString;
+import org.locationtech.jts.index.strtree.STRtree;
/**
* This index is used in Analyst and does not need to be instantiated if you are not performing
diff --git a/src/main/java/org/opentripplanner/analyst/core/GeometryIndexService.java b/src/main/java/org/opentripplanner/analyst/core/GeometryIndexService.java
index 5fd781a315a..200ba398238 100644
--- a/src/main/java/org/opentripplanner/analyst/core/GeometryIndexService.java
+++ b/src/main/java/org/opentripplanner/analyst/core/GeometryIndexService.java
@@ -5,7 +5,7 @@
import org.opengis.geometry.BoundingBox;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
-import com.vividsolutions.jts.geom.Envelope;
+import org.locationtech.jts.geom.Envelope;
public interface GeometryIndexService {
diff --git a/src/main/java/org/opentripplanner/analyst/core/IsochroneData.java b/src/main/java/org/opentripplanner/analyst/core/IsochroneData.java
index 90fb97ee0aa..98c28d040be 100644
--- a/src/main/java/org/opentripplanner/analyst/core/IsochroneData.java
+++ b/src/main/java/org/opentripplanner/analyst/core/IsochroneData.java
@@ -1,7 +1,7 @@
package org.opentripplanner.analyst.core;
import com.fasterxml.jackson.annotation.JsonProperty;
-import com.vividsolutions.jts.geom.Geometry;
+import org.locationtech.jts.geom.Geometry;
import java.io.Serializable;
diff --git a/src/main/java/org/opentripplanner/analyst/request/IsoChroneRequest.java b/src/main/java/org/opentripplanner/analyst/request/IsoChroneRequest.java
index 29103dc1e3f..f67be376715 100644
--- a/src/main/java/org/opentripplanner/analyst/request/IsoChroneRequest.java
+++ b/src/main/java/org/opentripplanner/analyst/request/IsoChroneRequest.java
@@ -3,7 +3,7 @@
import java.util.Arrays;
import java.util.List;
-import com.vividsolutions.jts.geom.Coordinate;
+import org.locationtech.jts.geom.Coordinate;
/**
* A request for an isochrone vector.
diff --git a/src/main/java/org/opentripplanner/analyst/request/IsoChroneSPTRendererRecursiveGrid.java b/src/main/java/org/opentripplanner/analyst/request/IsoChroneSPTRendererRecursiveGrid.java
index c296d84d980..fd34f1b3b31 100644
--- a/src/main/java/org/opentripplanner/analyst/request/IsoChroneSPTRendererRecursiveGrid.java
+++ b/src/main/java/org/opentripplanner/analyst/request/IsoChroneSPTRendererRecursiveGrid.java
@@ -19,7 +19,7 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import com.vividsolutions.jts.geom.Coordinate;
+import org.locationtech.jts.geom.Coordinate;
/**
* Compute isochrones out of a shortest path tree request (RecursiveGrid isoline algorithm).
diff --git a/src/main/java/org/opentripplanner/analyst/request/SampleFactory.java b/src/main/java/org/opentripplanner/analyst/request/SampleFactory.java
index d932095bb5b..d40cba3fca7 100644
--- a/src/main/java/org/opentripplanner/analyst/request/SampleFactory.java
+++ b/src/main/java/org/opentripplanner/analyst/request/SampleFactory.java
@@ -1,10 +1,10 @@
package org.opentripplanner.analyst.request;
import com.google.common.collect.Iterables;
-import com.vividsolutions.jts.geom.Coordinate;
-import com.vividsolutions.jts.geom.CoordinateSequence;
-import com.vividsolutions.jts.geom.Envelope;
-import com.vividsolutions.jts.geom.LineString;
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.CoordinateSequence;
+import org.locationtech.jts.geom.Envelope;
+import org.locationtech.jts.geom.LineString;
import gnu.trove.map.TIntDoubleMap;
import gnu.trove.map.hash.TIntDoubleHashMap;
import org.opentripplanner.analyst.core.Sample;
diff --git a/src/main/java/org/opentripplanner/analyst/request/SampleGridRenderer.java b/src/main/java/org/opentripplanner/analyst/request/SampleGridRenderer.java
index b03f60c97cb..dc7cc9a7d10 100644
--- a/src/main/java/org/opentripplanner/analyst/request/SampleGridRenderer.java
+++ b/src/main/java/org/opentripplanner/analyst/request/SampleGridRenderer.java
@@ -25,7 +25,7 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import com.vividsolutions.jts.geom.Coordinate;
+import org.locationtech.jts.geom.Coordinate;
/**
* Compute a sample grid from a SPT request.
diff --git a/src/main/java/org/opentripplanner/analyst/request/SampleGridRequest.java b/src/main/java/org/opentripplanner/analyst/request/SampleGridRequest.java
index 8690ffe37f0..9d3ff0b72fa 100644
--- a/src/main/java/org/opentripplanner/analyst/request/SampleGridRequest.java
+++ b/src/main/java/org/opentripplanner/analyst/request/SampleGridRequest.java
@@ -1,6 +1,6 @@
package org.opentripplanner.analyst.request;
-import com.vividsolutions.jts.geom.Coordinate;
+import org.locationtech.jts.geom.Coordinate;
/**
* A request for a sample grid (of a SPT).
diff --git a/src/main/java/org/opentripplanner/analyst/scenario/AddTripPattern.java b/src/main/java/org/opentripplanner/analyst/scenario/AddTripPattern.java
index c6cb419023b..f1dba73e18d 100644
--- a/src/main/java/org/opentripplanner/analyst/scenario/AddTripPattern.java
+++ b/src/main/java/org/opentripplanner/analyst/scenario/AddTripPattern.java
@@ -1,12 +1,11 @@
package org.opentripplanner.analyst.scenario;
-import com.conveyal.gtfs.model.Route;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
-import com.vividsolutions.jts.geom.Coordinate;
-import com.vividsolutions.jts.geom.LineString;
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.LineString;
-import org.apache.commons.math3.analysis.function.Add;
+import org.onebusaway.gtfs.model.Route;
import org.opentripplanner.analyst.core.Sample;
import org.opentripplanner.analyst.request.SampleFactory;
import org.opentripplanner.model.json_serialization.*;
@@ -43,8 +42,8 @@ public class AddTripPattern extends Modification {
/** used to store the indices of the temporary stops in the graph */
public transient TemporaryStop[] temporaryStops;
- /** GTFS mode (route_type), see constants in com.conveyal.gtfs.model.Route */
- public int mode = Route.BUS;
+ /** GTFS mode (route_type), see constants at https://developers.google.com/transit/gtfs/reference/#routestxt */
+ public int mode = 3;
/** Create temporary stops associated with the given graph. Note that a given AddTripPattern can be associated only with a single graph. */
public void materialize (Graph graph) {
diff --git a/src/main/java/org/opentripplanner/api/adapters/LineStringAdapter.java b/src/main/java/org/opentripplanner/api/adapters/LineStringAdapter.java
index e9813405111..55b5f66259b 100644
--- a/src/main/java/org/opentripplanner/api/adapters/LineStringAdapter.java
+++ b/src/main/java/org/opentripplanner/api/adapters/LineStringAdapter.java
@@ -8,8 +8,8 @@
import org.opentripplanner.util.PolylineEncoder;
import org.opentripplanner.util.model.EncodedPolylineBean;
-import com.vividsolutions.jts.geom.Coordinate;
-import com.vividsolutions.jts.geom.LineString;
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.LineString;
public class LineStringAdapter extends XmlAdapter{
diff --git a/src/main/java/org/opentripplanner/api/common/RoutingResource.java b/src/main/java/org/opentripplanner/api/common/RoutingResource.java
index fe444a2c212..04acf2a34fc 100644
--- a/src/main/java/org/opentripplanner/api/common/RoutingResource.java
+++ b/src/main/java/org/opentripplanner/api/common/RoutingResource.java
@@ -42,7 +42,7 @@
*
* @author abyrd
*/
-public abstract class RoutingResource {
+public abstract class RoutingResource {
private static final Logger LOG = LoggerFactory.getLogger(RoutingResource.class);
@@ -52,7 +52,7 @@ public abstract class RoutingResource {
* a path because we don't want it to be instantiated as an endpoint. Instead, the {routerId}
* path parameter should be included in the path annotations of all its subclasses.
*/
- @PathParam("routerId")
+ @PathParam("routerId")
public String routerId;
/** The start location -- either latitude, longitude pair in degrees or a Vertex
@@ -72,15 +72,15 @@ public abstract class RoutingResource {
/** The date that the trip should depart (or arrive, for requests where arriveBy is true). */
@QueryParam("date")
protected String date;
-
+
/** The time that the trip should depart (or arrive, for requests where arriveBy is true). */
@QueryParam("time")
protected String time;
-
+
/** Whether the trip should depart or arrive at the specified date and time. */
@QueryParam("arriveBy")
protected Boolean arriveBy;
-
+
/** Whether the trip must be wheelchair accessible. */
@QueryParam("wheelchair")
protected Boolean wheelchair;
@@ -146,19 +146,19 @@ public abstract class RoutingResource {
/** For bike triangle routing, how much safety matters (range 0-1). */
@QueryParam("triangleSafetyFactor")
protected Double triangleSafetyFactor;
-
+
/** For bike triangle routing, how much slope matters (range 0-1). */
@QueryParam("triangleSlopeFactor")
protected Double triangleSlopeFactor;
-
- /** For bike triangle routing, how much time matters (range 0-1). */
+
+ /** For bike triangle routing, how much time matters (range 0-1). */
@QueryParam("triangleTimeFactor")
protected Double triangleTimeFactor;
/** The set of characteristics that the user wants to optimize for. @See OptimizeType */
@QueryParam("optimize")
protected OptimizeType optimize;
-
+
/**
*
The set of modes that a user is willing to use, with qualifiers stating whether vehicles should be parked, rented, etc.
*
The possible values of the comma-separated list are:
@@ -214,18 +214,18 @@ public abstract class RoutingResource {
* to wait for preferred route. */
@QueryParam("otherThanPreferredRoutesPenalty")
protected Integer otherThanPreferredRoutesPenalty;
-
+
/** The comma-separated list of preferred agencies. */
@QueryParam("preferredAgencies")
protected String preferredAgencies;
-
+
/**
* The list of unpreferred routes. The format is agency_[routename][_routeid], so TriMet_100 (100 is route short name) or Trimet__42 (two
* underscores, 42 is the route internal ID).
*/
@QueryParam("unpreferredRoutes")
protected String unpreferredRoutes;
-
+
/** The comma-separated list of unpreferred agencies. */
@QueryParam("unpreferredAgencies")
protected String unpreferredAgencies;
@@ -243,14 +243,14 @@ public abstract class RoutingResource {
*/
@QueryParam("walkBoardCost")
protected Integer walkBoardCost;
-
+
/**
* Prevents unnecessary transfers by adding a cost for boarding a vehicle. This is the cost that
* is used when boarding while cycling. This is usually higher that walkBoardCost.
*/
@QueryParam("bikeBoardCost")
protected Integer bikeBoardCost;
-
+
/**
* The comma-separated list of banned routes. The format is agency_[routename][_routeid], so TriMet_100 (100 is route short name) or Trimet__42
* (two underscores, 42 is the route internal ID).
@@ -263,7 +263,7 @@ public abstract class RoutingResource {
*/
@QueryParam("whiteListedRoutes")
protected String whiteListedRoutes;
-
+
/** The comma-separated list of banned agencies. */
@QueryParam("bannedAgencies")
protected String bannedAgencies;
@@ -273,7 +273,7 @@ public abstract class RoutingResource {
*/
@QueryParam("whiteListedAgencies")
protected String whiteListedAgencies;
-
+
/** The comma-separated list of banned trips. The format is agency_trip[:stop*], so:
* TriMet_24601 or TriMet_24601:0:1:2:17:18:19
*/
@@ -288,7 +288,7 @@ public abstract class RoutingResource {
*/
@QueryParam("bannedStops")
protected String bannedStops;
-
+
/** A comma-separated list of banned stops. A stop is banned by ignoring its
* pre-board and pre-alight edges. This means the stop will be reachable via the
* street network. It is not possible to travel through the stop.
@@ -298,7 +298,7 @@ public abstract class RoutingResource {
*/
@QueryParam("bannedStopsHard")
protected String bannedStopsHard;
-
+
/**
* An additional penalty added to boardings after the first. The value is in OTP's
* internal weight units, which are roughly equivalent to seconds. Set this to a high
@@ -307,7 +307,7 @@ public abstract class RoutingResource {
*/
@QueryParam("transferPenalty")
protected Integer transferPenalty;
-
+
/**
* An additional penalty added to boardings after the first when the transfer is not
* preferred. Preferred transfers also include timed transfers. The value is in OTP's
@@ -318,7 +318,7 @@ public abstract class RoutingResource {
*/
@QueryParam("nonpreferredTransferPenalty")
protected Integer nonpreferredTransferPenalty;
-
+
/** The maximum number of transfers (that is, one plus the maximum number of boardings)
* that a trip will be allowed. Larger values will slow performance, but could give
* better routes. This is limited on the server side by the MAX_TRANSFERS value in
@@ -361,10 +361,10 @@ public abstract class RoutingResource {
*/
@QueryParam("reverseOptimizeOnTheFly")
protected Boolean reverseOptimizeOnTheFly;
-
+
@QueryParam("boardSlack")
private Integer boardSlack;
-
+
@QueryParam("alightSlack")
private Integer alightSlack;
@@ -384,6 +384,36 @@ public abstract class RoutingResource {
@QueryParam("disableRemainingWeightHeuristic")
protected Boolean disableRemainingWeightHeuristic;
+ /*
+ * Control the size of flag-stop buffer returned in API response. This parameter only applies
+ * to GTFS-Flex routing, which must be explicitly turned on via the useFlexService parameter in
+ * router-config.json.
+ */
+ @QueryParam("flexFlagStopBufferSize")
+ protected Double flexFlagStopBufferSize;
+
+ /**
+ * Whether to use reservation-based services
+ */
+ @QueryParam("flexUseReservationServices")
+ protected Boolean flexUseReservationServices = true;
+
+ /**
+ * Whether to use eligibility-based services
+ */
+ @QueryParam("flexUseEligibilityServices")
+ protected Boolean flexUseEligibilityServices = true;
+
+ /**
+ * Whether to ignore DRT time limits.
+ *
+ * According to the GTFS-flex spec, demand-response transit (DRT) service must be reserved
+ * at least `drt_advance_book_min` minutes in advance. OTP not allow DRT service to be used
+ * inside that time window, unless this parameter is set to true.
+ */
+ @QueryParam("flexIgnoreDrtAdvanceBookMin")
+ protected Boolean flexIgnoreDrtAdvanceBookMin;
+
@QueryParam("maxHours")
private Double maxHours;
@@ -471,8 +501,8 @@ public abstract class RoutingResource {
@QueryParam("maximumMicromobilitySpeed")
private Double maximumMicromobilitySpeed;
- /*
- * somewhat ugly bug fix: the graphService is only needed here for fetching per-graph time zones.
+ /*
+ * somewhat ugly bug fix: the graphService is only needed here for fetching per-graph time zones.
* this should ideally be done when setting the routing context, but at present departure/
* arrival time is stored in the request as an epoch time with the TZ already resolved, and other
* code depends on this behavior. (AMB)
@@ -526,6 +556,8 @@ protected RoutingRequest buildRequest() throws ParameterException {
} else {
request.setDateTime(date, time, tz);
}
+
+ request.resetClockTime();
}
if (wheelchair != null)
@@ -626,7 +658,7 @@ protected RoutingRequest buildRequest() throws ParameterException {
request.setWhiteListedAgencies(whiteListedAgencies);
HashMap bannedTripMap = makeBannedTripMap(bannedTrips);
-
+
if (bannedTripMap != null)
request.bannedTrips = bannedTripMap;
@@ -635,7 +667,7 @@ protected RoutingRequest buildRequest() throws ParameterException {
if (bannedStopsHard != null)
request.setBannedStopsHard(bannedStopsHard);
-
+
// The "Least transfers" optimization is accomplished via an increased transfer penalty.
// See comment on RoutingRequest.transferPentalty.
if (transferPenalty != null) request.transferPenalty = transferPenalty;
@@ -675,7 +707,7 @@ protected RoutingRequest buildRequest() throws ParameterException {
if (request.boardSlack + request.alightSlack > request.transferSlack) {
throw new RuntimeException("Invalid parameters: " +
- "transfer slack must be greater than or equal to board slack plus alight slack");
+ "transfer slack must be greater than or equal to board slack plus alight slack");
}
if (maxTransfers != null)
@@ -705,6 +737,18 @@ protected RoutingRequest buildRequest() throws ParameterException {
if (disableRemainingWeightHeuristic != null)
request.disableRemainingWeightHeuristic = disableRemainingWeightHeuristic;
+ if (flexFlagStopBufferSize != null)
+ request.flexFlagStopBufferSize = flexFlagStopBufferSize;
+
+ if (flexUseReservationServices != null)
+ request.flexUseReservationServices = flexUseReservationServices;
+
+ if (flexUseEligibilityServices != null)
+ request.flexUseEligibilityServices = flexUseEligibilityServices;
+
+ if (flexIgnoreDrtAdvanceBookMin != null)
+ request.flexIgnoreDrtAdvanceBookMin = flexIgnoreDrtAdvanceBookMin;
+
if (maxHours != null)
request.maxHours = maxHours;
@@ -733,7 +777,7 @@ protected RoutingRequest buildRequest() throws ParameterException {
// first TNC before transit. (See StateEditor.boardHailedCar)
if (this.modes != null && this.modes.qModes.contains(new QualifiedMode("CAR_HAIL"))) {
if (companies == null) {
- companies = "NOAPI";
+ companies = "NOAPI";
}
request.companies = companies;
@@ -838,7 +882,7 @@ private HashMap makeBannedTripMap(String banned) {
if (banned == null) {
return null;
}
-
+
HashMap bannedTripMap = new HashMap();
String[] tripStrings = banned.split(",");
for (String tripString : tripStrings) {
diff --git a/src/main/java/org/opentripplanner/api/model/BoardAlightType.java b/src/main/java/org/opentripplanner/api/model/BoardAlightType.java
new file mode 100644
index 00000000000..f1886d44394
--- /dev/null
+++ b/src/main/java/org/opentripplanner/api/model/BoardAlightType.java
@@ -0,0 +1,27 @@
+package org.opentripplanner.api.model;
+
+/**
+ * Distinguish between special ways a passenger may board or alight at a stop. The majority of
+ * boardings and alightings will be of type "default" -- a regular boarding or alighting at a
+ * regular transit stop. Currently, the only non-default types are related to GTFS-Flex, but this
+ * pattern can be extended as necessary.
+ */
+public enum BoardAlightType {
+
+ /**
+ * A regular boarding or alighting at a fixed-route transit stop.
+ */
+ DEFAULT,
+
+ /**
+ * A flag-stop boarding or alighting, e.g. flagging the bus down or a passenger asking the bus
+ * driver for a drop-off between stops. This is specific to GTFS-Flex.
+ */
+ FLAG_STOP,
+
+ /**
+ * A boarding or alighting at which the vehicle deviates from its fixed route to drop off a
+ * passenger. This is specific to GTFS-Flex.
+ */
+ DEVIATED;
+}
diff --git a/src/main/java/org/opentripplanner/api/model/GeometryAdapter.java b/src/main/java/org/opentripplanner/api/model/GeometryAdapter.java
index 463e44d1155..ea5c462072b 100644
--- a/src/main/java/org/opentripplanner/api/model/GeometryAdapter.java
+++ b/src/main/java/org/opentripplanner/api/model/GeometryAdapter.java
@@ -4,9 +4,9 @@
import org.opentripplanner.common.geometry.GeometryUtils;
-import com.vividsolutions.jts.geom.Geometry;
-import com.vividsolutions.jts.io.WKTReader;
-import com.vividsolutions.jts.io.gml2.GMLWriter;
+import org.locationtech.jts.geom.Geometry;
+import org.locationtech.jts.io.WKTReader;
+import org.locationtech.jts.io.gml2.GMLWriter;
public class GeometryAdapter extends XmlAdapter {
public Geometry unmarshal(String val) throws Exception {
diff --git a/src/main/java/org/opentripplanner/api/model/Itinerary.java b/src/main/java/org/opentripplanner/api/model/Itinerary.java
index 523c39fadb4..81c6a65c0a9 100644
--- a/src/main/java/org/opentripplanner/api/model/Itinerary.java
+++ b/src/main/java/org/opentripplanner/api/model/Itinerary.java
@@ -6,9 +6,6 @@
import java.util.List;
import java.util.TimeZone;
-import javax.xml.bind.annotation.XmlElement;
-import javax.xml.bind.annotation.XmlElementWrapper;
-
import org.opentripplanner.model.calendar.CalendarServiceData;
import org.opentripplanner.routing.core.Fare;
@@ -81,8 +78,6 @@ public class Itinerary {
* trip on a particular vehicle. So a trip where the use walks to the Q train, transfers to the
* 6, then walks to their destination, has four legs.
*/
- @XmlElementWrapper(name = "legs")
- @XmlElement(name = "leg")
public List legs = new ArrayList();
/**
diff --git a/src/main/java/org/opentripplanner/api/model/Leg.java b/src/main/java/org/opentripplanner/api/model/Leg.java
index ffc251f9fd9..93d51f6d9aa 100644
--- a/src/main/java/org/opentripplanner/api/model/Leg.java
+++ b/src/main/java/org/opentripplanner/api/model/Leg.java
@@ -8,16 +8,13 @@
import org.opentripplanner.routing.core.TraverseMode;
import org.opentripplanner.util.model.EncodedPolylineBean;
-import javax.xml.bind.annotation.XmlAttribute;
-import javax.xml.bind.annotation.XmlElement;
-import javax.xml.bind.annotation.XmlElementWrapper;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.Locale;
import java.util.TimeZone;
- /**
+/**
* One leg of a trip -- that is, a temporally continuous piece of the journey that takes place on a
* particular vehicle (or on foot).
*/
@@ -28,12 +25,12 @@ public class Leg {
* The date and time this leg begins.
*/
public Calendar startTime = null;
-
+
/**
* The date and time this leg ends.
*/
public Calendar endTime = null;
-
+
/**
* For transit leg, the offset from the scheduled departure-time of the boarding stop in this leg.
* "scheduled time of departure at boarding stop" = startTime - departureDelay
@@ -48,24 +45,24 @@ public class Leg {
* Whether there is real-time data about this Leg
*/
public Boolean realTime = false;
-
+
/**
* Is this a frequency-based trip with non-strict departure times?
*/
public Boolean isNonExactFrequency = null;
-
+
/**
- * The best estimate of the time between two arriving vehicles. This is particularly important
- * for non-strict frequency trips, but could become important for real-time trips, strict
+ * The best estimate of the time between two arriving vehicles. This is particularly important
+ * for non-strict frequency trips, but could become important for real-time trips, strict
* frequency trips, and scheduled trips with empirical headways.
*/
public Integer headway = null;
-
+
/**
* The distance traveled while traversing the leg in meters.
*/
public Double distance = null;
-
+
/**
* Is this leg a traversing pathways?
*/
@@ -74,7 +71,6 @@ public class Leg {
/**
* The mode (e.g., Walk) used when traversing this leg.
*/
- @XmlAttribute
@JsonSerialize
public String mode = TraverseMode.WALK.toString();
@@ -82,30 +78,24 @@ public class Leg {
* For transit legs, the route of the bus or train being used. For non-transit legs, the name of
* the street being traversed.
*/
- @XmlAttribute
@JsonSerialize
public String route = "";
- @XmlAttribute
@JsonSerialize
public String agencyName;
- @XmlAttribute
@JsonSerialize
public String agencyUrl;
- @XmlAttribute
@JsonSerialize
public String agencyBrandingUrl;
- @XmlAttribute
@JsonSerialize
public int agencyTimeZoneOffset;
/**
* For transit leg, the route's (background) color (if one exists). For non-transit legs, null.
*/
- @XmlAttribute
@JsonSerialize
public String routeColor = null;
@@ -115,10 +105,9 @@ public class Leg {
* When equal or highter than 100, it is coded using the Hierarchical Vehicle Type (HVT) codes from the European TPEG standard
* Also see http://groups.google.com/group/gtfs-changes/msg/ed917a69cf8c5bef
*/
- @XmlAttribute
@JsonSerialize
public Integer routeType = null;
-
+
/**
* For transit legs, the ID of the route.
* For non-transit legs, null.
@@ -128,36 +117,31 @@ public class Leg {
/**
* For transit leg, the route's text color (if one exists). For non-transit legs, null.
*/
- @XmlAttribute
@JsonSerialize
public String routeTextColor = null;
/**
* For transit legs, if the rider should stay on the vehicle as it changes route names.
*/
- @XmlAttribute
@JsonSerialize
public Boolean interlineWithPreviousLeg;
-
+
/**
* For transit leg, the trip's short name (if one exists). For non-transit legs, null.
*/
- @XmlAttribute
@JsonSerialize
public String tripShortName = null;
/**
* For transit leg, the trip's block ID (if one exists). For non-transit legs, null.
*/
- @XmlAttribute
@JsonSerialize
public String tripBlockId = null;
-
+
/**
* For transit legs, the headsign of the bus or train being used. For non-transit legs, null.
*/
- @XmlAttribute
@JsonSerialize
public String headsign = null;
@@ -165,36 +149,33 @@ public class Leg {
* For transit legs, the ID of the transit agency that operates the service used for this leg.
* For non-transit legs, null.
*/
- @XmlAttribute
@JsonSerialize
public String agencyId = null;
-
+
/**
* For transit legs, the ID of the trip.
* For non-transit legs, null.
*/
public FeedScopedId tripId = null;
-
+
/**
* For transit legs, the service date of the trip.
* For non-transit legs, null.
*/
- @XmlAttribute
@JsonSerialize
public String serviceDate = null;
- /**
- * For transit leg, the route's branding URL (if one exists). For non-transit legs, null.
- */
- @XmlAttribute
- @JsonSerialize
- public String routeBrandingUrl = null;
+ /**
+ * For transit leg, the route's branding URL (if one exists). For non-transit legs, null.
+ */
+ @JsonSerialize
+ public String routeBrandingUrl = null;
- /**
+ /**
* The Place where the leg originates.
*/
public Place from = null;
-
+
/**
* The Place where the leg begins.
*/
@@ -205,7 +186,6 @@ public class Leg {
* For non-transit legs, null.
* This field is optional i.e. it is always null unless "showIntermediateStops" parameter is set to "true" in the planner request.
*/
- @XmlElementWrapper(name = "intermediateStops")
@JsonProperty(value="intermediateStops")
public List stop;
@@ -216,53 +196,88 @@ public class Leg {
public List interStopGeometry;
- /**
- * A series of turn by turn instructions used for walking, biking and driving.
+ /**
+ * A series of turn by turn instructions used for walking, biking and driving.
*/
- @XmlElementWrapper(name = "steps")
@JsonProperty(value="steps")
public List walkSteps;
- @XmlElement
@JsonSerialize
public List alerts;
- @XmlAttribute
@JsonSerialize
public String routeShortName;
- @XmlAttribute
@JsonSerialize
public String routeLongName;
- @XmlAttribute
@JsonSerialize
public String boardRule;
- @XmlAttribute
@JsonSerialize
public String alightRule;
- @XmlAttribute
@JsonSerialize
public Boolean rentedBike;
- @XmlAttribute
@JsonSerialize
public Boolean rentedCar;
- @XmlAttribute
@JsonSerialize
public Boolean rentedVehicle;
- @XmlAttribute
@JsonSerialize
public Boolean hailedCar;
- @XmlAttribute
@JsonSerialize
public TransportationNetworkCompanySummary tncData;
+ /**
+ * True if this is a call-and-ride leg.
+ */
+ @JsonSerialize
+ public Boolean callAndRide;
+
+ /* For call-n-ride leg, supply maximum start time based on calculation. */
+ @JsonSerialize
+ public Calendar flexCallAndRideMaxStartTime = null;
+
+ /* For call-n-ride leg, supply minimum end time based on calculation. */
+ @JsonSerialize
+ public Calendar flexCallAndRideMinEndTime = null;
+
+ /** trip.drt_advance_book_min if this is a demand-response leg */
+ @JsonSerialize
+ public double flexDrtAdvanceBookMin;
+
+ /**
+ * Agency message if this is leg has a demand-response pickup and the Trip has
+ * `drt_pickup_message` defined.
+ */
+ @JsonSerialize
+ public String flexDrtPickupMessage;
+
+ /**
+ * Agency message if this is leg has a demand-response dropoff and the Trip has
+ * `drt_drop_off_message` defined.
+ */
+ @JsonSerialize
+ public String flexDrtDropOffMessage;
+
+ /**
+ * Agency message if this is leg has a flag stop pickup and the Trip has
+ * `continuous_pickup_message` defined.
+ */
+ @JsonSerialize
+ public String flexFlagStopPickupMessage;
+
+ /**
+ * Agency message if this is leg has a flag stop dropoff and the Trip has
+ * `continuous_drop_off_message` defined.
+ */
+ @JsonSerialize
+ public String flexFlagStopDropOffMessage;
+
/**
* Whether this leg is a transit leg or not.
* @return Boolean true if the leg is a transit leg
@@ -275,11 +290,10 @@ public Boolean isTransitLeg() {
else if (mode.equals(TraverseMode.MICROMOBILITY.toString())) return false;
else return true;
}
-
- /**
+
+ /**
* The leg's duration in seconds
*/
- @XmlElement
@JsonSerialize
public double getDuration() {
return endTime.getTimeInMillis()/1000.0 - startTime.getTimeInMillis()/1000.0;
diff --git a/src/main/java/org/opentripplanner/api/model/Place.java b/src/main/java/org/opentripplanner/api/model/Place.java
index 728d0c79e45..36643232d35 100644
--- a/src/main/java/org/opentripplanner/api/model/Place.java
+++ b/src/main/java/org/opentripplanner/api/model/Place.java
@@ -2,46 +2,45 @@
import java.util.Calendar;
import java.util.Set;
-import javax.xml.bind.annotation.XmlAttribute;
-import javax.xml.bind.annotation.XmlElement;
import org.opentripplanner.model.FeedScopedId;
import org.opentripplanner.util.Constants;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import org.opentripplanner.util.model.EncodedPolylineBean;
-/**
-* A Place is where a journey starts or ends, or a transit stop along the way.
-*/
+/**
+ * A Place is where a journey starts or ends, or a transit stop along the way.
+ */
public class Place {
- /**
+ /**
* For transit stops, the name of the stop. For points of interest, the name of the POI.
*/
public String name = null;
- /**
+ /**
* The ID of the stop. This is often something that users don't care about.
*/
public FeedScopedId stopId = null;
- /**
+ /**
* The "code" of the stop. Depending on the transit agency, this is often
* something that users care about.
*/
public String stopCode = null;
/**
- * The code or name identifying the quay/platform the vehicle will arrive at or depart from
- *
- */
+ * The code or name identifying the quay/platform the vehicle will arrive at or depart from
+ *
+ */
public String platformCode = null;
/**
* The longitude of the place.
*/
public Double lon = null;
-
+
/**
* The latitude of the place.
*/
@@ -57,25 +56,21 @@ public class Place {
*/
public Calendar departure = null;
- @XmlAttribute
@JsonSerialize
public String orig;
- @XmlAttribute
@JsonSerialize
public String zoneId;
/**
* For transit trips, the stop index (numbered from zero from the start of the trip
*/
- @XmlAttribute
@JsonSerialize
public Integer stopIndex;
/**
* For transit trips, the sequence number of the stop. Per GTFS, these numbers are increasing.
*/
- @XmlAttribute
@JsonSerialize
public Integer stopSequence;
@@ -83,7 +78,6 @@ public class Place {
* Type of vertex. (Normal, Bike sharing station, Bike P+R, Transit stop)
* Mostly used for better localization of bike sharing and P+R station names
*/
- @XmlAttribute
@JsonSerialize
public VertexType vertexType;
@@ -95,20 +89,29 @@ public class Place {
/**
* Car share station fields
*/
- @XmlAttribute
@JsonSerialize
public Set networks;
- @XmlAttribute
@JsonSerialize
public String address;
+ /**
+ * This is an optional field which can be used to distinguish among ways a passenger's
+ * boarding or alighting at a stop can differ among services operated by a transit agency.
+ * This will be "default" in most cases. Currently the only non-default values are for
+ * GTFS-Flex board or alight types.
+ */
+ public BoardAlightType boardAlightType;
+
+ /**
+ * Board or alight area for flag stops
+ */
+ public EncodedPolylineBean flagStopArea;
/**
* Returns the geometry in GeoJSON format
* @return
*/
- @XmlElement
String getGeometry() {
return Constants.GEO_JSON_POINT + lon + "," + lat + Constants.GEO_JSON_TAIL;
}
@@ -120,7 +123,7 @@ public Place(Double lon, Double lat, String name) {
this.lon = lon;
this.lat = lat;
this.name = name;
- this.vertexType = VertexType.NORMAL;
+ this.vertexType = VertexType.NORMAL;
}
public Place(Double lon, Double lat, String name, Calendar arrival, Calendar departure) {
diff --git a/src/main/java/org/opentripplanner/api/model/RouterInfo.java b/src/main/java/org/opentripplanner/api/model/RouterInfo.java
index 2d511a1e2c7..2b35e907410 100644
--- a/src/main/java/org/opentripplanner/api/model/RouterInfo.java
+++ b/src/main/java/org/opentripplanner/api/model/RouterInfo.java
@@ -1,11 +1,17 @@
package org.opentripplanner.api.model;
-import com.conveyal.geojson.GeometryDeserializer;
-import com.conveyal.geojson.GeometrySerializer;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Optional;
+
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
-import com.vividsolutions.jts.geom.Coordinate;
-import com.vividsolutions.jts.geom.Geometry;
+import org.locationtech.jts.geom.Geometry;
+
+import org.locationtech.jts.geom.Coordinate;
+import org.opentripplanner.common.geometry.GeometryDeserializer;
+import org.opentripplanner.common.geometry.GeometrySerializer;
import org.opentripplanner.routing.bike_rental.BikeRentalStationService;
import org.opentripplanner.routing.core.TraverseMode;
import org.opentripplanner.routing.graph.Graph;
@@ -13,34 +19,20 @@
import org.opentripplanner.util.TravelOptionsMaker;
import org.opentripplanner.util.WorldEnvelope;
-import javax.xml.bind.annotation.XmlElement;
-import javax.xml.bind.annotation.XmlRootElement;
-import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
-import java.util.Date;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Optional;
-
-@XmlRootElement(name = "RouterInfo")
public class RouterInfo {
private final BikeRentalStationService service;
- @XmlElement
public String routerId;
-
- @JsonSerialize(using=GeometrySerializer.class)
- @JsonDeserialize(using=GeometryDeserializer.class)
- @XmlJavaTypeAdapter(value=GeometryAdapter.class,type=Geometry.class)
+
+ @JsonSerialize(using= GeometrySerializer.class)
+ @JsonDeserialize(using= GeometryDeserializer.class)
public Geometry polygon;
- @XmlElement
public Date buildTime;
- @XmlElement
public long transitServiceStarts;
- @XmlElement
public long transitServiceEnds;
public HashSet transitModes;
diff --git a/src/main/java/org/opentripplanner/api/model/RouterList.java b/src/main/java/org/opentripplanner/api/model/RouterList.java
index d1bed4c2de2..613533aefd3 100644
--- a/src/main/java/org/opentripplanner/api/model/RouterList.java
+++ b/src/main/java/org/opentripplanner/api/model/RouterList.java
@@ -3,13 +3,6 @@
import java.util.ArrayList;
import java.util.List;
-import javax.xml.bind.annotation.XmlElement;
-import javax.xml.bind.annotation.XmlElements;
-import javax.xml.bind.annotation.XmlRootElement;
-
-@XmlRootElement(name = "RouterList")
public class RouterList {
- @XmlElements(value = { @XmlElement(name="routerInfo") })
public List routerInfo = new ArrayList();
-
}
diff --git a/src/main/java/org/opentripplanner/api/model/TripPlan.java b/src/main/java/org/opentripplanner/api/model/TripPlan.java
index 7c116cc945e..52d707bb98b 100644
--- a/src/main/java/org/opentripplanner/api/model/TripPlan.java
+++ b/src/main/java/org/opentripplanner/api/model/TripPlan.java
@@ -4,8 +4,6 @@
import java.util.Date;
import java.util.List;
-import javax.xml.bind.annotation.XmlElementWrapper;
-
import com.fasterxml.jackson.annotation.JsonProperty;
/**
@@ -23,7 +21,6 @@ public class TripPlan {
public Place to = null;
/** A list of possible itineraries */
- @XmlElementWrapper(name="itineraries") //TODO: why don't we just change the variable name?
@JsonProperty(value="itineraries")
public List itinerary = new ArrayList();
diff --git a/src/main/java/org/opentripplanner/api/model/WalkStep.java b/src/main/java/org/opentripplanner/api/model/WalkStep.java
index ad0ed31e30f..056b576b46d 100644
--- a/src/main/java/org/opentripplanner/api/model/WalkStep.java
+++ b/src/main/java/org/opentripplanner/api/model/WalkStep.java
@@ -2,15 +2,13 @@
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.google.common.collect.Lists;
+
import org.opentripplanner.api.model.alertpatch.LocalizedAlert;
import org.opentripplanner.common.model.P2;
import org.opentripplanner.profile.BikeRentalStationInfo;
import org.opentripplanner.routing.alertpatch.Alert;
import org.opentripplanner.routing.graph.Edge;
-import javax.xml.bind.annotation.XmlElement;
-import javax.xml.bind.annotation.XmlTransient;
-import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@@ -100,10 +98,8 @@ public class WalkStep {
* The elevation profile as a comma-separated list of x,y values. x is the distance from the start of the step, y is the elevation at this
* distance.
*/
- @XmlTransient
public List> elevation;
- @XmlElement
@JsonSerialize
public List alerts;
@@ -141,7 +137,7 @@ public String toString() {
}
public static RelativeDirection getRelativeDirection(double lastAngle, double thisAngle,
- boolean roundabout) {
+ boolean roundabout) {
double angleDiff = thisAngle - lastAngle;
if (angleDiff < 0) {
@@ -206,7 +202,6 @@ public String streetNameNoParens() {
return streetName.substring(0, idx - 1);
}
- @XmlJavaTypeAdapter(ElevationAdapter.class)
@JsonSerialize
public List> getElevation() {
return elevation;
diff --git a/src/main/java/org/opentripplanner/api/model/alertpatch/AlertPatchCreationResponse.java b/src/main/java/org/opentripplanner/api/model/alertpatch/AlertPatchCreationResponse.java
index 026c46cf01c..14f6a08c173 100644
--- a/src/main/java/org/opentripplanner/api/model/alertpatch/AlertPatchCreationResponse.java
+++ b/src/main/java/org/opentripplanner/api/model/alertpatch/AlertPatchCreationResponse.java
@@ -1,10 +1,5 @@
package org.opentripplanner.api.model.alertpatch;
-import javax.xml.bind.annotation.XmlElement;
-import javax.xml.bind.annotation.XmlRootElement;
-
-@XmlRootElement(name = "AlertPatchCreationResponse")
public class AlertPatchCreationResponse {
- @XmlElement
public String status;
}
diff --git a/src/main/java/org/opentripplanner/api/model/alertpatch/AlertPatchResponse.java b/src/main/java/org/opentripplanner/api/model/alertpatch/AlertPatchResponse.java
index add91d63e7e..bb6c6a59fd9 100644
--- a/src/main/java/org/opentripplanner/api/model/alertpatch/AlertPatchResponse.java
+++ b/src/main/java/org/opentripplanner/api/model/alertpatch/AlertPatchResponse.java
@@ -3,19 +3,9 @@
import java.util.ArrayList;
import java.util.List;
-import javax.xml.bind.annotation.XmlElement;
-import javax.xml.bind.annotation.XmlElementWrapper;
-import javax.xml.bind.annotation.XmlElements;
-import javax.xml.bind.annotation.XmlRootElement;
-
import org.opentripplanner.routing.alertpatch.AlertPatch;
-@XmlRootElement
public class AlertPatchResponse {
- @XmlElementWrapper
- @XmlElements({
- @XmlElement(name = "AlertPatch", type = AlertPatch.class)
- })
public List alertPatches;
public void addAlertPatch(AlertPatch alertPatch) {
diff --git a/src/main/java/org/opentripplanner/api/model/alertpatch/AlertPatchSet.java b/src/main/java/org/opentripplanner/api/model/alertpatch/AlertPatchSet.java
index 7fe797de775..20a4d79da86 100644
--- a/src/main/java/org/opentripplanner/api/model/alertpatch/AlertPatchSet.java
+++ b/src/main/java/org/opentripplanner/api/model/alertpatch/AlertPatchSet.java
@@ -2,16 +2,8 @@
import java.util.List;
-import javax.xml.bind.annotation.XmlElement;
-import javax.xml.bind.annotation.XmlElements;
-import javax.xml.bind.annotation.XmlRootElement;
-
import org.opentripplanner.routing.alertpatch.AlertPatch;
-@XmlRootElement(name="AlertPatchSet")
public class AlertPatchSet {
- @XmlElements({
- @XmlElement(name = "AlertPatch", type = AlertPatch.class)
- })
public List alertPatches;
}
diff --git a/src/main/java/org/opentripplanner/api/model/alertpatch/LocalizedAlert.java b/src/main/java/org/opentripplanner/api/model/alertpatch/LocalizedAlert.java
index 3a3a9f23a4d..94ffdc2b491 100644
--- a/src/main/java/org/opentripplanner/api/model/alertpatch/LocalizedAlert.java
+++ b/src/main/java/org/opentripplanner/api/model/alertpatch/LocalizedAlert.java
@@ -4,21 +4,14 @@
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import org.opentripplanner.routing.alertpatch.Alert;
-import javax.xml.bind.annotation.XmlAttribute;
-import javax.xml.bind.annotation.XmlElement;
-import javax.xml.bind.annotation.XmlRootElement;
-import javax.xml.bind.annotation.XmlTransient;
import java.util.Date;
import java.util.Locale;
-@XmlRootElement(name = "Alert")
public class LocalizedAlert {
- @XmlTransient
@JsonIgnore
public Alert alert;
- @XmlTransient
@JsonIgnore
private Locale locale;
@@ -30,7 +23,6 @@ public LocalizedAlert(Alert alert, Locale locale) {
public LocalizedAlert(){
}
- @XmlAttribute
@JsonSerialize
public String getAlertHeaderText() {
if (alert.alertHeaderText == null) {
@@ -39,7 +31,6 @@ public String getAlertHeaderText() {
return alert.alertHeaderText.toString(locale);
}
- @XmlAttribute
@JsonSerialize
public String getAlertDescriptionText() {
if (alert.alertDescriptionText == null) {
@@ -48,7 +39,6 @@ public String getAlertDescriptionText() {
return alert.alertDescriptionText.toString(locale);
}
- @XmlAttribute
@JsonSerialize
public String getAlertUrl() {
if (alert.alertUrl == null) {
@@ -58,7 +48,6 @@ public String getAlertUrl() {
}
//null means unknown
- @XmlElement
@JsonSerialize
public Date getEffectiveStartDate() {
return alert.effectiveStartDate;
diff --git a/src/main/java/org/opentripplanner/api/model/error/TransitError.java b/src/main/java/org/opentripplanner/api/model/error/TransitError.java
index 5c5bb3b96b8..450fefa9a94 100644
--- a/src/main/java/org/opentripplanner/api/model/error/TransitError.java
+++ b/src/main/java/org/opentripplanner/api/model/error/TransitError.java
@@ -1,9 +1,5 @@
package org.opentripplanner.api.model.error;
-import javax.xml.bind.annotation.XmlElement;
-import javax.xml.bind.annotation.XmlRootElement;
-
-@XmlRootElement
public class TransitError {
private String message;
@@ -17,7 +13,6 @@ public void setMessage(String message) {
this.message = message;
}
- @XmlElement(name="message")
public String getMessage() {
return message;
}
diff --git a/src/main/java/org/opentripplanner/api/parameter/BoundingBox.java b/src/main/java/org/opentripplanner/api/parameter/BoundingBox.java
index 1bfa0f70837..17299cfeb54 100644
--- a/src/main/java/org/opentripplanner/api/parameter/BoundingBox.java
+++ b/src/main/java/org/opentripplanner/api/parameter/BoundingBox.java
@@ -4,8 +4,8 @@
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
-import com.vividsolutions.jts.geom.Coordinate;
-import com.vividsolutions.jts.geom.Envelope;
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.Envelope;
public class BoundingBox {
diff --git a/src/main/java/org/opentripplanner/api/parameter/BoundingCircle.java b/src/main/java/org/opentripplanner/api/parameter/BoundingCircle.java
index 8ba579a17a7..cd15b98d5cd 100644
--- a/src/main/java/org/opentripplanner/api/parameter/BoundingCircle.java
+++ b/src/main/java/org/opentripplanner/api/parameter/BoundingCircle.java
@@ -4,8 +4,8 @@
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
-import com.vividsolutions.jts.geom.Coordinate;
-import com.vividsolutions.jts.geom.Envelope;
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.Envelope;
public class BoundingCircle {
diff --git a/src/main/java/org/opentripplanner/api/resource/BikeRental.java b/src/main/java/org/opentripplanner/api/resource/BikeRental.java
index 41efc1fd8bd..1f76ede65af 100644
--- a/src/main/java/org/opentripplanner/api/resource/BikeRental.java
+++ b/src/main/java/org/opentripplanner/api/resource/BikeRental.java
@@ -20,7 +20,7 @@
import org.opentripplanner.standalone.OTPServer;
import org.opentripplanner.standalone.Router;
-import com.vividsolutions.jts.geom.Envelope;
+import org.locationtech.jts.geom.Envelope;
import org.opentripplanner.util.ResourceBundleSingleton;
@Path("/routers/{routerId}/bike_rental")
diff --git a/src/main/java/org/opentripplanner/api/resource/BikeRentalStationList.java b/src/main/java/org/opentripplanner/api/resource/BikeRentalStationList.java
index 7d9431075a2..9a28ef89599 100644
--- a/src/main/java/org/opentripplanner/api/resource/BikeRentalStationList.java
+++ b/src/main/java/org/opentripplanner/api/resource/BikeRentalStationList.java
@@ -3,14 +3,8 @@
import java.util.ArrayList;
import java.util.List;
-import javax.xml.bind.annotation.XmlElement;
-import javax.xml.bind.annotation.XmlElements;
-import javax.xml.bind.annotation.XmlRootElement;
-
import org.opentripplanner.routing.bike_rental.BikeRentalStation;
-@XmlRootElement(name="BikeRentalStationList")
public class BikeRentalStationList {
- @XmlElements(value = { @XmlElement(name="station") })
public List stations = new ArrayList();
}
diff --git a/src/main/java/org/opentripplanner/api/resource/CarRental.java b/src/main/java/org/opentripplanner/api/resource/CarRental.java
index 953579e7913..6378211fcdb 100644
--- a/src/main/java/org/opentripplanner/api/resource/CarRental.java
+++ b/src/main/java/org/opentripplanner/api/resource/CarRental.java
@@ -13,7 +13,7 @@ the License, or (at your option) any later version.
package org.opentripplanner.api.resource;
-import com.vividsolutions.jts.geom.Envelope;
+import org.locationtech.jts.geom.Envelope;
import org.opentripplanner.routing.car_rental.CarRentalStation;
import org.opentripplanner.routing.car_rental.CarRentalStationService;
import org.opentripplanner.standalone.OTPServer;
diff --git a/src/main/java/org/opentripplanner/api/resource/CoordinateArrayListSequence.java b/src/main/java/org/opentripplanner/api/resource/CoordinateArrayListSequence.java
index bb4f7626e98..3ee8a6b47a7 100644
--- a/src/main/java/org/opentripplanner/api/resource/CoordinateArrayListSequence.java
+++ b/src/main/java/org/opentripplanner/api/resource/CoordinateArrayListSequence.java
@@ -3,9 +3,9 @@
import java.util.ArrayList;
import java.util.Arrays;
-import com.vividsolutions.jts.geom.Coordinate;
-import com.vividsolutions.jts.geom.CoordinateSequence;
-import com.vividsolutions.jts.geom.Envelope;
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.CoordinateSequence;
+import org.locationtech.jts.geom.Envelope;
/** An instance of CoordinateSequence that can be efficiently extended */
public class CoordinateArrayListSequence implements CoordinateSequence, Cloneable {
@@ -124,4 +124,17 @@ public void add(Coordinate newCoordinate) {
public void clear() {
coordinates = new ArrayList();
}
+
+ @Override
+ public CoordinateSequence copy() {
+ CoordinateArrayListSequence clone;
+ try {
+ clone = (CoordinateArrayListSequence) super.clone();
+ } catch (CloneNotSupportedException e) {
+ /* never happens since super is Object */
+ throw new RuntimeException(e);
+ }
+ clone.coordinates = (ArrayList) coordinates.clone();
+ return clone;
+ }
}
diff --git a/src/main/java/org/opentripplanner/api/resource/ExternalGeocoderResource.java b/src/main/java/org/opentripplanner/api/resource/ExternalGeocoderResource.java
index b3effa58466..859bc41a7b8 100644
--- a/src/main/java/org/opentripplanner/api/resource/ExternalGeocoderResource.java
+++ b/src/main/java/org/opentripplanner/api/resource/ExternalGeocoderResource.java
@@ -13,7 +13,7 @@
import org.opentripplanner.geocoder.Geocoder;
import org.opentripplanner.geocoder.GeocoderResults;
-import com.vividsolutions.jts.geom.Envelope;
+import org.locationtech.jts.geom.Envelope;
/**
* Maybe the internal geocoder resource should just chain to defined external geocoders?
diff --git a/src/main/java/org/opentripplanner/api/resource/GraphPathToTripPlanConverter.java b/src/main/java/org/opentripplanner/api/resource/GraphPathToTripPlanConverter.java
index c298f6a3e87..f42825a595e 100644
--- a/src/main/java/org/opentripplanner/api/resource/GraphPathToTripPlanConverter.java
+++ b/src/main/java/org/opentripplanner/api/resource/GraphPathToTripPlanConverter.java
@@ -1,8 +1,9 @@
package org.opentripplanner.api.resource;
-import com.vividsolutions.jts.geom.Coordinate;
-import com.vividsolutions.jts.geom.Geometry;
-import com.vividsolutions.jts.geom.LineString;
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.Geometry;
+import org.locationtech.jts.geom.LineString;
+import org.opentripplanner.api.model.BoardAlightType;
import org.opentripplanner.api.model.Itinerary;
import org.opentripplanner.api.model.Leg;
import org.opentripplanner.api.model.Place;
@@ -43,6 +44,8 @@
import org.opentripplanner.routing.edgetype.StreetEdge;
import org.opentripplanner.routing.edgetype.TripPattern;
import org.opentripplanner.routing.error.TransportationNetworkCompanyAvailabilityException;
+import org.opentripplanner.routing.edgetype.flex.PartialPatternHop;
+import org.opentripplanner.routing.edgetype.flex.TemporaryDirectPatternHop;
import org.opentripplanner.routing.error.TrivialPathException;
import org.opentripplanner.routing.graph.Edge;
import org.opentripplanner.routing.graph.Graph;
@@ -252,9 +255,13 @@ public static Itinerary generateItinerary(GraphPath path, boolean showIntermedia
private static Calendar makeCalendar(State state) {
RoutingContext rctx = state.getContext();
- TimeZone timeZone = rctx.graph.getTimeZone();
+ TimeZone timeZone = rctx.graph.getTimeZone();
+ return makeCalendar(timeZone, state.getTimeInMillis());
+ }
+
+ private static Calendar makeCalendar(TimeZone timeZone, long timeMillis) {
Calendar calendar = Calendar.getInstance(timeZone);
- calendar.setTimeInMillis(state.getTimeInMillis());
+ calendar.setTimeInMillis(timeMillis);
return calendar;
}
@@ -264,11 +271,11 @@ private static Calendar makeCalendar(State state) {
* @param edges The array of input edges
* @return The coordinates of the points on the edges
*/
- private static CoordinateArrayListSequence makeCoordinates(Edge[] edges) {
+ public static CoordinateArrayListSequence makeCoordinates(Edge[] edges) {
CoordinateArrayListSequence coordinates = new CoordinateArrayListSequence();
for (Edge edge : edges) {
- LineString geometry = edge.getGeometry();
+ LineString geometry = edge.getDisplayGeometry();
if (geometry != null) {
if (coordinates.size() == 0) {
@@ -461,7 +468,7 @@ private static boolean addTNCData(
// This avoids unnecessary/redundant API requests to TNC providers.
Place from = leg.from;
if (request.transportationNetworkCompanyEtaAtOrigin > -1 &&
- (i == 0 || (i == 1 && itinerary.legs.get(0).mode.equals("WALK")))) {
+ (i == 0 || (i == 1 && itinerary.legs.get(0).mode.equals("WALK")))) {
from = new Place(request.from.lng, request.from.lat, request.from.name);
tncLegsAreFromOrigin.add(true);
} else {
@@ -687,18 +694,18 @@ private static void calculateTimes(Itinerary itinerary, State[] states) {
if (state.getBackMode() == null) continue;
switch (state.getBackMode()) {
- default:
- itinerary.transitTime += state.getTimeDeltaSeconds();
- break;
-
- case LEG_SWITCH:
- itinerary.waitingTime += state.getTimeDeltaSeconds();
- break;
-
- case WALK:
- case BICYCLE:
- case CAR:
- itinerary.walkTime += state.getTimeDeltaSeconds();
+ default:
+ itinerary.transitTime += state.getTimeDeltaSeconds();
+ break;
+
+ case LEG_SWITCH:
+ itinerary.waitingTime += state.getTimeDeltaSeconds();
+ break;
+
+ case WALK:
+ case BICYCLE:
+ case CAR:
+ itinerary.walkTime += state.getTimeDeltaSeconds();
}
}
}
@@ -801,6 +808,11 @@ private static void addTripFields(Leg leg, State[] states, Locale requestedLocal
leg.tripId = trip.getId();
leg.tripShortName = trip.getTripShortName();
leg.tripBlockId = trip.getBlockId();
+ leg.flexDrtAdvanceBookMin = trip.getDrtAdvanceBookMin();
+ leg.flexDrtPickupMessage = trip.getDrtPickupMessage();
+ leg.flexDrtDropOffMessage = trip.getDrtDropOffMessage();
+ leg.flexFlagStopPickupMessage = trip.getContinuousPickupMessage();
+ leg.flexFlagStopDropOffMessage = trip.getContinuousDropOffMessage();
if (serviceDay != null) {
leg.serviceDate = serviceDay.getServiceDate().getAsString();
@@ -809,6 +821,30 @@ private static void addTripFields(Leg leg, State[] states, Locale requestedLocal
if (leg.headsign == null) {
leg.headsign = trip.getTripHeadsign();
}
+
+ Edge edge = states[states.length - 1].backEdge;
+ if (edge instanceof TemporaryDirectPatternHop) {
+ leg.callAndRide = true;
+ }
+ if (edge instanceof PartialPatternHop) {
+ PartialPatternHop hop = (PartialPatternHop) edge;
+ int directTime = hop.getDirectVehicleTime();
+ TripTimes tt = states[states.length - 1].getTripTimes();
+ int maxTime = tt.getDemandResponseMaxTime(directTime);
+ int avgTime = tt.getDemandResponseAvgTime(directTime);
+ int delta = maxTime - avgTime;
+ if (directTime != 0 && delta > 0) {
+ if (hop.isDeviatedRouteBoard()) {
+ long maxStartTime = leg.startTime.getTimeInMillis() + (delta * 1000);
+ leg.flexCallAndRideMaxStartTime = makeCalendar(leg.startTime.getTimeZone(), maxStartTime);
+ }
+ if (hop.isDeviatedRouteAlight()) {
+ long minEndTime = leg.endTime.getTimeInMillis() - (delta * 1000);
+ leg.flexCallAndRideMinEndTime = makeCalendar(leg.endTime.getTimeZone(), minEndTime);
+ }
+ }
+ }
+
}
}
@@ -828,9 +864,9 @@ private static void addPlaces(Leg leg, State[] states, Edge[] edges, boolean sho
Vertex lastVertex = states[states.length - 1].getVertex();
Stop firstStop = firstVertex instanceof TransitVertex ?
- ((TransitVertex) firstVertex).getStop(): null;
+ ((TransitVertex) firstVertex).getStop(): null;
Stop lastStop = lastVertex instanceof TransitVertex ?
- ((TransitVertex) lastVertex).getStop(): null;
+ ((TransitVertex) lastVertex).getStop(): null;
TripTimes tripTimes = states[states.length - 1].getTripTimes();
leg.from = makePlace(states[0], firstVertex, edges[0], firstStop, tripTimes, requestedLocale);
@@ -887,7 +923,7 @@ private static Place makePlace(State state, Vertex vertex, Edge edge, Stop stop,
name = ((StreetVertex) vertex).getIntersectionName(requestedLocale).toString(requestedLocale);
}
Place place = new Place(vertex.getX(), vertex.getY(), name,
- makeCalendar(state), makeCalendar(state));
+ makeCalendar(state), makeCalendar(state));
if (endOfLeg) edge = state.getBackEdge();
@@ -904,6 +940,22 @@ private static Place makePlace(State state, Vertex vertex, Edge edge, Stop stop,
place.stopSequence = tripTimes.getStopSequence(place.stopIndex);
}
place.vertexType = VertexType.TRANSIT;
+ place.boardAlightType = BoardAlightType.DEFAULT;
+ if (edge instanceof PartialPatternHop) {
+ PartialPatternHop hop = (PartialPatternHop) edge;
+ if (hop.hasBoardArea() && !endOfLeg) {
+ place.flagStopArea = PolylineEncoder.createEncodings(hop.getBoardArea());
+ }
+ if (hop.hasAlightArea() && endOfLeg) {
+ place.flagStopArea = PolylineEncoder.createEncodings(hop.getAlightArea());
+ }
+ if ((endOfLeg && hop.isFlagStopAlight()) || (!endOfLeg && hop.isFlagStopBoard())) {
+ place.boardAlightType = BoardAlightType.FLAG_STOP;
+ }
+ if ((endOfLeg && hop.isDeviatedRouteAlight()) || (!endOfLeg && hop.isDeviatedRouteBoard())) {
+ place.boardAlightType = BoardAlightType.DEVIATED;
+ }
+ }
} else if(vertex instanceof BikeRentalStationVertex) {
place.bikeShareId = ((BikeRentalStationVertex) vertex).getId();
LOG.trace("Added bike share Id {} to place", place.bikeShareId);
@@ -945,9 +997,9 @@ private static void addRealTimeData(Leg leg, State[] states) {
/**
* Converts a list of street edges to a list of turn-by-turn directions.
- *
+ *
* @param previous a non-transit leg that immediately precedes this one (bike-walking, say), or null
- *
+ *
* @return
*/
public static List generateWalkSteps(Graph graph, State[] states, WalkStep previous, Locale requestedLocale) {
@@ -1049,9 +1101,9 @@ public static List generateWalkSteps(Graph graph, State[] states, Walk
// new step, set distance to length of first edge
distance = edge.getDistance();
} else if (((step.streetName != null && !step.streetNameNoParens().equals(streetNameNoParens))
- && (!step.bogusName || !edge.hasBogusName())) ||
- edge.isRoundabout() != (roundaboutExit > 0) || // went on to or off of a roundabout
- isLink(edge) && !isLink(backState.getBackEdge())) {
+ && (!step.bogusName || !edge.hasBogusName())) ||
+ edge.isRoundabout() != (roundaboutExit > 0) || // went on to or off of a roundabout
+ isLink(edge) && !isLink(backState.getBackEdge())) {
// Street name has changed, or we've gone on to or off of a roundabout.
if (roundaboutExit > 0) {
// if we were just on a roundabout,
@@ -1084,7 +1136,7 @@ public static List generateWalkSteps(Graph graph, State[] states, Walk
/* street name has not changed */
double thisAngle = DirectionUtils.getFirstAngle(geom);
RelativeDirection direction = WalkStep.getRelativeDirection(lastAngle, thisAngle,
- edge.isRoundabout());
+ edge.isRoundabout());
boolean optionsBefore = backState.multipleOptionsBefore();
if (edge.isRoundabout()) {
// we are on a roundabout, and have already traversed at least one edge of it.
@@ -1114,7 +1166,7 @@ public static List generateWalkSteps(Graph graph, State[] states, Walk
continue;
}
double altAngle = DirectionUtils.getFirstAngle(alternative
- .getGeometry());
+ .getGeometry());
double altAngleDiff = getAbsoluteAngleDiff(altAngle, lastAngle);
if (angleDiff > Math.PI / 4 || altAngleDiff - angleDiff < Math.PI / 16) {
shouldGenerateContinue = true;
@@ -1128,7 +1180,7 @@ public static List generateWalkSteps(Graph graph, State[] states, Walk
Vertex backVertex = twoStatesBack.getVertex();
for (Edge alternative : backVertex.getOutgoingStreetEdges()) {
List alternatives = alternative.getToVertex()
- .getOutgoingStreetEdges();
+ .getOutgoingStreetEdges();
if (alternatives.size() == 0) {
continue; // this is not an alternative
}
@@ -1139,7 +1191,7 @@ public static List generateWalkSteps(Graph graph, State[] states, Walk
continue;
}
double altAngle = DirectionUtils.getFirstAngle(alternative
- .getGeometry());
+ .getGeometry());
double altAngleDiff = getAbsoluteAngleDiff(altAngle, lastAngle);
if (angleDiff > Math.PI / 4 || altAngleDiff - angleDiff < Math.PI / 16) {
shouldGenerateContinue = true;
@@ -1180,30 +1232,30 @@ public static List generateWalkSteps(Graph graph, State[] states, Walk
WalkStep lastStep = steps.get(last);
if (twoBack.distance < MAX_ZAG_DISTANCE
- && lastStep.streetNameNoParens().equals(threeBack.streetNameNoParens())) {
-
- if (((lastStep.relativeDirection == RelativeDirection.RIGHT ||
- lastStep.relativeDirection == RelativeDirection.HARD_RIGHT) &&
- (twoBack.relativeDirection == RelativeDirection.RIGHT ||
+ && lastStep.streetNameNoParens().equals(threeBack.streetNameNoParens())) {
+
+ if (((lastStep.relativeDirection == RelativeDirection.RIGHT ||
+ lastStep.relativeDirection == RelativeDirection.HARD_RIGHT) &&
+ (twoBack.relativeDirection == RelativeDirection.RIGHT ||
twoBack.relativeDirection == RelativeDirection.HARD_RIGHT)) ||
- ((lastStep.relativeDirection == RelativeDirection.LEFT ||
+ ((lastStep.relativeDirection == RelativeDirection.LEFT ||
lastStep.relativeDirection == RelativeDirection.HARD_LEFT) &&
(twoBack.relativeDirection == RelativeDirection.LEFT ||
- twoBack.relativeDirection == RelativeDirection.HARD_LEFT))) {
+ twoBack.relativeDirection == RelativeDirection.HARD_LEFT))) {
// in this case, we have two left turns or two right turns in quick
// succession; this is probably a U-turn.
-
+
steps.remove(last - 1);
-
+
lastStep.distance += twoBack.distance;
-
+
// A U-turn to the left, typical in the US.
- if (lastStep.relativeDirection == RelativeDirection.LEFT ||
- lastStep.relativeDirection == RelativeDirection.HARD_LEFT)
+ if (lastStep.relativeDirection == RelativeDirection.LEFT ||
+ lastStep.relativeDirection == RelativeDirection.HARD_LEFT)
lastStep.relativeDirection = RelativeDirection.UTURN_LEFT;
else
lastStep.relativeDirection = RelativeDirection.UTURN_RIGHT;
-
+
// in this case, we're definitely staying on the same street
// (since it's zag removal, the street names are the same)
lastStep.stayOn = true;
@@ -1232,7 +1284,7 @@ public static List generateWalkSteps(Graph graph, State[] states, Walk
} else {
if (!createdNewStep && step.elevation != null) {
List> s = encodeElevationProfile(edge, distance,
- backState.getOptions().geoidElevation ? -graph.ellipsoidToGeoidDifference : 0);
+ backState.getOptions().geoidElevation ? -graph.ellipsoidToGeoidDifference : 0);
if (step.elevation != null && step.elevation.size() > 0) {
step.elevation.addAll(s);
} else {
@@ -1253,12 +1305,12 @@ public static List generateWalkSteps(Graph graph, State[] states, Walk
// add bike rental information if applicable
if(onBikeRentalState != null && !steps.isEmpty()) {
- steps.get(steps.size()-1).bikeRentalOnStation =
- new BikeRentalStationInfo((BikeRentalStationVertex) onBikeRentalState.getBackEdge().getToVertex());
+ steps.get(steps.size()-1).bikeRentalOnStation =
+ new BikeRentalStationInfo((BikeRentalStationVertex) onBikeRentalState.getBackEdge().getToVertex());
}
if(offBikeRentalState != null && !steps.isEmpty()) {
- steps.get(0).bikeRentalOffStation =
- new BikeRentalStationInfo((BikeRentalStationVertex) offBikeRentalState.getBackEdge().getFromVertex());
+ steps.get(0).bikeRentalOffStation =
+ new BikeRentalStationInfo((BikeRentalStationVertex) offBikeRentalState.getBackEdge().getFromVertex());
}
return steps;
@@ -1288,7 +1340,7 @@ private static WalkStep createWalkStep(Graph graph, State s, Locale wantedLocale
step.lon = en.getFromVertex().getX();
step.lat = en.getFromVertex().getY();
step.elevation = encodeElevationProfile(s.getBackEdge(), 0,
- s.getOptions().geoidElevation ? -graph.ellipsoidToGeoidDifference : 0);
+ s.getOptions().geoidElevation ? -graph.ellipsoidToGeoidDifference : 0);
step.bogusName = en.hasBogusName();
step.addAlerts(graph.streetNotesService.getNotes(s), wantedLocale);
step.angle = DirectionUtils.getFirstAngle(s.getBackEdge().getGeometry());
diff --git a/src/main/java/org/opentripplanner/api/resource/InspectorLayersList.java b/src/main/java/org/opentripplanner/api/resource/InspectorLayersList.java
index b5ba3f269cf..4f95f30a3ca 100644
--- a/src/main/java/org/opentripplanner/api/resource/InspectorLayersList.java
+++ b/src/main/java/org/opentripplanner/api/resource/InspectorLayersList.java
@@ -4,21 +4,15 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
-import java.util.Set;
-import javax.xml.bind.annotation.XmlAttribute;
-import javax.xml.bind.annotation.XmlElement;
-import javax.xml.bind.annotation.XmlElements;
-import javax.xml.bind.annotation.XmlRootElement;
+
import org.opentripplanner.inspector.TileRenderer;
/**
*
* @author mabu
*/
-@XmlRootElement(name="InspectorLayersList")
public class InspectorLayersList {
- @XmlElements(value = {@XmlElement(name="layer") })
public List layers;
InspectorLayersList(Map renderers) {
@@ -32,10 +26,8 @@ public class InspectorLayersList {
private static class InspectorLayer {
- @XmlAttribute
@JsonSerialize
String key;
- @XmlAttribute
@JsonSerialize
String name;
diff --git a/src/main/java/org/opentripplanner/api/resource/LIsochrone.java b/src/main/java/org/opentripplanner/api/resource/LIsochrone.java
index dc2f975d1ae..05e4e875df7 100644
--- a/src/main/java/org/opentripplanner/api/resource/LIsochrone.java
+++ b/src/main/java/org/opentripplanner/api/resource/LIsochrone.java
@@ -40,7 +40,7 @@
import org.slf4j.LoggerFactory;
import com.google.common.io.Files;
-import com.vividsolutions.jts.geom.MultiPolygon;
+import org.locationtech.jts.geom.MultiPolygon;
/**
* Return isochrone geometry as a set of GeoJSON or zipped-shapefile multi-polygons.
diff --git a/src/main/java/org/opentripplanner/api/resource/ParkAndRide.java b/src/main/java/org/opentripplanner/api/resource/ParkAndRide.java
index 2188e709191..3254be8c5ac 100644
--- a/src/main/java/org/opentripplanner/api/resource/ParkAndRide.java
+++ b/src/main/java/org/opentripplanner/api/resource/ParkAndRide.java
@@ -13,8 +13,8 @@ the License, or (at your option) any later version.
package org.opentripplanner.api.resource;
-import com.vividsolutions.jts.geom.Coordinate;
-import com.vividsolutions.jts.geom.Envelope;
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.Envelope;
import org.opentripplanner.routing.graph.Vertex;
import org.opentripplanner.routing.vertextype.ParkAndRideVertex;
import org.opentripplanner.routing.vertextype.TransitStop;
diff --git a/src/main/java/org/opentripplanner/api/resource/Response.java b/src/main/java/org/opentripplanner/api/resource/Response.java
index b7415059ce8..c11810bf222 100644
--- a/src/main/java/org/opentripplanner/api/resource/Response.java
+++ b/src/main/java/org/opentripplanner/api/resource/Response.java
@@ -5,18 +5,14 @@
import java.util.Map.Entry;
import javax.ws.rs.core.UriInfo;
-import javax.xml.bind.annotation.XmlElement;
-import javax.xml.bind.annotation.XmlRootElement;
import org.opentripplanner.api.model.TripPlan;
import org.opentripplanner.api.model.error.PlannerError;
/** Represents a trip planner response, will be serialized into XML or JSON by Jersey */
-@XmlRootElement
public class Response {
/** A dictionary of the parameters provided in the request that triggered this response. */
- @XmlElement
public HashMap requestParameters;
private TripPlan plan;
private PlannerError error = null;
@@ -57,7 +53,6 @@ public void setPlan(TripPlan plan) {
}
/** The error (if any) that this response raised. */
- @XmlElement(required=false)
public PlannerError getError() {
return error;
}
diff --git a/src/main/java/org/opentripplanner/api/resource/Routers.java b/src/main/java/org/opentripplanner/api/resource/Routers.java
index caac36b0b6a..22ff5249550 100644
--- a/src/main/java/org/opentripplanner/api/resource/Routers.java
+++ b/src/main/java/org/opentripplanner/api/resource/Routers.java
@@ -32,7 +32,6 @@
import org.opentripplanner.graph_builder.GraphBuilder;
import org.opentripplanner.routing.error.GraphNotFoundException;
import org.opentripplanner.routing.graph.Graph;
-import org.opentripplanner.routing.graph.Graph.LoadLevel;
import org.opentripplanner.routing.impl.MemoryGraphSource;
import org.opentripplanner.routing.services.GraphService;
import org.opentripplanner.standalone.CommandLineParameters;
@@ -46,40 +45,40 @@
/**
* This REST API endpoint allows remotely loading, reloading, and evicting graphs on a running server.
- *
+ *
* A GraphService maintains a mapping between routerIds and specific Graph objects.
* The HTTP verbs are used as follows to manipulate that mapping:
- *
+ *
* GET - see the registered routerIds and Graphs, verify whether a particular routerId is registered
* PUT - create or replace a mapping from a routerId to a Graph loaded from the server filesystem
* POST - create or replace a mapping from a routerId to a serialized Graph sent in the request
* DELETE - de-register a routerId, releasing the reference to the associated graph
- *
+ *
* The HTTP request URLs are of the form /ws/routers/{routerId}, where the routerId is optional.
* If a routerId is supplied in the URL, the verb will act upon the mapping for that specific
* routerId. If no routerId is given, the verb will act upon all routerIds currently registered.
- *
+ *
* For example:
- *
+ *
* GET http://localhost/otp-rest-servlet/ws/routers
* will retrieve a list of all registered routerId -> Graph mappings and their geographic bounds.
- *
+ *
* GET http://localhost/otp-rest-servlet/ws/routers/london
* will return status code 200 and a brief description of the 'london' graph including geographic
* bounds, or 404 if the 'london' routerId is not registered.
- *
+ *
* PUT http://localhost/otp-rest-servlet/ws/routers
* will reload the graphs for all currently registered routerIds from disk.
- *
+ *
* PUT http://localhost/otp-rest-servlet/ws/routers/paris
* will load a Graph from a sub-directory called 'paris' and associate it with the routerId 'paris'.
- *
+ *
* DELETE http://localhost/otp-rest-servlet/ws/routers/paris
* will release the Paris Graph and de-register the 'paris' routerId.
- *
+ *
* DELETE http://localhost/otp-rest-servlet/ws/routers
* will de-register all currently registered routerIds.
- *
+ *
* The GET methods are not secured, but all other methods are secured under ROLE_ROUTERS.
* See documentation for individual methods for additional parameters.
*/
@@ -91,7 +90,7 @@ public class Routers {
@Context OTPServer otpServer;
- /**
+ /**
* Returns a list of routers and their bounds.
* @return a representation of the graphs and their geographic bounds, in JSON or XML depending
* on the Accept header in the HTTP request.
@@ -110,7 +109,7 @@ public RouterList getRouterIds() {
return routerList;
}
- /**
+ /**
* Returns the bounds for a specific routerId, or verifies whether it is registered.
* @returns status code 200 if the routerId is registered, otherwise a 404.
*/
@@ -121,11 +120,11 @@ public RouterInfo getGraphId(@PathParam("routerId") String routerId) {
RouterInfo routerInfo = getRouterInfo(routerId);
if (routerInfo == null)
throw new WebApplicationException(Response.status(Status.NOT_FOUND)
- .entity("Graph id '" + routerId + "' not registered.\n").type("text/plain")
- .build());
+ .entity("Graph id '" + routerId + "' not registered.\n").type("text/plain")
+ .build());
return routerInfo;
}
-
+
private RouterInfo getRouterInfo(String routerId) {
try {
Router router = otpServer.getRouter(routerId);
@@ -138,19 +137,19 @@ private RouterInfo getRouterInfo(String routerId) {
}
}
- /**
+ /**
* Reload the graphs for all registered routerIds from disk.
*/
@RolesAllowed({ "ROUTERS" })
@PUT @Produces({ MediaType.APPLICATION_JSON })
public Response reloadGraphs(@QueryParam("path") String path,
- @QueryParam("preEvict") @DefaultValue("true") boolean preEvict,
- @QueryParam("force") @DefaultValue("true") boolean force) {
+ @QueryParam("preEvict") @DefaultValue("true") boolean preEvict,
+ @QueryParam("force") @DefaultValue("true") boolean force) {
otpServer.getGraphService().reloadGraphs(preEvict, force);
return Response.status(Status.OK).build();
}
- /**
+ /**
* Load the graph for the specified routerId from disk.
* @param preEvict before reloading each graph, evict the existing graph. This will prevent
* memory usage from increasing during the reload, but routing will be unavailable on this
@@ -159,7 +158,7 @@ public Response reloadGraphs(@QueryParam("path") String path,
@RolesAllowed({ "ROUTERS" })
@PUT @Path("{routerId}") @Produces({ MediaType.TEXT_PLAIN })
public Response putGraphId(@PathParam("routerId") String routerId,
- @QueryParam("preEvict") @DefaultValue("true") boolean preEvict) {
+ @QueryParam("preEvict") @DefaultValue("true") boolean preEvict) {
LOG.debug("Attempting to load graph '{}' from server's local filesystem.", routerId);
GraphService graphService = otpServer.getGraphService();
if (graphService.getRouterIds().contains(routerId)) {
@@ -170,7 +169,7 @@ public Response putGraphId(@PathParam("routerId") String routerId,
return Response.status(404).entity("graph already registered, but reload failed.\n").build();
} else {
boolean success = graphService.registerGraph(routerId, graphService
- .getGraphSourceFactory().createGraphSource(routerId));
+ .getGraphSourceFactory().createGraphSource(routerId));
if (success)
return Response.status(201).entity("graph registered.\n").build();
else
@@ -178,7 +177,7 @@ public Response putGraphId(@PathParam("routerId") String routerId,
}
}
- /**
+ /**
* Deserialize a graph sent with the HTTP request as POST data, associating it with the given
* routerId.
*/
@@ -186,10 +185,9 @@ public Response putGraphId(@PathParam("routerId") String routerId,
@POST @Path("{routerId}") @Produces({ MediaType.TEXT_PLAIN })
@Consumes(MediaType.APPLICATION_OCTET_STREAM)
public Response postGraphOverWire (
- @PathParam("routerId") String routerId,
- @QueryParam("preEvict") @DefaultValue("true") boolean preEvict,
- @QueryParam("loadLevel") @DefaultValue("FULL") LoadLevel level,
- InputStream is) {
+ @PathParam("routerId") String routerId,
+ @QueryParam("preEvict") @DefaultValue("true") boolean preEvict,
+ InputStream is) {
if (preEvict) {
LOG.debug("pre-evicting graph");
otpServer.getGraphService().evictRouter(routerId);
@@ -197,7 +195,7 @@ public Response postGraphOverWire (
LOG.debug("deserializing graph from POST data stream...");
Graph graph;
try {
- graph = Graph.load(is, level);
+ graph = Graph.load(is);
GraphService graphService = otpServer.getGraphService();
graphService.registerGraph(routerId, new MemoryGraphSource(routerId, graph));
return Response.status(Status.CREATED).entity(graph.toString() + "\n").build();
@@ -205,7 +203,7 @@ public Response postGraphOverWire (
return Response.status(Status.BAD_REQUEST).entity(e.toString() + "\n").build();
}
}
-
+
/**
* Build a graph from data in the ZIP file posted over the wire, associating it with the given router ID.
* This method will be selected when the Content-Type is application/zip.
@@ -214,35 +212,35 @@ public Response postGraphOverWire (
@POST @Path("{routerId}") @Consumes({"application/zip"})
@Produces({ MediaType.TEXT_PLAIN })
public Response buildGraphOverWire (
- @PathParam("routerId") String routerId,
- @QueryParam("preEvict") @DefaultValue("true") boolean preEvict,
- InputStream input) {
+ @PathParam("routerId") String routerId,
+ @QueryParam("preEvict") @DefaultValue("true") boolean preEvict,
+ InputStream input) {
// TODO: async processing
-
+
if (preEvict) {
LOG.debug("Pre-evicting graph with routerId {} before building new graph", routerId);
otpServer.getGraphService().evictRouter(routerId);
}
-
+
// get a temporary directory, using Google Guava
File tempDir = Files.createTempDir();
-
+
// extract the zip file to the temp dir
ZipInputStream zis = new ZipInputStream(input);
-
+
try {
for (ZipEntry entry = zis.getNextEntry(); entry != null; entry = zis.getNextEntry()) {
if (entry.isDirectory())
// we only support flat ZIP files
return Response.status(Response.Status.BAD_REQUEST)
- .entity("ZIP files containing directories are not supported").build();
-
+ .entity("ZIP files containing directories are not supported").build();
+
File file = new File(tempDir, entry.getName());
-
+
if (!file.getParentFile().equals(tempDir))
return Response.status(Response.Status.BAD_REQUEST)
- .entity("ZIP files containing directories are not supported").build();
-
+ .entity("ZIP files containing directories are not supported").build();
+
OutputStream os = new FileOutputStream(file);
ByteStreams.copy(zis, os);
os.close();
@@ -256,32 +254,32 @@ public Response buildGraphOverWire (
CommandLineParameters params = otpServer.params.clone();
params.build = tempDir;
params.inMemory = true;
-
+
GraphBuilder graphBuilder = GraphBuilder.forDirectory(params, tempDir);
-
+
graphBuilder.run();
-
+
// remove the temporary directory
// this doesn't work for nested directories, but the extract doesn't either,
// so we'll crash long before we get here . . .
for (File file : tempDir.listFiles()) {
file.delete();
}
-
+
tempDir.delete();
-
+
Graph graph = graphBuilder.getGraph();
// re-index the graph to ensure all data is added and recreate a new streetIndex. It's ok to recreate the
// streetIndex because the previous one created during graph build is not needed anymore and isn't able to be
// used outside of the graphBuilder.
graph.index(true);
-
+
GraphService graphService = otpServer.getGraphService();
graphService.registerGraph(routerId, new MemoryGraphSource(routerId, graph));
return Response.status(Status.CREATED).entity(graph.toString() + "\n").build();
}
-
- /**
+
+ /**
* Save the graph data, but don't load it in memory. The file location is based on routerId.
* If the graph already exists, the graph will be overwritten.
*/
@@ -289,8 +287,8 @@ public Response buildGraphOverWire (
@POST @Path("/save") @Produces({ MediaType.TEXT_PLAIN })
@Consumes(MediaType.APPLICATION_OCTET_STREAM)
public Response saveGraphOverWire (
- @QueryParam("routerId") String routerId,
- InputStream is) {
+ @QueryParam("routerId") String routerId,
+ InputStream is) {
LOG.debug("save graph from POST data stream...");
try {
boolean success = otpServer.getGraphService().getGraphSourceFactory().save(routerId, is);
@@ -313,7 +311,7 @@ public Response deleteAll() {
return Response.status(200).entity(message).build();
}
- /**
+ /**
* De-register a specific routerId, evicting the associated graph from memory.
* @return status code 200 if the routerId was de-registered,
* 404 if the routerId was not registered.
diff --git a/src/main/java/org/opentripplanner/api/resource/SIsochrone.java b/src/main/java/org/opentripplanner/api/resource/SIsochrone.java
index 2bee1913d62..8b313d409dc 100644
--- a/src/main/java/org/opentripplanner/api/resource/SIsochrone.java
+++ b/src/main/java/org/opentripplanner/api/resource/SIsochrone.java
@@ -1,8 +1,8 @@
package org.opentripplanner.api.resource;
import com.google.common.collect.Maps;
-import com.vividsolutions.jts.geom.*;
-import com.vividsolutions.jts.linearref.LengthIndexedLine;
+import org.locationtech.jts.geom.*;
+import org.locationtech.jts.linearref.LengthIndexedLine;
import org.geotools.geojson.geom.GeometryJSON;
import org.geotools.referencing.GeodeticCalculator;
import org.opensphere.geometry.algorithm.ConcaveHull;
diff --git a/src/main/java/org/opentripplanner/api/resource/ServerInfo.java b/src/main/java/org/opentripplanner/api/resource/ServerInfo.java
index 585cbf30db5..b724cf3b2d0 100644
--- a/src/main/java/org/opentripplanner/api/resource/ServerInfo.java
+++ b/src/main/java/org/opentripplanner/api/resource/ServerInfo.java
@@ -6,8 +6,7 @@
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
-import javax.xml.bind.annotation.XmlElement;
-import javax.xml.bind.annotation.XmlRootElement;
+
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.InputStream;
@@ -15,7 +14,6 @@
import java.nio.charset.Charset;
@Path("/")
-@XmlRootElement
public class ServerInfo {
/** Quality value prioritizes MIME types */
@@ -29,16 +27,12 @@ public static ServerInfo getServerInfo() {
return SERVER_INFO;
}
- // Fields must be public or have a public getter to be auto-serialized to JSON;
- // they are annotated with @XmlElement to be serialized to XML elements (as opposed to attributes).
+ // Fields must be public or have a public getter to be auto-serialized to JSON
- @XmlElement
- public MavenVersion serverVersion = MavenVersion.VERSION;
+ public MavenVersion serverVersion = MavenVersion.VERSION;
- @XmlElement
public String cpuName = "unknown";
- @XmlElement
public int nCores = 0;
/* It would make sense to have one object containing maven, git, and hardware subobjects. */
diff --git a/src/main/java/org/opentripplanner/api/resource/SimpleIsochrone.java b/src/main/java/org/opentripplanner/api/resource/SimpleIsochrone.java
index 0ab55063d5a..b4e81d05d86 100644
--- a/src/main/java/org/opentripplanner/api/resource/SimpleIsochrone.java
+++ b/src/main/java/org/opentripplanner/api/resource/SimpleIsochrone.java
@@ -56,11 +56,11 @@
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
import com.google.common.io.Files;
-import com.vividsolutions.jts.geom.Coordinate;
-import com.vividsolutions.jts.geom.Geometry;
-import com.vividsolutions.jts.geom.GeometryFactory;
-import com.vividsolutions.jts.geom.MultiPolygon;
-import com.vividsolutions.jts.geom.Point;
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.Geometry;
+import org.locationtech.jts.geom.GeometryFactory;
+import org.locationtech.jts.geom.MultiPolygon;
+import org.locationtech.jts.geom.Point;
/**
* This class provides a vector isochrone generator for places without good OSM street connectivity,
diff --git a/src/main/java/org/opentripplanner/api/resource/VehicleRental.java b/src/main/java/org/opentripplanner/api/resource/VehicleRental.java
index 52001831af4..07763dfd128 100644
--- a/src/main/java/org/opentripplanner/api/resource/VehicleRental.java
+++ b/src/main/java/org/opentripplanner/api/resource/VehicleRental.java
@@ -13,7 +13,7 @@ the License, or (at your option) any later version.
package org.opentripplanner.api.resource;
-import com.vividsolutions.jts.geom.Envelope;
+import org.locationtech.jts.geom.Envelope;
import org.opentripplanner.routing.vehicle_rental.VehicleRentalStation;
import org.opentripplanner.routing.vehicle_rental.VehicleRentalStationService;
import org.opentripplanner.standalone.OTPServer;
diff --git a/src/main/java/org/opentripplanner/common/StreetUtils.java b/src/main/java/org/opentripplanner/common/StreetUtils.java
index 88195698870..34abb5c318c 100644
--- a/src/main/java/org/opentripplanner/common/StreetUtils.java
+++ b/src/main/java/org/opentripplanner/common/StreetUtils.java
@@ -1,7 +1,7 @@
package org.opentripplanner.common;
-import com.vividsolutions.jts.geom.Geometry;
-import com.vividsolutions.jts.geom.Polygon;
+import org.locationtech.jts.geom.Geometry;
+import org.locationtech.jts.geom.Polygon;
import org.opentripplanner.common.geometry.Subgraph;
import org.opentripplanner.graph_builder.annotation.GraphConnectivity;
import org.opentripplanner.routing.core.RoutingRequest;
diff --git a/src/main/java/org/opentripplanner/common/geometry/AccumulativeGridSampler.java b/src/main/java/org/opentripplanner/common/geometry/AccumulativeGridSampler.java
index 6da767168d6..172bf5fea2d 100644
--- a/src/main/java/org/opentripplanner/common/geometry/AccumulativeGridSampler.java
+++ b/src/main/java/org/opentripplanner/common/geometry/AccumulativeGridSampler.java
@@ -7,7 +7,7 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import com.vividsolutions.jts.geom.Coordinate;
+import org.locationtech.jts.geom.Coordinate;
/**
* Helper class to fill-in a ZSampleGrid from a given loosely-defined set of sampling points.
diff --git a/src/main/java/org/opentripplanner/common/geometry/CompactElevationProfile.java b/src/main/java/org/opentripplanner/common/geometry/CompactElevationProfile.java
index e5e4e2f0bbc..8aa5704de22 100644
--- a/src/main/java/org/opentripplanner/common/geometry/CompactElevationProfile.java
+++ b/src/main/java/org/opentripplanner/common/geometry/CompactElevationProfile.java
@@ -2,8 +2,8 @@
import java.io.Serializable;
-import com.vividsolutions.jts.geom.Coordinate;
-import com.vividsolutions.jts.geom.CoordinateSequence;
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.CoordinateSequence;
/**
* Compact elevation profile. To optimize storage, we use the following tricks:
diff --git a/src/main/java/org/opentripplanner/common/geometry/CompactLineString.java b/src/main/java/org/opentripplanner/common/geometry/CompactLineString.java
index cf7db0291cb..2d2135fc958 100644
--- a/src/main/java/org/opentripplanner/common/geometry/CompactLineString.java
+++ b/src/main/java/org/opentripplanner/common/geometry/CompactLineString.java
@@ -2,9 +2,9 @@
import java.io.Serializable;
-import com.vividsolutions.jts.geom.Coordinate;
-import com.vividsolutions.jts.geom.GeometryFactory;
-import com.vividsolutions.jts.geom.LineString;
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.GeometryFactory;
+import org.locationtech.jts.geom.LineString;
/**
* Compact line string. To optimize storage, we use the following tricks:
diff --git a/src/main/java/org/opentripplanner/common/geometry/DelaunayIsolineBuilder.java b/src/main/java/org/opentripplanner/common/geometry/DelaunayIsolineBuilder.java
index d5f458dc703..82bf58f0a8f 100644
--- a/src/main/java/org/opentripplanner/common/geometry/DelaunayIsolineBuilder.java
+++ b/src/main/java/org/opentripplanner/common/geometry/DelaunayIsolineBuilder.java
@@ -10,12 +10,12 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import com.vividsolutions.jts.algorithm.CGAlgorithms;
-import com.vividsolutions.jts.geom.Coordinate;
-import com.vividsolutions.jts.geom.Geometry;
-import com.vividsolutions.jts.geom.GeometryFactory;
-import com.vividsolutions.jts.geom.LinearRing;
-import com.vividsolutions.jts.geom.Polygon;
+import org.locationtech.jts.algorithm.CGAlgorithms;
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.Geometry;
+import org.locationtech.jts.geom.GeometryFactory;
+import org.locationtech.jts.geom.LinearRing;
+import org.locationtech.jts.geom.Polygon;
/**
* Compute isoline based on a Delaunay triangulation of z samplings.
diff --git a/src/main/java/org/opentripplanner/common/geometry/DelaunayTriangulation.java b/src/main/java/org/opentripplanner/common/geometry/DelaunayTriangulation.java
index 8b2e62046b4..d0f6f3cb42e 100644
--- a/src/main/java/org/opentripplanner/common/geometry/DelaunayTriangulation.java
+++ b/src/main/java/org/opentripplanner/common/geometry/DelaunayTriangulation.java
@@ -1,6 +1,6 @@
package org.opentripplanner.common.geometry;
-import com.vividsolutions.jts.geom.Coordinate;
+import org.locationtech.jts.geom.Coordinate;
/**
* A DelaunayPoint is the geometrical point of a node of the triangulation.
diff --git a/src/main/java/org/opentripplanner/common/geometry/DirectionUtils.java b/src/main/java/org/opentripplanner/common/geometry/DirectionUtils.java
index d920beb2d23..bed0607a214 100644
--- a/src/main/java/org/opentripplanner/common/geometry/DirectionUtils.java
+++ b/src/main/java/org/opentripplanner/common/geometry/DirectionUtils.java
@@ -2,10 +2,10 @@
import org.apache.commons.math3.util.FastMath;
-import com.vividsolutions.jts.geom.Coordinate;
-import com.vividsolutions.jts.geom.Geometry;
-import com.vividsolutions.jts.geom.LineString;
-import com.vividsolutions.jts.geom.MultiLineString;
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.Geometry;
+import org.locationtech.jts.geom.LineString;
+import org.locationtech.jts.geom.MultiLineString;
public class DirectionUtils {
diff --git a/src/main/java/org/opentripplanner/common/geometry/GeoJsonModule.java b/src/main/java/org/opentripplanner/common/geometry/GeoJsonModule.java
new file mode 100644
index 00000000000..c53f6cb5b7b
--- /dev/null
+++ b/src/main/java/org/opentripplanner/common/geometry/GeoJsonModule.java
@@ -0,0 +1,15 @@
+package org.opentripplanner.common.geometry;
+
+import com.fasterxml.jackson.core.Version;
+import com.fasterxml.jackson.databind.module.SimpleModule;
+import org.locationtech.jts.geom.Geometry;
+
+public class GeoJsonModule extends SimpleModule {
+
+ public GeoJsonModule() {
+ super("GeoJson", new Version(1, 0, 0, null, "com.bedatadriven", "jackson-geojson"));
+ addSerializer(Geometry.class, new GeometrySerializer());
+ addDeserializer(Geometry.class, new GeometryDeserializer());
+ }
+
+}
diff --git a/src/main/java/org/opentripplanner/common/geometry/GeometryDeserializer.java b/src/main/java/org/opentripplanner/common/geometry/GeometryDeserializer.java
new file mode 100644
index 00000000000..f6fde5fea85
--- /dev/null
+++ b/src/main/java/org/opentripplanner/common/geometry/GeometryDeserializer.java
@@ -0,0 +1,107 @@
+package org.opentripplanner.common.geometry;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.ObjectCodec;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonDeserializer;
+import com.fasterxml.jackson.databind.JsonNode;
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.Geometry;
+import org.locationtech.jts.geom.GeometryFactory;
+import org.locationtech.jts.geom.LineString;
+import org.locationtech.jts.geom.LinearRing;
+import org.locationtech.jts.geom.Polygon;
+
+import java.io.IOException;
+
+public class GeometryDeserializer extends JsonDeserializer {
+
+ private GeometryFactory gf = new GeometryFactory();
+
+ @Override
+ public Geometry deserialize(JsonParser jp, DeserializationContext ctxt)
+ throws IOException {
+
+ ObjectCodec oc = jp.getCodec();
+ JsonNode root = oc.readTree(jp);
+ return parseGeometry(root);
+ }
+
+ private Geometry parseGeometry(JsonNode root) {
+ String typeName = root.get("type").asText();
+ if (typeName.equals("Point")) {
+ return gf.createPoint(parseCoordinate(root.get("coordinates")));
+ } else if(typeName.equals("MultiPoint")) {
+ return gf.createMultiPoint(parseLineString(root.get("coordinates")));
+ } else if(typeName.equals("LineString")) {
+ return gf.createLineString(parseLineString(root.get("coordinates")));
+ } else if (typeName.equals("MultiLineString")) {
+ return gf.createMultiLineString(parseLineStrings(root.get("coordinates")));
+ } else if(typeName.equals("Polygon")) {
+ JsonNode arrayOfRings = root.get("coordinates");
+ return parsePolygonCoordinates(arrayOfRings);
+ } else if (typeName.equals("MultiPolygon")) {
+ JsonNode arrayOfPolygons = root.get("coordinates");
+ return gf.createMultiPolygon(parsePolygons(arrayOfPolygons));
+ } else if (typeName.equals("GeometryCollection")) {
+ return gf.createGeometryCollection(parseGeometries(root.get("geometries")));
+ } else {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ private Geometry[] parseGeometries(JsonNode arrayOfGeoms) {
+ Geometry[] items = new Geometry[arrayOfGeoms.size()];
+ for(int i=0;i!=arrayOfGeoms.size();++i) {
+ items[i] = parseGeometry(arrayOfGeoms.get(i));
+ }
+ return items;
+ }
+
+ private Polygon parsePolygonCoordinates(JsonNode arrayOfRings) {
+ return gf.createPolygon(parseExteriorRing(arrayOfRings),
+ parseInteriorRings(arrayOfRings));
+ }
+
+ private Polygon[] parsePolygons(JsonNode arrayOfPolygons) {
+ Polygon[] polygons = new Polygon[arrayOfPolygons.size()];
+ for (int i = 0; i != arrayOfPolygons.size(); ++i) {
+ polygons[i] = parsePolygonCoordinates(arrayOfPolygons.get(i));
+ }
+ return polygons;
+ }
+
+ private LinearRing parseExteriorRing(JsonNode arrayOfRings) {
+ return gf.createLinearRing(parseLineString(arrayOfRings.get(0)));
+ }
+
+ private LinearRing[] parseInteriorRings(JsonNode arrayOfRings) {
+ LinearRing rings[] = new LinearRing[arrayOfRings.size() - 1];
+ for (int i = 1; i < arrayOfRings.size(); ++i) {
+ rings[i - 1] = gf.createLinearRing(parseLineString(arrayOfRings
+ .get(i)));
+ }
+ return rings;
+ }
+
+ private Coordinate parseCoordinate(JsonNode array) {
+ return new Coordinate(array.get(0).asDouble(), array.get(1).asDouble());
+ }
+
+ private Coordinate[] parseLineString(JsonNode array) {
+ Coordinate[] points = new Coordinate[array.size()];
+ for (int i = 0; i != array.size(); ++i) {
+ points[i] = parseCoordinate(array.get(i));
+ }
+ return points;
+ }
+
+ private LineString[] parseLineStrings(JsonNode array) {
+ LineString[] strings = new LineString[array.size()];
+ for (int i = 0; i != array.size(); ++i) {
+ strings[i] = gf.createLineString(parseLineString(array.get(i)));
+ }
+ return strings;
+ }
+
+}
diff --git a/src/main/java/org/opentripplanner/common/geometry/GeometrySerializer.java b/src/main/java/org/opentripplanner/common/geometry/GeometrySerializer.java
new file mode 100644
index 00000000000..dc1e8fba2b4
--- /dev/null
+++ b/src/main/java/org/opentripplanner/common/geometry/GeometrySerializer.java
@@ -0,0 +1,176 @@
+package org.opentripplanner.common.geometry;
+
+import com.fasterxml.jackson.core.JsonGenerationException;
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import org.locationtech.jts.geom.Geometry;
+import org.locationtech.jts.geom.GeometryCollection;
+import org.locationtech.jts.geom.LineString;
+import org.locationtech.jts.geom.MultiLineString;
+import org.locationtech.jts.geom.MultiPoint;
+import org.locationtech.jts.geom.MultiPolygon;
+import org.locationtech.jts.geom.Point;
+import org.locationtech.jts.geom.Polygon;
+
+import java.io.IOException;
+
+public class GeometrySerializer extends JsonSerializer {
+
+ @Override
+ public void serialize(Geometry value, JsonGenerator jgen,
+ SerializerProvider provider) throws IOException,
+ JsonProcessingException {
+
+ writeGeometry(jgen, value);
+ }
+
+ public void writeGeometry(JsonGenerator jgen, Geometry value)
+ throws JsonGenerationException, IOException {
+ if (value instanceof Polygon) {
+ writePolygon(jgen, (Polygon) value);
+
+ } else if(value instanceof Point) {
+ writePoint(jgen, (Point) value);
+
+ } else if (value instanceof MultiPoint) {
+ writeMultiPoint(jgen, (MultiPoint) value);
+
+ } else if (value instanceof MultiPolygon) {
+ writeMultiPolygon(jgen, (MultiPolygon) value);
+
+ } else if (value instanceof LineString) {
+ writeLineString(jgen, (LineString) value);
+
+ } else if (value instanceof MultiLineString) {
+ writeMultiLineString(jgen, (MultiLineString) value);
+ } else if (value instanceof GeometryCollection) {
+ writeGeometryCollection(jgen, (GeometryCollection) value);
+
+ } else {
+ throw new UnsupportedOperationException("not implemented: "
+ + value.getClass().getName());
+ }
+ }
+
+ private void writeGeometryCollection(JsonGenerator jgen,
+ GeometryCollection value) throws JsonGenerationException,
+ IOException {
+ jgen.writeStartObject();
+ jgen.writeStringField("type", "GeometryCollection");
+ jgen.writeArrayFieldStart("geometries");
+
+ for (int i = 0; i != value.getNumGeometries(); ++i) {
+ writeGeometry(jgen, value.getGeometryN(i));
+ }
+
+ jgen.writeEndArray();
+ jgen.writeEndObject();
+ }
+
+ private void writeMultiPoint(JsonGenerator jgen, MultiPoint value)
+ throws JsonGenerationException, IOException {
+ jgen.writeStartObject();
+ jgen.writeStringField("type", "MultiPoint");
+ jgen.writeArrayFieldStart("coordinates");
+
+ for (int i = 0; i != value.getNumGeometries(); ++i) {
+ writePointCoords(jgen, (Point) value.getGeometryN(i));
+ }
+
+ jgen.writeEndArray();
+ jgen.writeEndObject();
+ }
+
+ private void writeMultiLineString(JsonGenerator jgen, MultiLineString value)
+ throws JsonGenerationException, IOException {
+ jgen.writeStartObject();
+ jgen.writeStringField("type", "MultiLineString");
+ jgen.writeArrayFieldStart("coordinates");
+
+ for (int i = 0; i != value.getNumGeometries(); ++i) {
+ writeLineStringCoords(jgen, (LineString) value.getGeometryN(i));
+ }
+
+ jgen.writeEndArray();
+ jgen.writeEndObject();
+ }
+
+ @Override
+ public Class handledType() {
+ return Geometry.class;
+ }
+
+ private void writeMultiPolygon(JsonGenerator jgen, MultiPolygon value)
+ throws JsonGenerationException, IOException {
+ jgen.writeStartObject();
+ jgen.writeStringField("type", "MultiPolygon");
+ jgen.writeArrayFieldStart("coordinates");
+
+ for (int i = 0; i != value.getNumGeometries(); ++i) {
+ writePolygonCoordinates(jgen, (Polygon) value.getGeometryN(i));
+ }
+
+ jgen.writeEndArray();
+ jgen.writeEndObject();
+ }
+
+ private void writePolygon(JsonGenerator jgen, Polygon value)
+ throws JsonGenerationException, IOException {
+ jgen.writeStartObject();
+ jgen.writeStringField("type", "Polygon");
+ jgen.writeFieldName("coordinates");
+ writePolygonCoordinates(jgen, value);
+
+ jgen.writeEndObject();
+ }
+
+ private void writePolygonCoordinates(JsonGenerator jgen, Polygon value)
+ throws IOException, JsonGenerationException {
+ jgen.writeStartArray();
+ writeLineStringCoords(jgen, value.getExteriorRing());
+
+ for (int i = 0; i != value.getNumInteriorRing(); ++i) {
+ writeLineStringCoords(jgen, value.getInteriorRingN(i));
+ }
+ jgen.writeEndArray();
+ }
+
+ private void writeLineStringCoords(JsonGenerator jgen, LineString ring)
+ throws JsonGenerationException, IOException {
+ jgen.writeStartArray();
+ for (int i = 0; i != ring.getNumPoints(); ++i) {
+ Point p = ring.getPointN(i);
+ writePointCoords(jgen, p);
+ }
+ jgen.writeEndArray();
+ }
+
+ private void writeLineString(JsonGenerator jgen, LineString lineString)
+ throws JsonGenerationException, IOException {
+ jgen.writeStartObject();
+ jgen.writeStringField("type", "LineString");
+ jgen.writeFieldName("coordinates");
+ writeLineStringCoords(jgen, lineString);
+ jgen.writeEndObject();
+ }
+
+ private void writePoint(JsonGenerator jgen, Point p)
+ throws JsonGenerationException, IOException {
+ jgen.writeStartObject();
+ jgen.writeStringField("type", "Point");
+ jgen.writeFieldName("coordinates");
+ writePointCoords(jgen, p);
+ jgen.writeEndObject();
+ }
+
+ private void writePointCoords(JsonGenerator jgen, Point p)
+ throws IOException, JsonGenerationException {
+ jgen.writeStartArray();
+ jgen.writeNumber(p.getX());
+ jgen.writeNumber(p.getY());
+ jgen.writeEndArray();
+ }
+
+}
diff --git a/src/main/java/org/opentripplanner/common/geometry/GeometryUtils.java b/src/main/java/org/opentripplanner/common/geometry/GeometryUtils.java
index cf94279fc41..22a84245699 100644
--- a/src/main/java/org/opentripplanner/common/geometry/GeometryUtils.java
+++ b/src/main/java/org/opentripplanner/common/geometry/GeometryUtils.java
@@ -1,9 +1,11 @@
package org.opentripplanner.common.geometry;
-import com.vividsolutions.jts.geom.*;
-import com.vividsolutions.jts.linearref.LengthLocationMap;
-import com.vividsolutions.jts.linearref.LinearLocation;
-import com.vividsolutions.jts.linearref.LocationIndexedLine;
+import org.locationtech.jts.geom.*;
+import org.locationtech.jts.linearref.LengthLocationMap;
+import org.locationtech.jts.linearref.LinearLocation;
+import org.locationtech.jts.linearref.LocationIndexedLine;
+import org.locationtech.jts.io.ParseException;
+import org.locationtech.jts.io.WKTReader;
import org.geojson.GeoJsonObject;
import org.geojson.LngLatAlt;
import org.geotools.referencing.CRS;
@@ -98,7 +100,7 @@ public static LineString getInteriorSegment(Geometry geomerty, Coordinate first,
}
/**
- * Adapted from com.vividsolutions.jts.geom.LineSegment
+ * Adapted from org.locationtech.jts.geom.LineSegment
* Combines segmentFraction and projectionFactor methods.
*/
public static double segmentFraction(double x0, double y0, double x1, double y1,
@@ -181,4 +183,13 @@ private static Coordinate[] convertPath(List path) {
}
return coords;
}
+
+ public static Geometry parseWkt(String wkt) {
+ try {
+ return new WKTReader(GeometryUtils.getGeometryFactory()).read(wkt);
+ } catch(ParseException e) {
+ LOG.error("Unable to parse wkt: " + e);
+ }
+ return null;
+ }
}
diff --git a/src/main/java/org/opentripplanner/common/geometry/GraphUtils.java b/src/main/java/org/opentripplanner/common/geometry/GraphUtils.java
index 4767e9247cb..b5fb95b82cd 100644
--- a/src/main/java/org/opentripplanner/common/geometry/GraphUtils.java
+++ b/src/main/java/org/opentripplanner/common/geometry/GraphUtils.java
@@ -6,11 +6,11 @@
import org.opentripplanner.routing.graph.Graph;
import org.opentripplanner.routing.graph.Vertex;
-import com.vividsolutions.jts.algorithm.ConvexHull;
-import com.vividsolutions.jts.geom.Geometry;
-import com.vividsolutions.jts.geom.GeometryCollection;
-import com.vividsolutions.jts.geom.GeometryFactory;
-import com.vividsolutions.jts.simplify.DouglasPeuckerSimplifier;
+import org.locationtech.jts.algorithm.ConvexHull;
+import org.locationtech.jts.geom.Geometry;
+import org.locationtech.jts.geom.GeometryCollection;
+import org.locationtech.jts.geom.GeometryFactory;
+import org.locationtech.jts.simplify.DouglasPeuckerSimplifier;
public class GraphUtils {
diff --git a/src/main/java/org/opentripplanner/common/geometry/HashGridSpatialIndex.java b/src/main/java/org/opentripplanner/common/geometry/HashGridSpatialIndex.java
index 6f41b6ac25a..e958737a31b 100644
--- a/src/main/java/org/opentripplanner/common/geometry/HashGridSpatialIndex.java
+++ b/src/main/java/org/opentripplanner/common/geometry/HashGridSpatialIndex.java
@@ -16,11 +16,11 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import com.vividsolutions.jts.geom.Coordinate;
-import com.vividsolutions.jts.geom.Envelope;
-import com.vividsolutions.jts.geom.LineString;
-import com.vividsolutions.jts.index.ItemVisitor;
-import com.vividsolutions.jts.index.SpatialIndex;
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.Envelope;
+import org.locationtech.jts.geom.LineString;
+import org.locationtech.jts.index.ItemVisitor;
+import org.locationtech.jts.index.SpatialIndex;
/**
* A spatial index using a 2D fast long hashtable (Trove lib).
diff --git a/src/main/java/org/opentripplanner/common/geometry/IntPackedCoordinateSequence.java b/src/main/java/org/opentripplanner/common/geometry/IntPackedCoordinateSequence.java
deleted file mode 100644
index b5d826ef957..00000000000
--- a/src/main/java/org/opentripplanner/common/geometry/IntPackedCoordinateSequence.java
+++ /dev/null
@@ -1,111 +0,0 @@
-package org.opentripplanner.common.geometry;
-
-import java.io.Serializable;
-
-import com.vividsolutions.jts.geom.Coordinate;
-import com.vividsolutions.jts.geom.CoordinateSequence;
-import com.vividsolutions.jts.geom.Envelope;
-
-/**
- * 2D, supports only coordinates in range +/- 180.
- */
-public class IntPackedCoordinateSequence implements CoordinateSequence, Cloneable, Serializable {
-
- private static final long serialVersionUID = 1L;
-
- int[] ordinates;
-
- private static final double TO_FIXED = Integer.MAX_VALUE / 180.0;
- private static final double FROM_FIXED = 180.0 / Integer.MAX_VALUE;
-
- private int toFixedInt(double latlon) {
- return (int) (latlon * TO_FIXED);
- }
-
- private double fromFixedInt(int fixed) {
- return (double) (fixed * FROM_FIXED);
- }
-
- public IntPackedCoordinateSequence(Coordinate[] coordinates) {
- int nc = coordinates.length;
- ordinates = new int[nc * 2];
- int i = 0;
- for (Coordinate c : coordinates) {
- setOrdinate(i, 0, c.x);
- setOrdinate(i, 1, c.y);
- i++;
- }
- }
-
- @Override
- public int getDimension() {
- return 2;
- }
-
- @Override
- public Coordinate getCoordinate(int i) {
- return getCoordinateCopy(i);
- }
-
- @Override
- public Coordinate getCoordinateCopy(int i) {
- return new Coordinate(getX(i), getY(i));
- }
-
- @Override
- public void getCoordinate(int index, Coordinate coord) {
- coord.x = getX(index);
- coord.y = getY(index);
- }
-
- @Override
- public double getX(int index) {
- return fromFixedInt(ordinates[2*index]);
- }
-
- @Override
- public double getY(int index) {
- return fromFixedInt(ordinates[2*index + 1]);
- }
-
- @Override
- public double getOrdinate(int index, int ordinateIndex) {
- return fromFixedInt(ordinates[2*index + ordinateIndex]);
- }
-
- @Override
- public int size() {
- return ordinates.length / 2;
- }
-
- @Override
- public void setOrdinate(int index, int ordinateIndex, double value) {
- ordinates[2*index + ordinateIndex] = toFixedInt(value);
- }
-
- @Override
- public Coordinate[] toCoordinateArray() {
- Coordinate[] ret = new Coordinate[this.size()];
- for (int i = 0; i < this.size(); i++) {
- ret[i] = this.getCoordinate(i);
- }
- return ret;
- }
-
- @Override
- public Envelope expandEnvelope(Envelope env) {
- for (int i = 0; i < ordinates.length / 2; i++) {
- env.expandToInclude(getX(i), getY(i));
- }
- return env;
- }
-
- @Override
- public IntPackedCoordinateSequence clone() {
- try {
- return (IntPackedCoordinateSequence) super.clone();
- } catch (CloneNotSupportedException e) {
- throw new RuntimeException("programming error.", e);
- }
- }
-}
diff --git a/src/main/java/org/opentripplanner/common/geometry/IsolineBuilder.java b/src/main/java/org/opentripplanner/common/geometry/IsolineBuilder.java
index 82d8079d84d..b4195b7b7ac 100644
--- a/src/main/java/org/opentripplanner/common/geometry/IsolineBuilder.java
+++ b/src/main/java/org/opentripplanner/common/geometry/IsolineBuilder.java
@@ -1,6 +1,6 @@
package org.opentripplanner.common.geometry;
-import com.vividsolutions.jts.geom.Geometry;
+import org.locationtech.jts.geom.Geometry;
/**
* Generic interface for a class that compute an isoline out of a TZ 2D "field".
diff --git a/src/main/java/org/opentripplanner/common/geometry/PackedCoordinateSequence.java b/src/main/java/org/opentripplanner/common/geometry/PackedCoordinateSequence.java
index 50246d92d10..c22d120c4cd 100644
--- a/src/main/java/org/opentripplanner/common/geometry/PackedCoordinateSequence.java
+++ b/src/main/java/org/opentripplanner/common/geometry/PackedCoordinateSequence.java
@@ -3,9 +3,9 @@
import java.io.Serializable;
import java.lang.ref.SoftReference;
-import com.vividsolutions.jts.geom.Coordinate;
-import com.vividsolutions.jts.geom.CoordinateSequence;
-import com.vividsolutions.jts.geom.Envelope;
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.CoordinateSequence;
+import org.locationtech.jts.geom.Envelope;
/**
* A {@link CoordinateSequence} implementation based on a packed arrays. In this implementation,
@@ -36,14 +36,14 @@ public abstract class PackedCoordinateSequence implements CoordinateSequence, Se
transient protected SoftReference coordRef;
/**
- * @see com.vividsolutions.jts.geom.CoordinateSequence#getDimension()
+ * @see org.locationtech.jts.geom.CoordinateSequence#getDimension()
*/
public int getDimension() {
return this.dimension;
}
/**
- * @see com.vividsolutions.jts.geom.CoordinateSequence#getCoordinate(int)
+ * @see org.locationtech.jts.geom.CoordinateSequence#getCoordinate(int)
*/
public Coordinate getCoordinate(int i) {
Coordinate[] coords = getCachedCoords();
@@ -54,14 +54,14 @@ public Coordinate getCoordinate(int i) {
}
/**
- * @see com.vividsolutions.jts.geom.CoordinateSequence#getCoordinate(int)
+ * @see org.locationtech.jts.geom.CoordinateSequence#getCoordinate(int)
*/
public Coordinate getCoordinateCopy(int i) {
return getCoordinateInternal(i);
}
/**
- * @see com.vividsolutions.jts.geom.CoordinateSequence#getCoordinate(int)
+ * @see org.locationtech.jts.geom.CoordinateSequence#getCoordinate(int)
*/
public void getCoordinate(int i, Coordinate coord) {
coord.x = getOrdinate(i, 0);
@@ -69,7 +69,7 @@ public void getCoordinate(int i, Coordinate coord) {
}
/**
- * @see com.vividsolutions.jts.geom.CoordinateSequence#toCoordinateArray()
+ * @see org.locationtech.jts.geom.CoordinateSequence#toCoordinateArray()
*/
public Coordinate[] toCoordinateArray() {
Coordinate[] coords = getCachedCoords();
@@ -107,21 +107,21 @@ private Coordinate[] getCachedCoords() {
}
/**
- * @see com.vividsolutions.jts.geom.CoordinateSequence#getX(int)
+ * @see org.locationtech.jts.geom.CoordinateSequence#getX(int)
*/
public double getX(int index) {
return getOrdinate(index, 0);
}
/**
- * @see com.vividsolutions.jts.geom.CoordinateSequence#getY(int)
+ * @see org.locationtech.jts.geom.CoordinateSequence#getY(int)
*/
public double getY(int index) {
return getOrdinate(index, 1);
}
/**
- * @see com.vividsolutions.jts.geom.CoordinateSequence#getOrdinate(int, int)
+ * @see org.locationtech.jts.geom.CoordinateSequence#getOrdinate(int, int)
*/
public abstract double getOrdinate(int index, int ordinateIndex);
@@ -261,8 +261,6 @@ public Double(Coordinate[] coordinates) {
/**
* Builds a new empty packed coordinate sequence of a given size and dimension
- *
- * @param coordinates
*/
public Double(int size, int dimension) {
this.dimension = dimension;
@@ -270,7 +268,7 @@ public Double(int size, int dimension) {
}
/**
- * @see com.vividsolutions.jts.geom.CoordinateSequence#getCoordinate(int)
+ * @see org.locationtech.jts.geom.CoordinateSequence#getCoordinate(int)
*/
public Coordinate getCoordinateInternal(int i) {
double x = coords[i * dimension];
@@ -280,7 +278,7 @@ public Coordinate getCoordinateInternal(int i) {
}
/**
- * @see com.vividsolutions.jts.geom.CoordinateSequence#size()
+ * @see org.locationtech.jts.geom.CoordinateSequence#size()
*/
public int size() {
return coords.length / dimension;
@@ -296,7 +294,7 @@ public Object clone() {
}
/**
- * @see com.vividsolutions.jts.geom.CoordinateSequence#getOrdinate(int, int) Beware, for
+ * @see org.locationtech.jts.geom.CoordinateSequence#getOrdinate(int, int) Beware, for
* performace reasons the ordinate index is not checked, if it's over dimensions you
* may not get an exception but a meaningless value.
*/
@@ -305,7 +303,7 @@ public double getOrdinate(int index, int ordinate) {
}
/**
- * @see com.vividsolutions.jts.geom.PackedCoordinateSequence#setOrdinate(int, int, double)
+ * @see org.locationtech.jts.geom.CoordinateSequence#setOrdinate(int, int, double)
*/
public void setOrdinate(int index, int ordinate, double value) {
coordRef = null;
@@ -318,6 +316,13 @@ public Envelope expandEnvelope(Envelope env) {
}
return env;
}
+
+ @Override
+ public CoordinateSequence copy() {
+ Double clone = (Double) super.clone();
+ clone.coords = coords.clone();
+ return clone;
+ }
}
/**
@@ -352,9 +357,6 @@ public Float(float[] coords, int dimensions) {
/**
* Constructs a packed coordinate sequence from an array of doubles
- *
- * @param coordinates
- * @param dimension
*/
public Float(double[] coordinates, int dimensions) {
this.coords = new float[coordinates.length];
@@ -386,8 +388,6 @@ public Float(Coordinate[] coordinates, int dimension) {
/**
* Constructs an empty packed coordinate sequence of a given size and dimension
- *
- * @param coordinates
*/
public Float(int size, int dimension) {
this.dimension = dimension;
@@ -395,7 +395,7 @@ public Float(int size, int dimension) {
}
/**
- * @see com.vividsolutions.jts.geom.CoordinateSequence#getCoordinate(int)
+ * @see org.locationtech.jts.geom.CoordinateSequence#getCoordinate(int)
*/
public Coordinate getCoordinateInternal(int i) {
double x = coords[i * dimension];
@@ -405,7 +405,7 @@ public Coordinate getCoordinateInternal(int i) {
}
/**
- * @see com.vividsolutions.jts.geom.CoordinateSequence#size()
+ * @see org.locationtech.jts.geom.CoordinateSequence#size()
*/
public int size() {
return coords.length / dimension;
@@ -421,7 +421,7 @@ public Object clone() {
}
/**
- * @see com.vividsolutions.jts.geom.CoordinateSequence#getOrdinate(int, int) Beware, for
+ * @see org.locationtech.jts.geom.CoordinateSequence#getOrdinate(int, int) Beware, for
* performace reasons the ordinate index is not checked, if it's over dimensions you
* may not get an exception but a meaningless value.
*/
@@ -430,7 +430,7 @@ public double getOrdinate(int index, int ordinate) {
}
/**
- * @see com.vividsolutions.jts.geom.PackedCoordinateSequence#setOrdinate(int, int, double)
+ * @see org.locationtech.jts.geom.CoordinateSequence#setOrdinate(int, int, double)
*/
public void setOrdinate(int index, int ordinate, double value) {
coordRef = null;
@@ -444,6 +444,12 @@ public Envelope expandEnvelope(Envelope env) {
return env;
}
+ @Override
+ public CoordinateSequence copy() {
+ Float clone = (Float) super.clone();
+ clone.coords = coords.clone();
+ return clone;
+ }
}
public String toString() {
diff --git a/src/main/java/org/opentripplanner/common/geometry/RecursiveGridIsolineBuilder.java b/src/main/java/org/opentripplanner/common/geometry/RecursiveGridIsolineBuilder.java
index e6a09d16b3f..d1b4483ec42 100644
--- a/src/main/java/org/opentripplanner/common/geometry/RecursiveGridIsolineBuilder.java
+++ b/src/main/java/org/opentripplanner/common/geometry/RecursiveGridIsolineBuilder.java
@@ -15,12 +15,12 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import com.vividsolutions.jts.algorithm.CGAlgorithms;
-import com.vividsolutions.jts.geom.Coordinate;
-import com.vividsolutions.jts.geom.Geometry;
-import com.vividsolutions.jts.geom.GeometryFactory;
-import com.vividsolutions.jts.geom.LinearRing;
-import com.vividsolutions.jts.geom.Polygon;
+import org.locationtech.jts.algorithm.CGAlgorithms;
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.Geometry;
+import org.locationtech.jts.geom.GeometryFactory;
+import org.locationtech.jts.geom.LinearRing;
+import org.locationtech.jts.geom.Polygon;
/**
* Compute isoline based on a zFunc and a set of initial coverage points P0={(x,y)} to seed the
diff --git a/src/main/java/org/opentripplanner/common/geometry/ReversibleLineStringWrapper.java b/src/main/java/org/opentripplanner/common/geometry/ReversibleLineStringWrapper.java
index 04a9011d64a..94420a95a57 100644
--- a/src/main/java/org/opentripplanner/common/geometry/ReversibleLineStringWrapper.java
+++ b/src/main/java/org/opentripplanner/common/geometry/ReversibleLineStringWrapper.java
@@ -1,7 +1,7 @@
package org.opentripplanner.common.geometry;
-import com.vividsolutions.jts.geom.CoordinateSequence;
-import com.vividsolutions.jts.geom.LineString;
+import org.locationtech.jts.geom.CoordinateSequence;
+import org.locationtech.jts.geom.LineString;
public class ReversibleLineStringWrapper {
diff --git a/src/main/java/org/opentripplanner/common/geometry/Serializable2DPackedCoordinateSequenceFactory.java b/src/main/java/org/opentripplanner/common/geometry/Serializable2DPackedCoordinateSequenceFactory.java
index 0aaab78fa85..9efd40c3b47 100644
--- a/src/main/java/org/opentripplanner/common/geometry/Serializable2DPackedCoordinateSequenceFactory.java
+++ b/src/main/java/org/opentripplanner/common/geometry/Serializable2DPackedCoordinateSequenceFactory.java
@@ -2,9 +2,9 @@
import java.io.Serializable;
-import com.vividsolutions.jts.geom.Coordinate;
-import com.vividsolutions.jts.geom.CoordinateSequence;
-import com.vividsolutions.jts.geom.CoordinateSequenceFactory;
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.CoordinateSequence;
+import org.locationtech.jts.geom.CoordinateSequenceFactory;
public class Serializable2DPackedCoordinateSequenceFactory implements Serializable, CoordinateSequenceFactory {
@@ -13,7 +13,6 @@ public class Serializable2DPackedCoordinateSequenceFactory implements Serializab
@Override
public CoordinateSequence create(Coordinate[] coordinates) {
return new PackedCoordinateSequence.Double(coordinates, 2);
- //return new IntPackedCoordinateSequence(coordinates);
}
@Override
@@ -23,8 +22,7 @@ public CoordinateSequence create(CoordinateSequence coordSeq) {
@Override
public CoordinateSequence create(int size, int dimension) {
- throw new UnsupportedOperationException();
+ return new PackedCoordinateSequence.Double(size, dimension);
}
-
}
diff --git a/src/main/java/org/opentripplanner/common/geometry/SparseMatrixZSampleGrid.java b/src/main/java/org/opentripplanner/common/geometry/SparseMatrixZSampleGrid.java
index 20fd8cc538f..646b103be48 100644
--- a/src/main/java/org/opentripplanner/common/geometry/SparseMatrixZSampleGrid.java
+++ b/src/main/java/org/opentripplanner/common/geometry/SparseMatrixZSampleGrid.java
@@ -4,7 +4,7 @@
import java.util.Iterator;
import java.util.List;
-import com.vividsolutions.jts.geom.Coordinate;
+import org.locationtech.jts.geom.Coordinate;
/**
* A generic indexed grid of Z samples.
diff --git a/src/main/java/org/opentripplanner/common/geometry/SphericalDistanceLibrary.java b/src/main/java/org/opentripplanner/common/geometry/SphericalDistanceLibrary.java
index 3e77a3030dd..50f570f37c1 100644
--- a/src/main/java/org/opentripplanner/common/geometry/SphericalDistanceLibrary.java
+++ b/src/main/java/org/opentripplanner/common/geometry/SphericalDistanceLibrary.java
@@ -10,10 +10,10 @@
import org.apache.commons.math3.util.FastMath;
-import com.vividsolutions.jts.geom.Coordinate;
-import com.vividsolutions.jts.geom.Envelope;
-import com.vividsolutions.jts.geom.LineString;
-import com.vividsolutions.jts.geom.Point;
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.Envelope;
+import org.locationtech.jts.geom.LineString;
+import org.locationtech.jts.geom.Point;
public abstract class SphericalDistanceLibrary {
diff --git a/src/main/java/org/opentripplanner/common/geometry/Subgraph.java b/src/main/java/org/opentripplanner/common/geometry/Subgraph.java
index 6757ea1c1cd..5d9e1c964e5 100644
--- a/src/main/java/org/opentripplanner/common/geometry/Subgraph.java
+++ b/src/main/java/org/opentripplanner/common/geometry/Subgraph.java
@@ -8,10 +8,10 @@
import org.opentripplanner.routing.graph.Vertex;
import org.opentripplanner.routing.vertextype.TransitVertex;
-import com.vividsolutions.jts.geom.Coordinate;
-import com.vividsolutions.jts.geom.Geometry;
-import com.vividsolutions.jts.geom.GeometryFactory;
-import com.vividsolutions.jts.geom.MultiPoint;
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.Geometry;
+import org.locationtech.jts.geom.GeometryFactory;
+import org.locationtech.jts.geom.MultiPoint;
public class Subgraph {
diff --git a/src/main/java/org/opentripplanner/common/geometry/ZSampleGrid.java b/src/main/java/org/opentripplanner/common/geometry/ZSampleGrid.java
index 7d3d875879b..20dcdab3834 100644
--- a/src/main/java/org/opentripplanner/common/geometry/ZSampleGrid.java
+++ b/src/main/java/org/opentripplanner/common/geometry/ZSampleGrid.java
@@ -2,7 +2,7 @@
import org.opentripplanner.common.geometry.ZSampleGrid.ZSamplePoint;
-import com.vividsolutions.jts.geom.Coordinate;
+import org.locationtech.jts.geom.Coordinate;
/**
* A generic indexed grid of TZ samples. TZ could be anything but is usually a vector of parameters.
diff --git a/src/main/java/org/opentripplanner/common/model/GenericLocation.java b/src/main/java/org/opentripplanner/common/model/GenericLocation.java
index abce800e442..fc7fe652b4f 100644
--- a/src/main/java/org/opentripplanner/common/model/GenericLocation.java
+++ b/src/main/java/org/opentripplanner/common/model/GenericLocation.java
@@ -4,7 +4,7 @@
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-import com.vividsolutions.jts.geom.Coordinate;
+import org.locationtech.jts.geom.Coordinate;
/**
* Class describing a location provided by clients of routing. Used to describe end points (origin, destination) of a routing request as well as any
@@ -54,8 +54,10 @@ public class GenericLocation implements Cloneable, Serializable {
private static final String _doublePattern = "-{0,1}\\d+(\\.\\d+){0,1}";
// We want to ignore any number of non-digit characters at the beginning of the string, except
- // that signs are also non-digits. So ignore any number of non-(digit or sign or decimal point).
- private static final Pattern _latLonPattern = Pattern.compile("[^[\\d&&[-|+|.]]]*(" + _doublePattern
+ // that signs are also non-digits. So ignore any number of non-(digit or sign or decimal point).
+ // Regex has been rewritten following https://bugs.openjdk.java.net/browse/JDK-8189343
+ // from "[^[\\d&&[-|+|.]]]*(" to "[\\D&&[^-+.]]*("
+ private static final Pattern _latLonPattern = Pattern.compile("[\\D&&[^-+.]]*(" + _doublePattern
+ ")(\\s*,\\s*|\\s+)(" + _doublePattern + ")\\D*");
private static final Pattern _headingPattern = Pattern.compile("\\D*heading=("
diff --git a/src/main/java/org/opentripplanner/geocoder/AlternatingGeocoder.java b/src/main/java/org/opentripplanner/geocoder/AlternatingGeocoder.java
index b9d36adbe9f..506d6212504 100644
--- a/src/main/java/org/opentripplanner/geocoder/AlternatingGeocoder.java
+++ b/src/main/java/org/opentripplanner/geocoder/AlternatingGeocoder.java
@@ -1,6 +1,6 @@
package org.opentripplanner.geocoder;
-import com.vividsolutions.jts.geom.Envelope;
+import org.locationtech.jts.geom.Envelope;
public class AlternatingGeocoder implements Geocoder {
diff --git a/src/main/java/org/opentripplanner/geocoder/BackupGeocoder.java b/src/main/java/org/opentripplanner/geocoder/BackupGeocoder.java
index 262a5c3a6fe..c865ed2ab3f 100644
--- a/src/main/java/org/opentripplanner/geocoder/BackupGeocoder.java
+++ b/src/main/java/org/opentripplanner/geocoder/BackupGeocoder.java
@@ -1,6 +1,6 @@
package org.opentripplanner.geocoder;
-import com.vividsolutions.jts.geom.Envelope;
+import org.locationtech.jts.geom.Envelope;
/**
* Multiplexe two geocoders: a master and a backup.
diff --git a/src/main/java/org/opentripplanner/geocoder/Geocoder.java b/src/main/java/org/opentripplanner/geocoder/Geocoder.java
index b6a08dedebe..3fb093c8138 100644
--- a/src/main/java/org/opentripplanner/geocoder/Geocoder.java
+++ b/src/main/java/org/opentripplanner/geocoder/Geocoder.java
@@ -1,6 +1,6 @@
package org.opentripplanner.geocoder;
-import com.vividsolutions.jts.geom.Envelope;
+import org.locationtech.jts.geom.Envelope;
public interface Geocoder {
diff --git a/src/main/java/org/opentripplanner/geocoder/GeocoderGeoZoneCropper.java b/src/main/java/org/opentripplanner/geocoder/GeocoderGeoZoneCropper.java
index cac20b40d9e..0bd563c4280 100644
--- a/src/main/java/org/opentripplanner/geocoder/GeocoderGeoZoneCropper.java
+++ b/src/main/java/org/opentripplanner/geocoder/GeocoderGeoZoneCropper.java
@@ -7,7 +7,7 @@
import org.opentripplanner.geocoder.GeocoderResult;
import org.opentripplanner.geocoder.GeocoderResults;
-import com.vividsolutions.jts.geom.Envelope;
+import org.locationtech.jts.geom.Envelope;
/**
* Filter results of a geocoding request by removing elements outside of the covered geographical
diff --git a/src/main/java/org/opentripplanner/geocoder/GeocoderMultipleResultsStubImpl.java b/src/main/java/org/opentripplanner/geocoder/GeocoderMultipleResultsStubImpl.java
index bb67cb6bfe0..108a691d80f 100644
--- a/src/main/java/org/opentripplanner/geocoder/GeocoderMultipleResultsStubImpl.java
+++ b/src/main/java/org/opentripplanner/geocoder/GeocoderMultipleResultsStubImpl.java
@@ -2,7 +2,7 @@
import java.util.Collection;
-import com.vividsolutions.jts.geom.Envelope;
+import org.locationtech.jts.geom.Envelope;
public class GeocoderMultipleResultsStubImpl implements Geocoder {
diff --git a/src/main/java/org/opentripplanner/geocoder/GeocoderNullImpl.java b/src/main/java/org/opentripplanner/geocoder/GeocoderNullImpl.java
index a9526cd8d8c..9296901528c 100644
--- a/src/main/java/org/opentripplanner/geocoder/GeocoderNullImpl.java
+++ b/src/main/java/org/opentripplanner/geocoder/GeocoderNullImpl.java
@@ -1,6 +1,6 @@
package org.opentripplanner.geocoder;
-import com.vividsolutions.jts.geom.Envelope;
+import org.locationtech.jts.geom.Envelope;
public class GeocoderNullImpl implements Geocoder {
diff --git a/src/main/java/org/opentripplanner/geocoder/GeocoderResults.java b/src/main/java/org/opentripplanner/geocoder/GeocoderResults.java
index 46e19dd8804..dbaa784f725 100644
--- a/src/main/java/org/opentripplanner/geocoder/GeocoderResults.java
+++ b/src/main/java/org/opentripplanner/geocoder/GeocoderResults.java
@@ -3,14 +3,9 @@
import java.util.ArrayList;
import java.util.Collection;
-import javax.xml.bind.annotation.XmlElement;
-import javax.xml.bind.annotation.XmlElementWrapper;
-import javax.xml.bind.annotation.XmlRootElement;
-
import com.fasterxml.jackson.annotation.JsonProperty;
-@XmlRootElement
public class GeocoderResults {
private String error;
@@ -26,7 +21,6 @@ public GeocoderResults(Collection results) {
this.results = results;
}
- @XmlElement(required=false)
public String getError() {
return error;
}
@@ -36,9 +30,6 @@ public void setError(String error) {
this.error = error;
}
-
- @XmlElementWrapper(name="results")
- @XmlElement(name="result")
@JsonProperty(value="results")
public Collection getResults() {
return results;
@@ -55,7 +46,6 @@ public void addResult(GeocoderResult result) {
results.add(result);
}
- @XmlElement(name="count")
public int getCount() {
return results != null ? results.size() : 0;
}
diff --git a/src/main/java/org/opentripplanner/geocoder/GeocoderStubImpl.java b/src/main/java/org/opentripplanner/geocoder/GeocoderStubImpl.java
index 3cb4ea7ca12..3886f5097ec 100644
--- a/src/main/java/org/opentripplanner/geocoder/GeocoderStubImpl.java
+++ b/src/main/java/org/opentripplanner/geocoder/GeocoderStubImpl.java
@@ -2,7 +2,7 @@
import java.util.Arrays;
-import com.vividsolutions.jts.geom.Envelope;
+import org.locationtech.jts.geom.Envelope;
public class GeocoderStubImpl implements Geocoder {
diff --git a/src/main/java/org/opentripplanner/geocoder/GeocoderUSCSV.java b/src/main/java/org/opentripplanner/geocoder/GeocoderUSCSV.java
index 1b682b64750..d37c0433769 100644
--- a/src/main/java/org/opentripplanner/geocoder/GeocoderUSCSV.java
+++ b/src/main/java/org/opentripplanner/geocoder/GeocoderUSCSV.java
@@ -12,7 +12,7 @@
import javax.ws.rs.core.UriBuilder;
-import com.vividsolutions.jts.geom.Envelope;
+import org.locationtech.jts.geom.Envelope;
public class GeocoderUSCSV implements Geocoder {
diff --git a/src/main/java/org/opentripplanner/geocoder/bano/BanoGeocoder.java b/src/main/java/org/opentripplanner/geocoder/bano/BanoGeocoder.java
index 61b66f8a29e..95a0ec3af10 100644
--- a/src/main/java/org/opentripplanner/geocoder/bano/BanoGeocoder.java
+++ b/src/main/java/org/opentripplanner/geocoder/bano/BanoGeocoder.java
@@ -2,7 +2,7 @@
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
-import com.vividsolutions.jts.geom.Envelope;
+import org.locationtech.jts.geom.Envelope;
import org.geojson.Feature;
import org.geojson.FeatureCollection;
import org.geojson.GeoJsonObject;
diff --git a/src/main/java/org/opentripplanner/geocoder/google/GoogleGeocoder.java b/src/main/java/org/opentripplanner/geocoder/google/GoogleGeocoder.java
index ce9a5eeffb4..4b7d35a938e 100644
--- a/src/main/java/org/opentripplanner/geocoder/google/GoogleGeocoder.java
+++ b/src/main/java/org/opentripplanner/geocoder/google/GoogleGeocoder.java
@@ -1,6 +1,6 @@
package org.opentripplanner.geocoder.google;
-import com.vividsolutions.jts.geom.Envelope;
+import org.locationtech.jts.geom.Envelope;
import org.opentripplanner.geocoder.Geocoder;
import org.opentripplanner.geocoder.GeocoderResult;
import org.opentripplanner.geocoder.GeocoderResults;
diff --git a/src/main/java/org/opentripplanner/geocoder/nominatim/NominatimGeocoder.java b/src/main/java/org/opentripplanner/geocoder/nominatim/NominatimGeocoder.java
index 31285e4dcee..c11d34ecedc 100644
--- a/src/main/java/org/opentripplanner/geocoder/nominatim/NominatimGeocoder.java
+++ b/src/main/java/org/opentripplanner/geocoder/nominatim/NominatimGeocoder.java
@@ -1,6 +1,6 @@
package org.opentripplanner.geocoder.nominatim;
-import com.vividsolutions.jts.geom.Envelope;
+import org.locationtech.jts.geom.Envelope;
import org.opentripplanner.geocoder.Geocoder;
import org.opentripplanner.geocoder.GeocoderResult;
import org.opentripplanner.geocoder.GeocoderResults;
diff --git a/src/main/java/org/opentripplanner/geocoder/reverse/MunicoderServer.java b/src/main/java/org/opentripplanner/geocoder/reverse/MunicoderServer.java
index 72aa28d7a73..01332c7a1b3 100644
--- a/src/main/java/org/opentripplanner/geocoder/reverse/MunicoderServer.java
+++ b/src/main/java/org/opentripplanner/geocoder/reverse/MunicoderServer.java
@@ -8,8 +8,8 @@
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
-/*import com.vividsolutions.jts.geom.Coordinate;
-import com.vividsolutions.jts.geom.Envelope;*/
+/*import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.Envelope;*/
@Path("/municoder")
diff --git a/src/main/java/org/opentripplanner/geocoder/reverse/ShapefileBoundaryResolver.java b/src/main/java/org/opentripplanner/geocoder/reverse/ShapefileBoundaryResolver.java
index c14736ef869..d7840a29fab 100644
--- a/src/main/java/org/opentripplanner/geocoder/reverse/ShapefileBoundaryResolver.java
+++ b/src/main/java/org/opentripplanner/geocoder/reverse/ShapefileBoundaryResolver.java
@@ -11,10 +11,10 @@
import org.opengis.feature.Feature;
import org.opengis.feature.simple.SimpleFeature;
-import com.vividsolutions.jts.geom.Coordinate;
-import com.vividsolutions.jts.geom.Geometry;
-import com.vividsolutions.jts.geom.GeometryFactory;
-import com.vividsolutions.jts.geom.Point;
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.Geometry;
+import org.locationtech.jts.geom.GeometryFactory;
+import org.locationtech.jts.geom.Point;
public class ShapefileBoundaryResolver implements BoundaryResolver {
diff --git a/src/main/java/org/opentripplanner/geocoder/yahoo/YahooGeocoder.java b/src/main/java/org/opentripplanner/geocoder/yahoo/YahooGeocoder.java
index bdf9e396765..89f44b689e4 100644
--- a/src/main/java/org/opentripplanner/geocoder/yahoo/YahooGeocoder.java
+++ b/src/main/java/org/opentripplanner/geocoder/yahoo/YahooGeocoder.java
@@ -1,6 +1,6 @@
package org.opentripplanner.geocoder.yahoo;
-import com.vividsolutions.jts.geom.Envelope;
+import org.locationtech.jts.geom.Envelope;
import org.opentripplanner.geocoder.Geocoder;
import org.opentripplanner.geocoder.GeocoderResult;
import org.opentripplanner.geocoder.GeocoderResults;
diff --git a/src/main/java/org/opentripplanner/graph_builder/GraphBuilder.java b/src/main/java/org/opentripplanner/graph_builder/GraphBuilder.java
index 6f1dcd5bc56..d8f338b5f3b 100644
--- a/src/main/java/org/opentripplanner/graph_builder/GraphBuilder.java
+++ b/src/main/java/org/opentripplanner/graph_builder/GraphBuilder.java
@@ -24,7 +24,6 @@
import org.opentripplanner.reflect.ReflectionLibrary;
import org.opentripplanner.routing.core.RoutingRequest;
import org.opentripplanner.routing.graph.Graph;
-import org.opentripplanner.routing.graph.Graph.LoadLevel;
import org.opentripplanner.standalone.CommandLineParameters;
import org.opentripplanner.standalone.GraphBuilderParameters;
import org.opentripplanner.standalone.OTPMain;
@@ -47,7 +46,7 @@
* It is modular: GraphBuilderModules are placed in a list and run in sequence.
*/
public class GraphBuilder implements Runnable {
-
+
private static Logger LOG = LoggerFactory.getLogger(GraphBuilder.class);
public static final String BUILDER_CONFIG_FILENAME = "build-config.json";
@@ -55,13 +54,13 @@ public class GraphBuilder implements Runnable {
private List _graphBuilderModules = new ArrayList();
private final File graphFile;
-
+
private boolean _alwaysRebuild = true;
private List modeList;
-
+
private String baseGraph = null;
-
+
private Graph graph = new Graph();
/** Should the graph be serialized to disk after being created or not? */
@@ -83,11 +82,11 @@ public void setGraphBuilders(List graphLoaders) {
public void setAlwaysRebuild(boolean alwaysRebuild) {
_alwaysRebuild = alwaysRebuild;
}
-
+
public void setBaseGraph(String baseGraph) {
this.baseGraph = baseGraph;
try {
- graph = Graph.load(new File(baseGraph), LoadLevel.FULL);
+ graph = Graph.load(new File(baseGraph));
} catch (Exception e) {
throw new RuntimeException("error loading base graph");
}
@@ -100,7 +99,7 @@ public void addMode(RoutingRequest mo) {
public void setModes(List modeList) {
this.modeList = modeList;
}
-
+
public Graph getGraph() {
return this.graph;
}
@@ -110,7 +109,7 @@ public void run() {
long startTime = System.currentTimeMillis();
if (serializeGraph) {
-
+
if (graphFile == null) {
throw new RuntimeException("graphBuilderTask has no attribute graphFile.");
}
@@ -119,7 +118,7 @@ public void run() {
LOG.info("graph already exists and alwaysRebuild=false => skipping graph build");
return;
}
-
+
try {
if (!graphFile.getParentFile().exists()) {
if (!graphFile.getParentFile().mkdirs()) {
@@ -193,24 +192,24 @@ public static GraphBuilder forDirectory(CommandLineParameters params, File dir)
for (File file : dir.listFiles()) {
switch (InputFileType.forFile(file)) {
- case GTFS:
- LOG.info("Found GTFS file {}", file);
- gtfsFiles.add(file);
- break;
- case OSM:
- LOG.info("Found OSM file {}", file);
- osmFiles.add(file);
- break;
- case DEM:
- if (!builderParams.fetchElevationUS && demFile == null) {
- LOG.info("Found DEM file {}", file);
- demFile = file;
- } else {
- LOG.info("Skipping DEM file {}", file);
- }
- break;
- case OTHER:
- LOG.warn("Skipping unrecognized file '{}'", file);
+ case GTFS:
+ LOG.info("Found GTFS file {}", file);
+ gtfsFiles.add(file);
+ break;
+ case OSM:
+ LOG.info("Found OSM file {}", file);
+ osmFiles.add(file);
+ break;
+ case DEM:
+ if (!builderParams.fetchElevationUS && demFile == null) {
+ LOG.info("Found DEM file {}", file);
+ demFile = file;
+ } else {
+ LOG.info("Skipping DEM file {}", file);
+ }
+ break;
+ case OTHER:
+ LOG.warn("Skipping unrecognized file '{}'", file);
}
}
boolean hasOSM = builderParams.streets && !osmFiles.isEmpty();
@@ -299,7 +298,8 @@ public static GraphBuilder forDirectory(CommandLineParameters params, File dir)
params.cacheDirectory,
builderParams.readCachedElevations,
builderParams.writeCachedElevations,
- builderParams.includeEllipsoidToGeoidDifference
+ builderParams.includeEllipsoidToGeoidDifference,
+ builderParams.elevationUnitMultiplier
)
);
}
diff --git a/src/main/java/org/opentripplanner/graph_builder/GraphStats.java b/src/main/java/org/opentripplanner/graph_builder/GraphStats.java
index 59e70e0dcb5..148ff5bb47e 100644
--- a/src/main/java/org/opentripplanner/graph_builder/GraphStats.java
+++ b/src/main/java/org/opentripplanner/graph_builder/GraphStats.java
@@ -29,10 +29,10 @@
import com.csvreader.CsvWriter;
import com.google.common.collect.Multiset;
import com.google.common.collect.TreeMultiset;
-import com.vividsolutions.jts.geom.Coordinate;
-import com.vividsolutions.jts.geom.LineString;
-import com.vividsolutions.jts.linearref.LinearLocation;
-import com.vividsolutions.jts.linearref.LocationIndexedLine;
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.LineString;
+import org.locationtech.jts.linearref.LinearLocation;
+import org.locationtech.jts.linearref.LocationIndexedLine;
public class GraphStats {
@@ -95,7 +95,7 @@ private void run() {
/* open input graph (same for all commands) */
File graphFile = new File(graphPath);
try {
- graph = Graph.load(graphFile, Graph.LoadLevel.FULL);
+ graph = Graph.load(graphFile);
} catch (Exception e) {
LOG.error("Exception while loading graph from " + graphFile);
return;
diff --git a/src/main/java/org/opentripplanner/graph_builder/linking/SampleStopLinker.java b/src/main/java/org/opentripplanner/graph_builder/linking/SampleStopLinker.java
index 20db1478352..d9fef86d8c5 100644
--- a/src/main/java/org/opentripplanner/graph_builder/linking/SampleStopLinker.java
+++ b/src/main/java/org/opentripplanner/graph_builder/linking/SampleStopLinker.java
@@ -3,9 +3,9 @@
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Multimap;
-import com.vividsolutions.jts.geom.Coordinate;
-import com.vividsolutions.jts.geom.GeometryFactory;
-import com.vividsolutions.jts.geom.LineString;
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.GeometryFactory;
+import org.locationtech.jts.geom.LineString;
import org.opentripplanner.analyst.core.Sample;
import org.opentripplanner.analyst.request.SampleFactory;
import org.opentripplanner.common.geometry.GeometryUtils;
diff --git a/src/main/java/org/opentripplanner/graph_builder/linking/StreetSplitter.java b/src/main/java/org/opentripplanner/graph_builder/linking/StreetSplitter.java
index e5d10b21db7..ba6eac68692 100644
--- a/src/main/java/org/opentripplanner/graph_builder/linking/StreetSplitter.java
+++ b/src/main/java/org/opentripplanner/graph_builder/linking/StreetSplitter.java
@@ -1,13 +1,13 @@
package org.opentripplanner.graph_builder.linking;
import com.google.common.collect.Iterables;
-import com.vividsolutions.jts.geom.Coordinate;
-import com.vividsolutions.jts.geom.Envelope;
-import com.vividsolutions.jts.geom.GeometryFactory;
-import com.vividsolutions.jts.geom.LineString;
-import com.vividsolutions.jts.index.SpatialIndex;
-import com.vividsolutions.jts.linearref.LinearLocation;
-import com.vividsolutions.jts.linearref.LocationIndexedLine;
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.Envelope;
+import org.locationtech.jts.geom.GeometryFactory;
+import org.locationtech.jts.geom.LineString;
+import org.locationtech.jts.index.SpatialIndex;
+import org.locationtech.jts.linearref.LinearLocation;
+import org.locationtech.jts.linearref.LocationIndexedLine;
import gnu.trove.map.TIntDoubleMap;
import gnu.trove.map.hash.TIntDoubleHashMap;
import jersey.repackaged.com.google.common.collect.Lists;
@@ -179,7 +179,9 @@ public StreetSplitter(Graph graph) {
*/
public void linkAllStationsToGraph() {
for (Vertex v : graph.getVertices()) {
- if (v instanceof TransitStop || v instanceof BikeRentalStationVertex || v instanceof BikeParkVertex)
+ if (v instanceof TransitStop || v instanceof BikeRentalStationVertex || v instanceof BikeParkVertex) {
+ boolean alreadyLinked = v.getOutgoing().stream().anyMatch(e -> e instanceof StreetTransitLink);
+ if (alreadyLinked) continue;
if (!linkToClosestWalkableEdge(v, DESTRUCTIVE_SPLIT, false)) {
if (v instanceof TransitStop)
LOG.warn(graph.addBuilderAnnotation(new StopUnlinked((TransitStop) v)));
@@ -188,6 +190,7 @@ else if (v instanceof BikeRentalStationVertex)
else if (v instanceof BikeParkVertex)
LOG.warn(graph.addBuilderAnnotation(new BikeParkUnlinked((BikeParkVertex) v)));
};
+ }
}
}
diff --git a/src/main/java/org/opentripplanner/graph_builder/module/CheckGeometryModule.java b/src/main/java/org/opentripplanner/graph_builder/module/CheckGeometryModule.java
index adccb78eb72..51a6ff2ee51 100644
--- a/src/main/java/org/opentripplanner/graph_builder/module/CheckGeometryModule.java
+++ b/src/main/java/org/opentripplanner/graph_builder/module/CheckGeometryModule.java
@@ -16,8 +16,8 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import com.vividsolutions.jts.geom.Coordinate;
-import com.vividsolutions.jts.geom.Geometry;
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.Geometry;
/**
* Check the geometry of every edge in the graph for any bogus geometry --
diff --git a/src/main/java/org/opentripplanner/graph_builder/module/GraphStatisticsModule.java b/src/main/java/org/opentripplanner/graph_builder/module/GraphStatisticsModule.java
index 6d5a6539f43..798e489ecbd 100644
--- a/src/main/java/org/opentripplanner/graph_builder/module/GraphStatisticsModule.java
+++ b/src/main/java/org/opentripplanner/graph_builder/module/GraphStatisticsModule.java
@@ -16,7 +16,7 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import com.vividsolutions.jts.geom.LineString;
+import org.locationtech.jts.geom.LineString;
/**
* Print statistics on geometry and edge/vertices data for a graph (number of geometry, average
diff --git a/src/main/java/org/opentripplanner/graph_builder/module/NearbyStopFinder.java b/src/main/java/org/opentripplanner/graph_builder/module/NearbyStopFinder.java
index 492453e1c0c..4bcc6517f3c 100644
--- a/src/main/java/org/opentripplanner/graph_builder/module/NearbyStopFinder.java
+++ b/src/main/java/org/opentripplanner/graph_builder/module/NearbyStopFinder.java
@@ -2,9 +2,9 @@
import com.beust.jcommander.internal.Lists;
import com.beust.jcommander.internal.Sets;
-import com.vividsolutions.jts.geom.Coordinate;
-import com.vividsolutions.jts.geom.GeometryFactory;
-import com.vividsolutions.jts.geom.LineString;
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.GeometryFactory;
+import org.locationtech.jts.geom.LineString;
import org.opentripplanner.api.resource.CoordinateArrayListSequence;
import org.opentripplanner.api.resource.SimpleIsochrone;
diff --git a/src/main/java/org/opentripplanner/graph_builder/module/TransitStopsRegionsSourceImpl.java b/src/main/java/org/opentripplanner/graph_builder/module/TransitStopsRegionsSourceImpl.java
index a3ecd7e0ee6..a971905a8a5 100644
--- a/src/main/java/org/opentripplanner/graph_builder/module/TransitStopsRegionsSourceImpl.java
+++ b/src/main/java/org/opentripplanner/graph_builder/module/TransitStopsRegionsSourceImpl.java
@@ -10,8 +10,8 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import com.vividsolutions.jts.geom.Coordinate;
-import com.vividsolutions.jts.geom.Envelope;
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.Envelope;
/**
* A rectangular region bounding a set of transit stops
diff --git a/src/main/java/org/opentripplanner/graph_builder/module/TransitToTaggedStopsModule.java b/src/main/java/org/opentripplanner/graph_builder/module/TransitToTaggedStopsModule.java
index 3f12cf3ea25..2c4cc7bb1a6 100644
--- a/src/main/java/org/opentripplanner/graph_builder/module/TransitToTaggedStopsModule.java
+++ b/src/main/java/org/opentripplanner/graph_builder/module/TransitToTaggedStopsModule.java
@@ -1,7 +1,7 @@
package org.opentripplanner.graph_builder.module;
import com.google.common.collect.Iterables;
-import com.vividsolutions.jts.geom.Envelope;
+import org.locationtech.jts.geom.Envelope;
import org.opentripplanner.common.geometry.SphericalDistanceLibrary;
import org.opentripplanner.graph_builder.services.GraphBuilderModule;
import org.opentripplanner.routing.edgetype.StreetTransitLink;
diff --git a/src/main/java/org/opentripplanner/graph_builder/module/map/EndMatchState.java b/src/main/java/org/opentripplanner/graph_builder/module/map/EndMatchState.java
new file mode 100644
index 00000000000..aba731a4d79
--- /dev/null
+++ b/src/main/java/org/opentripplanner/graph_builder/module/map/EndMatchState.java
@@ -0,0 +1,22 @@
+package org.opentripplanner.graph_builder.module.map;
+
+import java.util.List;
+
+/**
+ * The end of a route's geometry, meaning that the search can quit
+ * @author novalis
+ *
+ */
+public class EndMatchState extends MatchState {
+
+ public EndMatchState(MatchState parent, double error, double distance) {
+ super(parent, null, distance);
+ this.currentError = error;
+ }
+
+ @Override
+ public List getNextStates() {
+ return null;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/org/opentripplanner/graph_builder/module/map/LinearIterator.java b/src/main/java/org/opentripplanner/graph_builder/module/map/LinearIterator.java
new file mode 100644
index 00000000000..1abec894484
--- /dev/null
+++ b/src/main/java/org/opentripplanner/graph_builder/module/map/LinearIterator.java
@@ -0,0 +1,220 @@
+package org.opentripplanner.graph_builder.module.map;
+
+import java.util.Iterator;
+
+import org.locationtech.jts.geom.*;
+import org.locationtech.jts.linearref.LinearLocation;
+
+/**
+ * I copied this class from JTS but made a few changes.
+ *
+ * The JTS version of this class has several design decisions that don't work for me. In particular,
+ * hasNext() in the original should be "isValid", and if we start mid-segment, we should continue at
+ * the end of this segment rather than the end of the next segment.
+ */
+public class LinearIterator implements Iterable {
+
+ private Geometry linearGeom;
+
+ private final int numLines;
+
+ /**
+ * Invariant: currentLine <> null if the iterator is pointing at a valid coordinate
+ *
+ * @throws IllegalArgumentException if linearGeom is not lineal
+ */
+ private LineString currentLine;
+
+ private int componentIndex = 0;
+
+ private int vertexIndex = 0;
+
+ private double segmentFraction;
+
+ /**
+ * Creates an iterator initialized to the start of a linear {@link Geometry}
+ *
+ * @param linear the linear geometry to iterate over
+ * @throws IllegalArgumentException if linearGeom is not lineal
+ */
+ public LinearIterator(Geometry linear) {
+ this(linear, 0, 0);
+ }
+
+ /**
+ * Creates an iterator starting at a {@link LinearLocation} on a linear {@link Geometry}
+ *
+ * @param linear the linear geometry to iterate over
+ * @param start the location to start at
+ * @throws IllegalArgumentException if linearGeom is not lineal
+ */
+ public LinearIterator(Geometry linear, LinearLocation start) {
+ this(linear, start.getComponentIndex(), start.getSegmentIndex());
+ this.segmentFraction = start.getSegmentFraction();
+ }
+
+ /**
+ * Creates an iterator starting at a specified component and vertex in a linear {@link Geometry}
+ *
+ * @param linearGeom the linear geometry to iterate over
+ * @param componentIndex the component to start at
+ * @param vertexIndex the vertex to start at
+ * @throws IllegalArgumentException if linearGeom is not lineal
+ */
+ public LinearIterator(Geometry linearGeom, int componentIndex, int vertexIndex) {
+ if (!(linearGeom instanceof Lineal))
+ throw new IllegalArgumentException("Lineal geometry is required");
+ this.linearGeom = linearGeom;
+ numLines = linearGeom.getNumGeometries();
+ this.componentIndex = componentIndex;
+ this.vertexIndex = vertexIndex;
+ loadCurrentLine();
+ }
+
+ private void loadCurrentLine() {
+ if (componentIndex >= numLines) {
+ currentLine = null;
+ return;
+ }
+ currentLine = (LineString) linearGeom.getGeometryN(componentIndex);
+ }
+
+ /**
+ * Tests whether there are any vertices left to iterator over.
+ *
+ * @return true if there are more vertices to scan
+ */
+ public boolean hasNext() {
+ if (componentIndex >= numLines)
+ return false;
+ if (componentIndex == numLines - 1 && vertexIndex >= currentLine.getNumPoints() - 1)
+ return false;
+ return true;
+ }
+
+ public boolean isValidIndex() {
+ if (componentIndex >= numLines)
+ return false;
+ if (componentIndex == numLines - 1 && vertexIndex >= currentLine.getNumPoints())
+ return false;
+ return true;
+ }
+
+ /**
+ * Moves the iterator ahead to the next vertex and (possibly) linear component.
+ */
+ public void next() {
+ if (!hasNext())
+ return;
+ segmentFraction = 0.0;
+ vertexIndex++;
+ if (vertexIndex >= currentLine.getNumPoints()) {
+ componentIndex++;
+ if (componentIndex < linearGeom.getNumGeometries() - 1) {
+ loadCurrentLine();
+ vertexIndex = 0;
+ }
+ }
+ }
+
+ /**
+ * Checks whether the iterator cursor is pointing to the endpoint of a linestring.
+ *
+ * @return true if the iterator is at an endpoint
+ */
+ public boolean isEndOfLine() {
+ if (componentIndex >= numLines)
+ return false;
+ // LineString currentLine = (LineString) linear.getGeometryN(componentIndex);
+ if (vertexIndex < currentLine.getNumPoints() - 1)
+ return false;
+ return true;
+ }
+
+ /**
+ * The component index of the vertex the iterator is currently at.
+ *
+ * @return the current component index
+ */
+ public int getComponentIndex() {
+ return componentIndex;
+ }
+
+ /**
+ * The vertex index of the vertex the iterator is currently at.
+ *
+ * @return the current vertex index
+ */
+ public int getVertexIndex() {
+ return vertexIndex;
+ }
+
+ /**
+ * Gets the {@link LineString} component the iterator is current at.
+ *
+ * @return a linestring
+ */
+ public LineString getLine() {
+ return currentLine;
+ }
+
+ /**
+ * Gets the first {@link Coordinate} of the current segment. (the coordinate of the current
+ * vertex).
+ *
+ * @return a {@link Coordinate}
+ */
+ public Coordinate getSegmentStart() {
+ return currentLine.getCoordinateN(vertexIndex);
+ }
+
+ /**
+ * Gets the second {@link Coordinate} of the current segment. (the coordinate of the next
+ * vertex). If the iterator is at the end of a line, null is returned.
+ *
+ * @return a {@link Coordinate} or null
+ */
+ public Coordinate getSegmentEnd() {
+ if (vertexIndex < getLine().getNumPoints() - 1)
+ return currentLine.getCoordinateN(vertexIndex + 1);
+ return null;
+ }
+
+ public LinearLocation getLocation() {
+ return new LinearLocation(componentIndex, vertexIndex, segmentFraction);
+ }
+
+ class LinearIteratorIterator implements Iterator {
+
+ @Override
+ public boolean hasNext() {
+ return LinearIterator.this.hasNext();
+ }
+
+ @Override
+ public LinearLocation next() {
+ LinearLocation result = getLocation();
+ LinearIterator.this.next();
+ return result;
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+
+ }
+ @Override
+ public Iterator iterator() {
+ return new LinearIteratorIterator();
+ }
+
+ public static LinearLocation getEndLocation(Geometry linear) {
+ //the version in LinearLocation is broken
+
+ int lastComponentIndex = linear.getNumGeometries() - 1;
+ LineString lastLine = (LineString) linear.getGeometryN(lastComponentIndex);
+ int lastSegmentIndex = lastLine.getNumPoints() - 1;
+ return new LinearLocation(lastComponentIndex, lastSegmentIndex, 0.0);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/opentripplanner/graph_builder/module/map/MatchState.java b/src/main/java/org/opentripplanner/graph_builder/module/map/MatchState.java
new file mode 100644
index 00000000000..b123919ca8a
--- /dev/null
+++ b/src/main/java/org/opentripplanner/graph_builder/module/map/MatchState.java
@@ -0,0 +1,115 @@
+package org.opentripplanner.graph_builder.module.map;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.opentripplanner.common.geometry.SphericalDistanceLibrary;
+import org.opentripplanner.routing.core.State;
+import org.opentripplanner.routing.core.TraverseMode;
+import org.opentripplanner.routing.core.RoutingRequest;
+import org.opentripplanner.routing.edgetype.StreetEdge;
+import org.opentripplanner.routing.graph.Edge;
+import org.opentripplanner.routing.graph.Vertex;
+
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.Geometry;
+import org.locationtech.jts.linearref.LinearLocation;
+
+public abstract class MatchState {
+ private static final RoutingRequest traverseOptions = new RoutingRequest(TraverseMode.CAR);
+
+ protected static final double NEW_SEGMENT_PENALTY = 0.1;
+
+ protected static final double NO_TRAVERSE_PENALTY = 20;
+
+ public double currentError;
+
+ public double accumulatedError;
+
+ public MatchState parent;
+
+ protected Edge edge;
+
+ private double distanceAlongRoute = 0;
+
+ public MatchState(MatchState parent, Edge edge, double distanceAlongRoute) {
+ this.distanceAlongRoute = distanceAlongRoute;
+ this.parent = parent;
+ this.edge = edge;
+ if (parent != null) {
+ this.accumulatedError = parent.accumulatedError + parent.currentError;
+ this.distanceAlongRoute += parent.distanceAlongRoute;
+ }
+ }
+
+ public abstract List getNextStates();
+
+ public Edge getEdge() {
+ return edge;
+ }
+
+ public double getTotalError() {
+ return accumulatedError + currentError;
+ }
+
+ protected boolean carsCanTraverse(Edge edge) {
+ // should be done with a method on edge (canTraverse already exists on turnEdge)
+ State s0 = new State(edge.getFromVertex(), traverseOptions);
+ State s1 = edge.traverse(s0);
+ return s1 != null;
+ }
+
+ protected List getOutgoingMatchableEdges(Vertex vertex) {
+ List edges = new ArrayList();
+ for (Edge e : vertex.getOutgoing()) {
+ if (!(e instanceof StreetEdge))
+ continue;
+ if (e.getGeometry() == null)
+ continue;
+ edges.add(e);
+ }
+ return edges;
+ }
+
+
+ public double getDistanceAlongRoute() {
+ return distanceAlongRoute;
+ }
+
+ /* computes the distance, in meters, along a geometry */
+ protected static double distanceAlongGeometry(Geometry geometry, LinearLocation startIndex,
+ LinearLocation endIndex) {
+
+ if (endIndex == null) {
+ endIndex = LinearLocation.getEndLocation(geometry);
+ }
+ double total = 0;
+ LinearIterator it = new LinearIterator(geometry, startIndex);
+ LinearLocation index = startIndex;
+ Coordinate previousCoordinate = startIndex.getCoordinate(geometry);
+
+ it.next();
+ index = it.getLocation();
+ while (index.compareTo(endIndex) < 0) {
+ Coordinate thisCoordinate = index.getCoordinate(geometry);
+ double distance = SphericalDistanceLibrary.fastDistance(previousCoordinate, thisCoordinate);
+ total += distance;
+ previousCoordinate = thisCoordinate;
+ if (!it.hasNext())
+ break;
+ it.next();
+ index = it.getLocation();
+ }
+ //now, last bit of last segment
+ Coordinate finalCoordinate = endIndex.getCoordinate(geometry);
+ total += SphericalDistanceLibrary.distance(previousCoordinate, finalCoordinate);
+
+ return total;
+ }
+
+
+ protected static double distance(Coordinate from, Coordinate to) {
+ return SphericalDistanceLibrary.fastDistance(from, to);
+ }
+
+}
diff --git a/src/main/java/org/opentripplanner/graph_builder/module/map/MidblockMatchState.java b/src/main/java/org/opentripplanner/graph_builder/module/map/MidblockMatchState.java
new file mode 100644
index 00000000000..e63816100e0
--- /dev/null
+++ b/src/main/java/org/opentripplanner/graph_builder/module/map/MidblockMatchState.java
@@ -0,0 +1,234 @@
+package org.opentripplanner.graph_builder.module.map;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import org.opentripplanner.routing.graph.Edge;
+import org.opentripplanner.routing.graph.Vertex;
+
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.Geometry;
+import org.locationtech.jts.linearref.LinearLocation;
+import org.locationtech.jts.linearref.LocationIndexedLine;
+import org.locationtech.jts.util.AssertionFailedException;
+
+public class MidblockMatchState extends MatchState {
+
+ private static final double MAX_ERROR = 1000;
+
+ private LinearLocation edgeIndex;
+
+ public LinearLocation routeIndex;
+
+ Geometry routeGeometry;
+
+ private Geometry edgeGeometry;
+
+ private LocationIndexedLine indexedEdge;
+
+ public MidblockMatchState(MatchState parent, Geometry routeGeometry, Edge edge,
+ LinearLocation routeIndex, LinearLocation edgeIndex, double error,
+ double distanceAlongRoute) {
+ super(parent, edge, distanceAlongRoute);
+
+ this.routeGeometry = routeGeometry;
+ this.routeIndex = routeIndex;
+ this.edgeIndex = edgeIndex;
+
+ edgeGeometry = edge.getGeometry();
+ indexedEdge = new LocationIndexedLine(edgeGeometry);
+ currentError = error;
+ }
+
+ @Override
+ public List getNextStates() {
+ ArrayList nextStates = new ArrayList();
+ if (routeIndex.getSegmentIndex() == routeGeometry.getNumPoints() - 1) {
+ // this has either hit the end, or gone off the end. It's not real clear which.
+ // for now, let's assume it means that the ending is somewhere along this edge,
+ // so we return an end state
+ Coordinate pt = routeIndex.getCoordinate(routeGeometry);
+ double error = distance(pt, edgeIndex.getCoordinate(edgeGeometry));
+ nextStates.add(new EndMatchState(this, error, 0));
+ return nextStates;
+ }
+
+ LinearIterator it = new LinearIterator(routeGeometry, routeIndex);
+ if (it.hasNext()) {
+ it.next();
+ LinearLocation routeSuccessor = it.getLocation();
+
+ // now we want to see where this new point is in terms of the edge's geometry
+ Coordinate newRouteCoord = routeSuccessor.getCoordinate(routeGeometry);
+ LinearLocation newEdgeIndex = indexedEdge.project(newRouteCoord);
+
+ Coordinate edgeCoord = newEdgeIndex.getCoordinate(edgeGeometry);
+ if (newEdgeIndex.compareTo(edgeIndex) <= 0) {
+ // we must make forward progress along the edge... or go to the next edge
+ /* this should not require the try/catch, but there is a bug in JTS */
+ try {
+ LinearLocation projected2 = indexedEdge.indexOfAfter(edgeCoord, edgeIndex);
+ //another bug in JTS
+ if (Double.isNaN(projected2.getSegmentFraction())) {
+ // we are probably moving backwards
+ return Collections.emptyList();
+ } else {
+ newEdgeIndex = projected2;
+ if (newEdgeIndex.equals(edgeIndex)) {
+ return Collections.emptyList();
+ }
+ }
+ edgeCoord = newEdgeIndex.getCoordinate(edgeGeometry);
+ } catch (AssertionFailedException e) {
+ // we are not making progress, so just return an empty list
+ return Collections.emptyList();
+ }
+ }
+
+ if (newEdgeIndex.getSegmentIndex() == edgeGeometry.getNumPoints() - 1) {
+
+ // we might choose to continue from the end of the edge and a point mid-way
+ // along this route segment
+
+ // find nearest point that makes progress along the route
+ Vertex toVertex = edge.getToVertex();
+ Coordinate endCoord = toVertex.getCoordinate();
+ LocationIndexedLine indexedRoute = new LocationIndexedLine(routeGeometry);
+
+ // FIXME: it would be better to do this project/indexOfAfter in one step
+ // as the two-step version could snap to a bad place and be unable to escape.
+
+ LinearLocation routeProjectedEndIndex = indexedRoute.project(endCoord);
+ Coordinate routeProjectedEndCoord = routeProjectedEndIndex
+ .getCoordinate(routeGeometry);
+
+ if (routeProjectedEndIndex.compareTo(routeIndex) <= 0) {
+ try {
+ routeProjectedEndIndex = indexedRoute.indexOfAfter(routeProjectedEndCoord,
+ routeIndex);
+ if (Double.isNaN(routeProjectedEndIndex.getSegmentFraction())) {
+ // can't go forward
+ routeProjectedEndIndex = routeIndex; // this is bad, but not terrible
+ // since we are advancing along the edge
+ }
+ } catch (AssertionFailedException e) {
+ routeProjectedEndIndex = routeIndex;
+ }
+ routeProjectedEndCoord = routeProjectedEndIndex.getCoordinate(routeGeometry);
+ }
+
+ double positionError = distance(routeProjectedEndCoord, endCoord);
+ double travelAlongRoute = distanceAlongGeometry(routeGeometry, routeIndex,
+ routeProjectedEndIndex);
+ double travelAlongEdge = distanceAlongGeometry(edgeGeometry, edgeIndex,
+ newEdgeIndex);
+ double travelError = Math.abs(travelAlongEdge - travelAlongRoute);
+
+ double error = positionError + travelError;
+
+ if (error > MAX_ERROR) {
+ // we're not going to bother with states which are
+ // totally wrong
+ return nextStates;
+ }
+
+ for (Edge e : getOutgoingMatchableEdges(toVertex)) {
+ double cost = error + NEW_SEGMENT_PENALTY;
+ if (!carsCanTraverse(e)) {
+ cost += NO_TRAVERSE_PENALTY;
+ }
+ MatchState nextState = new MidblockMatchState(this, routeGeometry, e,
+ routeProjectedEndIndex, new LinearLocation(), cost, travelAlongRoute);
+ nextStates.add(nextState);
+ }
+
+ } else {
+
+ double travelAlongEdge = distanceAlongGeometry(edgeGeometry, edgeIndex,
+ newEdgeIndex);
+ double travelAlongRoute = distanceAlongGeometry(routeGeometry, routeIndex,
+ routeSuccessor);
+ double travelError = Math.abs(travelAlongRoute - travelAlongEdge);
+
+ double positionError = distance(edgeCoord, newRouteCoord);
+
+ double error = travelError + positionError;
+
+ MatchState nextState = new MidblockMatchState(this, routeGeometry, edge,
+ routeSuccessor, newEdgeIndex, error, travelAlongRoute);
+ nextStates.add(nextState);
+
+ // it's also possible that, although we have not yet reached the end of this edge,
+ // we are going to turn, because the route turns earlier than the edge. In that
+ // case, we jump to the corner, and our error is the distance from the route point
+ // and the corner
+
+ Vertex toVertex = edge.getToVertex();
+ double travelAlongOldEdge = distanceAlongGeometry(edgeGeometry, edgeIndex, null);
+
+ for (Edge e : getOutgoingMatchableEdges(toVertex)) {
+ Geometry newEdgeGeometry = e.getGeometry();
+ LocationIndexedLine newIndexedEdge = new LocationIndexedLine(newEdgeGeometry);
+ newEdgeIndex = newIndexedEdge.project(newRouteCoord);
+ Coordinate newEdgeCoord = newEdgeIndex.getCoordinate(newEdgeGeometry);
+ positionError = distance(newEdgeCoord, newRouteCoord);
+ travelAlongEdge = travelAlongOldEdge + distanceAlongGeometry(newEdgeGeometry, new LinearLocation(), newEdgeIndex);
+ travelError = Math.abs(travelAlongRoute - travelAlongEdge);
+
+ error = travelError + positionError;
+
+ if (error > MAX_ERROR) {
+ // we're not going to bother with states which are
+ // totally wrong
+ return nextStates;
+ }
+
+ double cost = error + NEW_SEGMENT_PENALTY;
+ if (!carsCanTraverse(e)) {
+ cost += NO_TRAVERSE_PENALTY;
+ }
+
+ nextState = new MidblockMatchState(this, routeGeometry, e, routeSuccessor,
+ new LinearLocation(), cost, travelAlongRoute);
+ nextStates.add(nextState);
+ }
+
+ }
+ return nextStates;
+
+ } else {
+ Coordinate routeCoord = routeIndex.getCoordinate(routeGeometry);
+ LinearLocation projected = indexedEdge.project(routeCoord);
+ double locationError = distance(projected.getCoordinate(edgeGeometry), routeCoord);
+
+ MatchState end = new EndMatchState(this, locationError, 0);
+ return Arrays.asList(end);
+ }
+
+ }
+
+ public String toString() {
+ return "MidblockMatchState(" + edge + ", " + edgeIndex.getSegmentIndex() + ", "
+ + edgeIndex.getSegmentFraction() + ") - " + currentError;
+ }
+
+ public int hashCode() {
+ return (edge.hashCode() * 1337 + hashCode(edgeIndex)) * 1337 + hashCode(routeIndex);
+ }
+
+ private int hashCode(LinearLocation location) {
+ return location.getComponentIndex() * 1000000 + location.getSegmentIndex() * 37
+ + new Double(location.getSegmentFraction()).hashCode();
+ }
+
+ public boolean equals(Object o) {
+ if (!(o instanceof MidblockMatchState)) {
+ return false;
+ }
+ MidblockMatchState other = (MidblockMatchState) o;
+ return other.edge == edge && other.edgeIndex.compareTo(edgeIndex) == 0
+ && other.routeIndex.compareTo(routeIndex) == 0;
+ }
+}
diff --git a/src/main/java/org/opentripplanner/graph_builder/module/map/StreetMatcher.java b/src/main/java/org/opentripplanner/graph_builder/module/map/StreetMatcher.java
new file mode 100644
index 00000000000..07fcf2a5bc9
--- /dev/null
+++ b/src/main/java/org/opentripplanner/graph_builder/module/map/StreetMatcher.java
@@ -0,0 +1,160 @@
+package org.opentripplanner.graph_builder.module.map;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+
+import org.opentripplanner.routing.edgetype.StreetEdge;
+import org.opentripplanner.routing.graph.Edge;
+import org.opentripplanner.routing.graph.Graph;
+import org.opentripplanner.routing.graph.Vertex;
+import org.opentripplanner.common.pqueue.BinHeap;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.Envelope;
+import org.locationtech.jts.geom.Geometry;
+import org.locationtech.jts.index.strtree.STRtree;
+import org.locationtech.jts.linearref.LinearLocation;
+import org.locationtech.jts.linearref.LocationIndexedLine;
+import org.locationtech.jts.simplify.DouglasPeuckerSimplifier;
+
+/**
+ * This Performs most of the work for the MapBuilder graph builder module.
+ * It determines which sequence of graph edges a GTFS shape probably corresponds to.
+ * Note that GTFS shapes are not in any way constrained to OSM edges or even roads.
+ */
+public class StreetMatcher {
+ private static final Logger log = LoggerFactory.getLogger(StreetMatcher.class);
+ private static final double DISTANCE_THRESHOLD = 0.0002;
+
+ Graph graph;
+
+ private STRtree index;
+
+ STRtree createIndex() {
+ STRtree edgeIndex = new STRtree();
+ for (Vertex v : graph.getVertices()) {
+ for (Edge e : v.getOutgoing()) {
+ if (e instanceof StreetEdge) {
+ Envelope envelope;
+ Geometry geometry = e.getGeometry();
+ envelope = geometry.getEnvelopeInternal();
+ edgeIndex.insert(envelope, e);
+ }
+ }
+ }
+ log.debug("Created index");
+ return edgeIndex;
+ }
+
+ public StreetMatcher(Graph graph) {
+ this.graph = graph;
+ index = createIndex();
+ index.build();
+ }
+
+ @SuppressWarnings("unchecked")
+ public List match(Geometry routeGeometry) {
+
+ routeGeometry = removeDuplicatePoints(routeGeometry);
+
+ if (routeGeometry == null)
+ return null;
+
+ routeGeometry = DouglasPeuckerSimplifier.simplify(routeGeometry, 0.00001);
+
+ // initial state: start midway along a block.
+ LocationIndexedLine indexedLine = new LocationIndexedLine(routeGeometry);
+
+ LinearLocation startIndex = indexedLine.getStartIndex();
+
+ Coordinate routeStartCoordinate = startIndex.getCoordinate(routeGeometry);
+ Envelope envelope = new Envelope(routeStartCoordinate);
+ double distanceThreshold = DISTANCE_THRESHOLD;
+ envelope.expandBy(distanceThreshold);
+
+ BinHeap states = new BinHeap();
+ List nearbyEdges = index.query(envelope);
+ while (nearbyEdges.isEmpty()) {
+ envelope.expandBy(distanceThreshold);
+ distanceThreshold *= 2;
+ nearbyEdges = index.query(envelope);
+ }
+
+ // compute initial states
+ for (Edge initialEdge : nearbyEdges) {
+ Geometry edgeGeometry = initialEdge.getGeometry();
+
+ LocationIndexedLine indexedEdge = new LocationIndexedLine(edgeGeometry);
+ LinearLocation initialLocation = indexedEdge.project(routeStartCoordinate);
+
+ double error = MatchState.distance(initialLocation.getCoordinate(edgeGeometry), routeStartCoordinate);
+ MidblockMatchState state = new MidblockMatchState(null, routeGeometry, initialEdge, startIndex, initialLocation, error, 0.01);
+ states.insert(state, 0); //make sure all initial states are visited by inserting them at 0
+ }
+
+ // search for best-matching path
+ int seen_count = 0, total = 0;
+ HashSet seen = new HashSet();
+ while (!states.empty()) {
+ double k = states.peek_min_key();
+ MatchState state = states.extract_min();
+ if (++total % 50000 == 0) {
+ log.debug("seen / total: " + seen_count + " / " + total);
+ }
+ if (seen.contains(state)) {
+ ++seen_count;
+ continue;
+ } else {
+ if (k != 0) {
+ //but do not mark states as closed if we start at them
+ seen.add(state);
+ }
+ }
+ if (state instanceof EndMatchState) {
+ return toEdgeList(state);
+ }
+ for (MatchState next : state.getNextStates()) {
+ if (seen.contains(next)) {
+ continue;
+ }
+ states.insert(next, next.getTotalError() - next.getDistanceAlongRoute());
+ }
+ }
+ return null;
+ }
+
+ private Geometry removeDuplicatePoints(Geometry routeGeometry) {
+ List coords = new ArrayList();
+ Coordinate last = null;
+ for (Coordinate c : routeGeometry.getCoordinates()) {
+ if (!c.equals(last)) {
+ last = c;
+ coords.add(c);
+ }
+ }
+ if (coords.size() < 2) {
+ return null;
+ }
+ Coordinate[] coordArray = new Coordinate[coords.size()];
+ return routeGeometry.getFactory().createLineString(coords.toArray(coordArray));
+ }
+
+ private List toEdgeList(MatchState next) {
+ ArrayList edges = new ArrayList();
+ Edge lastEdge = null;
+ while (next != null) {
+ Edge edge = next.getEdge();
+ if (edge != lastEdge) {
+ edges.add(edge);
+ lastEdge = edge;
+ }
+ next = next.parent;
+ }
+ Collections.reverse(edges);
+ return edges;
+ }
+}
diff --git a/src/main/java/org/opentripplanner/graph_builder/module/ned/DegreeGridNEDTileSource.java b/src/main/java/org/opentripplanner/graph_builder/module/ned/DegreeGridNEDTileSource.java
index 678d11c98a3..e9104897657 100644
--- a/src/main/java/org/opentripplanner/graph_builder/module/ned/DegreeGridNEDTileSource.java
+++ b/src/main/java/org/opentripplanner/graph_builder/module/ned/DegreeGridNEDTileSource.java
@@ -1,6 +1,6 @@
package org.opentripplanner.graph_builder.module.ned;
-import com.vividsolutions.jts.geom.Coordinate;
+import org.locationtech.jts.geom.Coordinate;
import org.jets3t.service.S3Service;
import org.jets3t.service.S3ServiceException;
import org.jets3t.service.ServiceException;
diff --git a/src/main/java/org/opentripplanner/graph_builder/module/ned/ElevationModule.java b/src/main/java/org/opentripplanner/graph_builder/module/ned/ElevationModule.java
index bccbdaf97b2..2a30dfe51b3 100644
--- a/src/main/java/org/opentripplanner/graph_builder/module/ned/ElevationModule.java
+++ b/src/main/java/org/opentripplanner/graph_builder/module/ned/ElevationModule.java
@@ -1,7 +1,7 @@
package org.opentripplanner.graph_builder.module.ned;
-import com.vividsolutions.jts.geom.Coordinate;
-import com.vividsolutions.jts.geom.Geometry;
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.Geometry;
import org.geotools.geometry.DirectPosition2D;
import org.opengis.coverage.Coverage;
import org.opengis.coverage.PointOutsideCoverageException;
@@ -67,6 +67,12 @@ public class ElevationModule implements GraphBuilderModule {
private final File cachedElevationsFile;
/* Whether or not to include geoid difference values in individual elevation calculations */
private final boolean includeEllipsoidToGeoidDifference;
+ /**
+ * Unit conversion multiplier for elevation values. No conversion needed if the elevation values
+ * are defined in meters in the source data. If, for example, decimetres are used in the source data,
+ * this should be set to 0.1 in build-config.json.
+ */
+ private double elevationUnitMultiplier = 1;
private HashMap cachedElevations;
@@ -104,13 +110,15 @@ public ElevationModule(
File cacheDirectory,
boolean readCachedElevations,
boolean writeCachedElevations,
- boolean includeEllipsoidToGeoidDifference
+ boolean includeEllipsoidToGeoidDifference,
+ double elevationUnitMultiplier
) {
gridCoverageFactory = factory;
cachedElevationsFile = new File(cacheDirectory, "cached_elevations.obj");
this.readCachedElevations = readCachedElevations;
this.writeCachedElevations = writeCachedElevations;
this.includeEllipsoidToGeoidDifference = includeEllipsoidToGeoidDifference;
+ this.elevationUnitMultiplier = elevationUnitMultiplier;
}
public List provides() {
@@ -290,7 +298,7 @@ class ElevationRepairState {
public double initialElevation;
public ElevationRepairState(StreetEdge backEdge, ElevationRepairState backState,
- Vertex vertex, double distance, double initialElevation) {
+ Vertex vertex, double distance, double initialElevation) {
this.backEdge = backEdge;
this.backState = backState;
this.vertex = vertex;
@@ -331,7 +339,7 @@ private void assignMissingElevations(
if (!elevations.containsKey(e.getFromVertex())) {
double firstElevation = profile.getOrdinate(0, 1);
ElevationRepairState state = new ElevationRepairState(null, null,
- e.getFromVertex(), 0, firstElevation);
+ e.getFromVertex(), 0, firstElevation);
pq.insert(state, 0);
elevations.put(e.getFromVertex(), firstElevation);
}
@@ -339,7 +347,7 @@ private void assignMissingElevations(
if (!elevations.containsKey(e.getToVertex())) {
double lastElevation = profile.getOrdinate(profile.size() - 1, 1);
ElevationRepairState state = new ElevationRepairState(null, null, e.getToVertex(),
- 0, lastElevation);
+ 0, lastElevation);
pq.insert(state, 0);
elevations.put(e.getToVertex(), lastElevation);
}
@@ -382,7 +390,7 @@ private void assignMissingElevations(
} else {
// continue
ElevationRepairState newState = new ElevationRepairState(edge, state, tov,
- e.getDistance() + state.distance, state.initialElevation);
+ e.getDistance() + state.distance, state.initialElevation);
pq.insert(newState, e.getDistance() + state.distance);
}
} // end loop over outgoing edges
@@ -405,7 +413,7 @@ private void assignMissingElevations(
} else {
// continue
ElevationRepairState newState = new ElevationRepairState(edge, state, fromv,
- e.getDistance() + state.distance, state.initialElevation);
+ e.getDistance() + state.distance, state.initialElevation);
pq.insert(newState, e.getDistance() + state.distance);
}
} // end loop over incoming edges
@@ -423,13 +431,13 @@ private void assignMissingElevations(
double totalDistance = bestDistance + state.distance;
// trace backwards, setting states as we go
while (true) {
- // watch out for division by 0 here, which will propagate NaNs
- // all the way out to edge lengths
+ // watch out for division by 0 here, which will propagate NaNs
+ // all the way out to edge lengths
if (totalDistance == 0)
elevations.put(state.vertex, bestElevation);
else {
- double elevation = (bestElevation * state.distance +
- state.initialElevation * bestDistance) / totalDistance;
+ double elevation = (bestElevation * state.distance +
+ state.initialElevation * bestDistance) / totalDistance;
elevations.put(state.vertex, elevation);
}
if (state.backState == null)
@@ -559,7 +567,7 @@ private void processEdge(StreetWithElevationEdge ee, Coverage coverage) {
// construct the PCS
Coordinate coordArr[] = new Coordinate[coordList.size()];
PackedCoordinateSequence elevPCS = new PackedCoordinateSequence.Double(
- coordList.toArray(coordArr));
+ coordList.toArray(coordArr));
setEdgeElevationProfile(ee, elevPCS, graph);
} catch (PointOutsideCoverageException | TransformException e) {
@@ -608,7 +616,9 @@ private double getElevation(Coverage coverage, double x, double y) throws PointO
throw e;
}
nPointsEvaluated.incrementAndGet();
- return values[0] - (includeEllipsoidToGeoidDifference ? getApproximateEllipsoidToGeoidDifference(y, x) : 0);
+ return values[0] * elevationUnitMultiplier - (
+ includeEllipsoidToGeoidDifference ? getApproximateEllipsoidToGeoidDifference(y, x) : 0
+ );
}
/**
diff --git a/src/main/java/org/opentripplanner/graph_builder/module/ned/NEDDownloader.java b/src/main/java/org/opentripplanner/graph_builder/module/ned/NEDDownloader.java
index e7f4a6b066d..6038156ec10 100644
--- a/src/main/java/org/opentripplanner/graph_builder/module/ned/NEDDownloader.java
+++ b/src/main/java/org/opentripplanner/graph_builder/module/ned/NEDDownloader.java
@@ -1,6 +1,6 @@
package org.opentripplanner.graph_builder.module.ned;
-import com.vividsolutions.jts.geom.Envelope;
+import org.locationtech.jts.geom.Envelope;
import org.apache.axis.client.Call;
import org.apache.axis.client.Service;
import org.opentripplanner.graph_builder.services.ned.NEDTileSource;
diff --git a/src/main/java/org/opentripplanner/graph_builder/module/ned/UnifiedGridCoverage.java b/src/main/java/org/opentripplanner/graph_builder/module/ned/UnifiedGridCoverage.java
index 0a14b47b76a..1756fa35be6 100644
--- a/src/main/java/org/opentripplanner/graph_builder/module/ned/UnifiedGridCoverage.java
+++ b/src/main/java/org/opentripplanner/graph_builder/module/ned/UnifiedGridCoverage.java
@@ -1,12 +1,12 @@
package org.opentripplanner.graph_builder.module.ned;
-import com.vividsolutions.jts.geom.Coordinate;
-import com.vividsolutions.jts.geom.Envelope;
-import com.vividsolutions.jts.index.SpatialIndex;
-import com.vividsolutions.jts.index.strtree.STRtree;
import org.geotools.coverage.AbstractCoverage;
import org.geotools.coverage.grid.GridCoverage2D;
import org.geotools.geometry.jts.ReferencedEnvelope;
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.Envelope;
+import org.locationtech.jts.index.SpatialIndex;
+import org.locationtech.jts.index.strtree.STRtree;
import org.opengis.coverage.CannotEvaluateException;
import org.opengis.coverage.Coverage;
import org.opengis.coverage.PointOutsideCoverageException;
diff --git a/src/main/java/org/opentripplanner/graph_builder/module/osm/Area.java b/src/main/java/org/opentripplanner/graph_builder/module/osm/Area.java
index 34ad7c74209..52c4df8deb2 100644
--- a/src/main/java/org/opentripplanner/graph_builder/module/osm/Area.java
+++ b/src/main/java/org/opentripplanner/graph_builder/module/osm/Area.java
@@ -12,8 +12,8 @@
import org.opentripplanner.openstreetmap.model.OSMWithTags;
import com.google.common.collect.ArrayListMultimap;
-import com.vividsolutions.jts.geom.MultiPolygon;
-import com.vividsolutions.jts.geom.Polygon;
+import org.locationtech.jts.geom.MultiPolygon;
+import org.locationtech.jts.geom.Polygon;
/**
* Stores information about an OSM area needed for visibility graph construction. Algorithm based on
diff --git a/src/main/java/org/opentripplanner/graph_builder/module/osm/AreaGroup.java b/src/main/java/org/opentripplanner/graph_builder/module/osm/AreaGroup.java
index 5f10941ebfd..f732562bdd2 100644
--- a/src/main/java/org/opentripplanner/graph_builder/module/osm/AreaGroup.java
+++ b/src/main/java/org/opentripplanner/graph_builder/module/osm/AreaGroup.java
@@ -19,12 +19,12 @@
import com.google.common.collect.LinkedListMultimap;
import com.google.common.collect.Multimap;
-import com.vividsolutions.jts.geom.Coordinate;
-import com.vividsolutions.jts.geom.Geometry;
-import com.vividsolutions.jts.geom.GeometryCollection;
-import com.vividsolutions.jts.geom.GeometryFactory;
-import com.vividsolutions.jts.geom.LineString;
-import com.vividsolutions.jts.geom.Polygon;
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.Geometry;
+import org.locationtech.jts.geom.GeometryCollection;
+import org.locationtech.jts.geom.GeometryFactory;
+import org.locationtech.jts.geom.LineString;
+import org.locationtech.jts.geom.Polygon;
/**
* A group of possibly-contiguous areas sharing the same level
diff --git a/src/main/java/org/opentripplanner/graph_builder/module/osm/OSMDatabase.java b/src/main/java/org/opentripplanner/graph_builder/module/osm/OSMDatabase.java
index a04ba7bd135..d229cb43f6b 100644
--- a/src/main/java/org/opentripplanner/graph_builder/module/osm/OSMDatabase.java
+++ b/src/main/java/org/opentripplanner/graph_builder/module/osm/OSMDatabase.java
@@ -3,7 +3,7 @@
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Multimap;
-import com.vividsolutions.jts.geom.*;
+import org.locationtech.jts.geom.*;
import org.opentripplanner.common.RepeatingTimePeriod;
import org.opentripplanner.common.TurnRestrictionType;
import org.opentripplanner.common.geometry.GeometryUtils;
diff --git a/src/main/java/org/opentripplanner/graph_builder/module/osm/OSMFilter.java b/src/main/java/org/opentripplanner/graph_builder/module/osm/OSMFilter.java
index 47a9d0f7578..123abbb4794 100644
--- a/src/main/java/org/opentripplanner/graph_builder/module/osm/OSMFilter.java
+++ b/src/main/java/org/opentripplanner/graph_builder/module/osm/OSMFilter.java
@@ -26,23 +26,30 @@ public class OSMFilter {
* (as well as ways where all access is specifically forbidden to the public).
* http://wiki.openstreetmap.org/wiki/Tag:highway%3Dproposed
*/
- public static boolean isWayRoutable(OSMWithTags way) {
- if (!isOsmEntityRoutable(way))
+ static boolean isWayRoutable(OSMWithTags way) {
+ if (!isOsmEntityRoutable(way)) {
return false;
+ }
String highway = way.getTag("highway");
- if (highway != null
- && (highway.equals("conveyer") || highway.equals("proposed")
- || highway.equals("construction") || highway.equals("raceway") || highway
- .equals("unbuilt")))
- return false;
+ if (highway != null) {
+ if(
+ highway.equals("conveyer") ||
+ highway.equals("proposed") ||
+ highway.equals("construction") ||
+ highway.equals("razed") ||
+ highway.equals("raceway") ||
+ highway.equals("unbuilt")
+ ) {
+ return false;
+ }
+ }
if (way.isGeneralAccessDenied()) {
// There are exceptions.
return (way.isMotorcarExplicitlyAllowed() || way.isBicycleExplicitlyAllowed() || way
.isPedestrianExplicitlyAllowed() || way.isMotorVehicleExplicitlyAllowed());
}
-
return true;
}
diff --git a/src/main/java/org/opentripplanner/graph_builder/module/osm/OpenStreetMapModule.java b/src/main/java/org/opentripplanner/graph_builder/module/osm/OpenStreetMapModule.java
index 9bf84ab3bbd..6856fdc78ab 100644
--- a/src/main/java/org/opentripplanner/graph_builder/module/osm/OpenStreetMapModule.java
+++ b/src/main/java/org/opentripplanner/graph_builder/module/osm/OpenStreetMapModule.java
@@ -3,12 +3,12 @@
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Iterables;
-import com.vividsolutions.jts.geom.Coordinate;
-import com.vividsolutions.jts.geom.Envelope;
-import com.vividsolutions.jts.geom.Geometry;
-import com.vividsolutions.jts.geom.LineString;
-import com.vividsolutions.jts.geom.prep.PreparedGeometry;
-import com.vividsolutions.jts.geom.prep.PreparedGeometryFactory;
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.Envelope;
+import org.locationtech.jts.geom.Geometry;
+import org.locationtech.jts.geom.LineString;
+import org.locationtech.jts.geom.prep.PreparedGeometry;
+import org.locationtech.jts.geom.prep.PreparedGeometryFactory;
import org.opentripplanner.analyst.UnsupportedGeometryException;
import org.opentripplanner.common.TurnRestriction;
import org.opentripplanner.common.geometry.GeometryUtils;
@@ -244,7 +244,7 @@ public void loadMicromobilityTravelRestrictions(GraphBuilderParameters params) {
* value is returned if that operation was unsuccessful in any way.
*/
private PreparedGeometry parseGeometry(String areaUrlOrFile) {
- if (areaUrlOrFile == null) {
+ if (areaUrlOrFile == null || areaUrlOrFile.equals("")) {
return null;
}
InputStream data = null;
@@ -307,7 +307,7 @@ public void buildGraph() {
}
for (Area area : Iterables.concat(osmdb.getWalkableAreas(),
- osmdb.getParkAndRideAreas(), osmdb.getBikeParkingAreas()))
+ osmdb.getParkAndRideAreas(), osmdb.getBikeParkingAreas()))
setWayName(area.parent);
// figure out which nodes that are actually intersections
@@ -350,7 +350,7 @@ private void processBikeRentalNodes() {
LOG.info("Processing bike rental nodes...");
int n = 0;
BikeRentalStationService bikeRentalService = graph.getService(
- BikeRentalStationService.class, true);
+ BikeRentalStationService.class, true);
graph.putService(BikeRentalStationService.class, bikeRentalService);
for (OSMNode node : osmdb.getBikeRentalNodes()) {
n++;
@@ -364,7 +364,7 @@ private void processBikeRentalNodes() {
capacity = node.getCapacity();
} catch (NumberFormatException e) {
LOG.warn("Capacity for osm node " + node.getId() + " (" + creativeName
- + ") is not a number: " + node.getTag("capacity"));
+ + ") is not a number: " + node.getTag("capacity"));
}
}
String networks = node.getTag("network");
@@ -376,7 +376,7 @@ private void processBikeRentalNodes() {
networkSet.addAll(Arrays.asList(operators.split(";")));
if (networkSet.isEmpty()) {
LOG.warn("Bike rental station at osm node " + node.getId() + " ("
- + creativeName + ") with no network; including as compatible-with-all.");
+ + creativeName + ") with no network; including as compatible-with-all.");
networkSet = null; // Special "catch-all" value
}
BikeRentalStation station = new BikeRentalStation();
@@ -405,7 +405,7 @@ private void processBikeParkAndRideNodes() {
LOG.info("Processing bike P+R nodes...");
int n = 0;
BikeRentalStationService bikeRentalService = graph.getService(
- BikeRentalStationService.class, true);
+ BikeRentalStationService.class, true);
for (OSMNode node : osmdb.getBikeParkingNodes()) {
n++;
I18NString creativeName = wayPropertySet.getCreativeNameForWay(node);
@@ -453,7 +453,7 @@ private void buildBikeParkAndRideAreas() {
*/
private void buildBikeParkAndRideForArea(Area area) {
BikeRentalStationService bikeRentalService = graph.getService(
- BikeRentalStationService.class, true);
+ BikeRentalStationService.class, true);
Envelope envelope = new Envelope();
long osmId = area.parent.getId();
I18NString creativeName = wayPropertySet.getCreativeNameForWay(area.parent);
@@ -482,7 +482,7 @@ private void buildWalkableAreas(boolean skipVisibility, boolean platformEntriesL
}
List areaGroups = groupAreas(osmdb.getWalkableAreas());
WalkableAreaBuilder walkableAreaBuilder = new WalkableAreaBuilder(graph, osmdb,
- wayPropertySet, edgeFactory, this);
+ wayPropertySet, edgeFactory, this);
if (skipVisibility) {
for (AreaGroup group : areaGroups) {
walkableAreaBuilder.buildWithoutVisibility(group);
@@ -494,8 +494,8 @@ private void buildWalkableAreas(boolean skipVisibility, boolean platformEntriesL
if(platformEntriesLinking){
List platforms = osmdb.getWalkableAreas().stream().
- filter(area -> "platform".equals(area.parent.getTag("public_transport"))).
- collect(Collectors.toList());
+ filter(area -> "platform".equals(area.parent.getTag("public_transport"))).
+ collect(Collectors.toList());
List platformGroups = groupAreas(platforms);
for (AreaGroup group : platformGroups) {
walkableAreaBuilder.buildWithoutVisibility(group);
@@ -543,7 +543,7 @@ private boolean buildParkAndRideAreasForGroup(AreaGroup group) {
envelope.expandToInclude(new Coordinate(node.lon, node.lat));
OsmVertex accessVertex = getVertexForOsmNode(node, area.parent);
if (accessVertex.getIncoming().isEmpty()
- || accessVertex.getOutgoing().isEmpty())
+ || accessVertex.getOutgoing().isEmpty())
continue;
accessVertexes.add(accessVertex);
}
@@ -551,9 +551,9 @@ private boolean buildParkAndRideAreasForGroup(AreaGroup group) {
}
// Check P+R accessibility by walking and driving.
TraversalRequirements walkReq = new TraversalRequirements(new RoutingRequest(
- TraverseMode.WALK));
+ TraverseMode.WALK));
TraversalRequirements driveReq = new TraversalRequirements(new RoutingRequest(
- TraverseMode.CAR));
+ TraverseMode.CAR));
boolean walkAccessibleIn = false;
boolean carAccessibleIn = false;
boolean walkAccessibleOut = false;
@@ -591,8 +591,8 @@ private boolean buildParkAndRideAreasForGroup(AreaGroup group) {
}
// Place the P+R at the center of the envelope
ParkAndRideVertex parkAndRideVertex = new ParkAndRideVertex(graph, "P+R" + osmId,
- "P+R_" + osmId, (envelope.getMinX() + envelope.getMaxX()) / 2,
- (envelope.getMinY() + envelope.getMaxY()) / 2, creativeName);
+ "P+R_" + osmId, (envelope.getMinX() + envelope.getMaxX()) / 2,
+ (envelope.getMinY() + envelope.getMaxY()) / 2, creativeName);
new ParkAndRideEdge(parkAndRideVertex);
for (OsmVertex accessVertex : accessVertexes) {
new ParkAndRideLinkEdge(parkAndRideVertex, accessVertex);
@@ -628,7 +628,7 @@ private void buildBasicGraph() {
setWayName(way);
StreetTraversalPermission permissions = OSMFilter.getPermissionsForWay(way,
- wayData.getPermission(), graph, banDiscouragedWalking, banDiscouragedBiking);
+ wayData.getPermission(), graph, banDiscouragedWalking, banDiscouragedBiking);
if (!OSMFilter.isWayRoutable(way) || permissions.allowsNothing())
continue;
@@ -654,7 +654,7 @@ private void buildBasicGraph() {
}
}
if (nodeId != last
- && (node.lat != lastLat || node.lon != lastLon || levelsDiffer))
+ && (node.lat != lastLat || node.lon != lastLon || levelsDiffer))
nodes.add(nodeId);
last = nodeId;
lastLon = node.lon;
@@ -704,14 +704,14 @@ private void buildBasicGraph() {
}
if (intersectionNodes.containsKey(endNode) || i == nodes.size() - 2
- || nodes.subList(0, i).contains(nodes.get(i))
- || osmEndNode.hasTag("ele")
- || osmEndNode.isStop()
- || osmEndNode.isBollard()) {
+ || nodes.subList(0, i).contains(nodes.get(i))
+ || osmEndNode.hasTag("ele")
+ || osmEndNode.isStop()
+ || osmEndNode.isBollard()) {
segmentCoordinates.add(getCoordinate(osmEndNode));
geometry = GeometryUtils.getGeometryFactory().createLineString(
- segmentCoordinates.toArray(new Coordinate[0]));
+ segmentCoordinates.toArray(new Coordinate[0]));
segmentCoordinates.clear();
} else {
segmentCoordinates.add(getCoordinate(osmEndNode));
@@ -743,7 +743,7 @@ private void buildBasicGraph() {
}
}
P2 streets = getEdgesForStreet(startEndpoint, endEndpoint,
- way, i, osmStartNode.getId(), osmEndNode.getId(), permissions, geometry);
+ way, i, osmStartNode.getId(), osmEndNode.getId(), permissions, geometry);
StreetEdge street = streets.first;
StreetEdge backStreet = streets.second;
@@ -795,7 +795,7 @@ private void applyMicromobilityRestrictions(StreetEdge streetEdge) {
// TODO Set this to private once WalkableAreaBuilder is gone
protected void applyWayProperties(StreetEdge street, StreetEdge backStreet,
- WayProperties wayData, OSMWithTags way) {
+ WayProperties wayData, OSMWithTags way) {
Set> notes = wayPropertySet.getNoteForWay(way);
float walkSafetyFactor = walkLTSGenerator.computeScore(way);
@@ -870,11 +870,11 @@ private void buildElevatorEdges(Graph graph) {
/*
* first, build FreeEdges to disconnect from the graph, GenericVertices to serve as attachment points, and ElevatorBoard and
* ElevatorAlight edges to connect future ElevatorHop edges to. After this iteration, graph will look like (side view): +==+~~X
- *
+ *
* +==+~~X
- *
+ *
* +==+~~X
- *
+ *
* + GenericVertex, X EndpointVertex, ~~ FreeEdge, == ElevatorBoardEdge/ElevatorAlightEdge Another loop will fill in the
* ElevatorHopEdges.
*/
@@ -889,15 +889,15 @@ private void buildElevatorEdges(Graph graph) {
String levelName = level.longName;
ElevatorOffboardVertex offboardVertex = new ElevatorOffboardVertex(graph,
- sourceVertexLabel + "_offboard", sourceVertex.getX(),
- sourceVertex.getY(), levelName);
+ sourceVertexLabel + "_offboard", sourceVertex.getX(),
+ sourceVertex.getY(), levelName);
new FreeEdge(sourceVertex, offboardVertex);
new FreeEdge(offboardVertex, sourceVertex);
ElevatorOnboardVertex onboardVertex = new ElevatorOnboardVertex(graph,
- sourceVertexLabel + "_onboard", sourceVertex.getX(),
- sourceVertex.getY(), levelName);
+ sourceVertexLabel + "_onboard", sourceVertex.getX(),
+ sourceVertex.getY(), levelName);
new ElevatorBoardEdge(offboardVertex, onboardVertex);
new ElevatorAlightEdge(onboardVertex, offboardVertex, level.longName);
@@ -1016,7 +1016,7 @@ private void unifyTurnRestrictions() {
}
private void applyEdgesToTurnRestrictions(OSMWay way, long startNode, long endNode,
- StreetEdge street, StreetEdge backStreet) {
+ StreetEdge street, StreetEdge backStreet) {
/* Check if there are turn restrictions starting on this segment */
Collection restrictionTags = osmdb.getFromWayTurnRestrictions(way.getId());
@@ -1079,7 +1079,7 @@ private void initIntersectionNodes() {
*/
private void applyBikeSafetyFactor(Graph graph) {
LOG.info(graph.addBuilderAnnotation(new Graphwide(
- "Multiplying all bike safety values by " + (1 / bestBikeSafety))));
+ "Multiplying all bike safety values by " + (1 / bestBikeSafety))));
HashSet seenEdges = new HashSet();
HashSet seenAreas = new HashSet();
for (Vertex vertex : graph.getVertices()) {
@@ -1091,7 +1091,7 @@ private void applyBikeSafetyFactor(Graph graph) {
seenAreas.add(areaEdgeList);
for (NamedArea area : areaEdgeList.getAreas()) {
area.setBicycleSafetyMultiplier(area.getBicycleSafetyMultiplier()
- / bestBikeSafety);
+ / bestBikeSafety);
}
}
if (!(e instanceof StreetEdge)) {
@@ -1153,8 +1153,8 @@ private double getGeometryLengthMeters(Geometry geometry) {
* @param startEndpoint
*/
private P2 getEdgesForStreet(OsmVertex startEndpoint,
- OsmVertex endEndpoint, OSMWay way, int index, long startNode, long endNode,
- StreetTraversalPermission permissions, LineString geometry) {
+ OsmVertex endEndpoint, OSMWay way, int index, long startNode, long endNode,
+ StreetTraversalPermission permissions, LineString geometry) {
// No point in returning edges that can't be traversed by anyone.
if (permissions.allowsNothing()) {
return new P2(null, null);
@@ -1165,17 +1165,17 @@ private P2 getEdgesForStreet(OsmVertex startEndpoint,
double length = this.getGeometryLengthMeters(geometry);
P2 permissionPair = OSMFilter.getPermissions(permissions,
- way);
+ way);
StreetTraversalPermission permissionsFront = permissionPair.first;
StreetTraversalPermission permissionsBack = permissionPair.second;
if (permissionsFront.allowsAnything()) {
street = getEdgeForStreet(startEndpoint, endEndpoint, way, index, startNode, endNode, length,
- permissionsFront, geometry, false);
+ permissionsFront, geometry, false);
}
if (permissionsBack.allowsAnything()) {
backStreet = getEdgeForStreet(endEndpoint, startEndpoint, way, index, endNode, startNode, length,
- permissionsBack, backGeometry, true);
+ permissionsBack, backGeometry, true);
}
if (street != null && backStreet != null) {
backStreet.shareData(street);
@@ -1193,8 +1193,8 @@ private P2 getEdgesForStreet(OsmVertex startEndpoint,
}
private StreetEdge getEdgeForStreet(OsmVertex startEndpoint, OsmVertex endEndpoint,
- OSMWay way, int index, long startNode, long endNode, double length,
- StreetTraversalPermission permissions, LineString geometry, boolean back) {
+ OSMWay way, int index, long startNode, long endNode, double length,
+ StreetTraversalPermission permissions, LineString geometry, boolean back) {
String label = "way " + way.getId() + " from " + index;
label = unique(label);
@@ -1209,7 +1209,7 @@ private StreetEdge getEdgeForStreet(OsmVertex startEndpoint, OsmVertex endEndpoi
float carSpeed = wayPropertySet.getCarSpeedForWay(way, back);
StreetEdge street = edgeFactory.createEdge(startEndpoint, endEndpoint, geometry, name, length,
- permissions, back);
+ permissions, back);
street.setCarSpeed(carSpeed);
street.setTNCStopSuitability(wayPropertySet.isSuitableForTNCStop(way));
street.setFloatingCarDropoffSuitability(wayPropertySet.isSuitableForStreetParking(way));
@@ -1219,12 +1219,12 @@ private StreetEdge getEdgeForStreet(OsmVertex startEndpoint, OsmVertex endEndpoi
if ("crossing".equals(highway) && !way.isTag("bicycle", "designated")) {
cls = StreetEdge.CLASS_CROSSING;
} else if ("footway".equals(highway) && way.isTag("footway", "crossing")
- && !way.isTag("bicycle", "designated")) {
+ && !way.isTag("bicycle", "designated")) {
cls = StreetEdge.CLASS_CROSSING;
} else if ("residential".equals(highway) || "tertiary".equals(highway)
- || "secondary".equals(highway) || "secondary_link".equals(highway)
- || "primary".equals(highway) || "primary_link".equals(highway)
- || "trunk".equals(highway) || "trunk_link".equals(highway)) {
+ || "secondary".equals(highway) || "secondary_link".equals(highway)
+ || "primary".equals(highway) || "primary_link".equals(highway)
+ || "trunk".equals(highway) || "trunk_link".equals(highway)) {
cls = StreetEdge.CLASS_STREET;
} else {
cls = StreetEdge.CLASS_OTHERPATH;
@@ -1240,7 +1240,7 @@ private StreetEdge getEdgeForStreet(OsmVertex startEndpoint, OsmVertex endEndpoi
/* TODO: This should probably generalized somehow? */
if (!ignoreWheelchairAccessibility
- && (way.isTagFalse("wheelchair") || (steps && !way.isTagTrue("wheelchair")))) {
+ && (way.isTagFalse("wheelchair") || (steps && !way.isTagTrue("wheelchair")))) {
street.setWheelchairAccessible(false);
}
@@ -1296,7 +1296,7 @@ private OsmVertex recordLevel(OSMNode node, OSMWithTags way) {
Coordinate coordinate = getCoordinate(node);
String label = this.getLevelNodeLabel(node, level);
OsmVertex vertex = new OsmVertex(graph, label, coordinate.x,
- coordinate.y, node.getId(), new NonLocalizedString(label));
+ coordinate.y, node.getId(), new NonLocalizedString(label));
vertices.put(level, vertex);
// multilevel nodes should also undergo turn-conversion
endpoints.add(vertex);
diff --git a/src/main/java/org/opentripplanner/graph_builder/module/osm/PlatformLinker.java b/src/main/java/org/opentripplanner/graph_builder/module/osm/PlatformLinker.java
index 756c9bf3ca8..060eb7c19ee 100644
--- a/src/main/java/org/opentripplanner/graph_builder/module/osm/PlatformLinker.java
+++ b/src/main/java/org/opentripplanner/graph_builder/module/osm/PlatformLinker.java
@@ -1,8 +1,8 @@
package org.opentripplanner.graph_builder.module.osm;
-import com.vividsolutions.jts.geom.Coordinate;
-import com.vividsolutions.jts.geom.GeometryFactory;
-import com.vividsolutions.jts.geom.LineString;
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.GeometryFactory;
+import org.locationtech.jts.geom.LineString;
import org.opentripplanner.common.geometry.GeometryUtils;
import org.opentripplanner.common.geometry.SphericalDistanceLibrary;
import org.opentripplanner.graph_builder.services.StreetEdgeFactory;
diff --git a/src/main/java/org/opentripplanner/graph_builder/module/osm/Ring.java b/src/main/java/org/opentripplanner/graph_builder/module/osm/Ring.java
index 39765b6b43c..b27be031a13 100644
--- a/src/main/java/org/opentripplanner/graph_builder/module/osm/Ring.java
+++ b/src/main/java/org/opentripplanner/graph_builder/module/osm/Ring.java
@@ -10,12 +10,12 @@
import org.opentripplanner.visibility.VLPoint;
import org.opentripplanner.visibility.VLPolygon;
-import com.vividsolutions.jts.geom.Coordinate;
-import com.vividsolutions.jts.geom.Geometry;
-import com.vividsolutions.jts.geom.GeometryFactory;
-import com.vividsolutions.jts.geom.LineString;
-import com.vividsolutions.jts.geom.LinearRing;
-import com.vividsolutions.jts.geom.Polygon;
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.Geometry;
+import org.locationtech.jts.geom.GeometryFactory;
+import org.locationtech.jts.geom.LineString;
+import org.locationtech.jts.geom.LinearRing;
+import org.locationtech.jts.geom.Polygon;
public class Ring {
diff --git a/src/main/java/org/opentripplanner/graph_builder/module/osm/UKWayPropertySetSource.java b/src/main/java/org/opentripplanner/graph_builder/module/osm/UKWayPropertySetSource.java
new file mode 100644
index 00000000000..aa11454c710
--- /dev/null
+++ b/src/main/java/org/opentripplanner/graph_builder/module/osm/UKWayPropertySetSource.java
@@ -0,0 +1,72 @@
+package org.opentripplanner.graph_builder.module.osm;
+
+import org.opentripplanner.routing.edgetype.StreetTraversalPermission;
+
+/**
+ * OSM way properties for UK roads.
+ * The main differences compared to the default property set are:
+ * 1. In the UK there is no real distinction between trunk highways and primary highways, other than the
+ * body responsible for them. Most highway=trunk and highway=trunk_link will allow traversal by all modes.
+ * 2. Speeds have been set to reflect average free flow road speeds provided by UK DfT. In particular
+ * note that a distinction is made between tertiary and unclassified/residential roads. The default has these
+ * the same (25mph) but in the UK tertiary roads are considered by OSM tagging guidelines to be busy unclassified
+ * through roads wide enough to allow two cars to pass safely. The free flow speeds are therefore higher.
+ * These changes result in more realistic driving routes.
+ * https://www.gov.uk/government/statistical-data-sets/vehicle-speed-compliance-statistics-data-tables-spe
+ * https://wiki.openstreetmap.org/wiki/United_Kingdom_Tagging_Guidelines
+ *
+ * @author marcusyoung
+ * @see WayPropertySetSource
+ * @see DefaultWayPropertySetSource
+ */
+public class UKWayPropertySetSource implements WayPropertySetSource {
+
+ @Override
+ public void populateProperties(WayPropertySet props) {
+ // Replace existing matching properties as the logic is that the first statement registered takes precedence over later statements
+ props.setProperties("highway=trunk_link", StreetTraversalPermission.ALL, 2.06, 2.06);
+ props.setProperties("highway=trunk", StreetTraversalPermission.ALL, 7.47, 7.47);
+ props.setProperties("highway=trunk;cycleway=lane", StreetTraversalPermission.ALL, 1.5,
+ 1.5);
+ props.setProperties("highway=trunk_link;cycleway=lane", StreetTraversalPermission.ALL,
+ 1.15, 1.15);
+ props.setProperties("highway=trunk;cycleway=share_busway", StreetTraversalPermission.ALL,
+ 1.75, 1.75);
+ props.setProperties("highway=trunk_link;cycleway=share_busway",
+ StreetTraversalPermission.ALL,1.25, 1.25);
+ props.setProperties("highway=trunk;cycleway=opposite_lane", StreetTraversalPermission.ALL,
+ 7.47, 1.5);
+ props.setProperties("highway=trunk_link;cycleway=opposite_lane",
+ StreetTraversalPermission.ALL, 2.06, 1.15);
+ props.setProperties("highway=trunk;cycleway=track", StreetTraversalPermission.ALL, 0.95,
+ 0.95);
+ props.setProperties("highway=trunk_link;cycleway=track", StreetTraversalPermission.ALL,
+ 0.85, 0.85);
+ props.setProperties("highway=trunk;cycleway=opposite_track", StreetTraversalPermission.ALL,
+ 7.47, 0.95);
+ props.setProperties("highway=trunk_link;cycleway=opposite_track",
+ StreetTraversalPermission.ALL, 2.06, 0.85);
+ props.setProperties("highway=trunk;bicycle=designated", StreetTraversalPermission.ALL,
+ 7.25, 7.25);
+ props.setProperties("highway=trunk_link;bicycle=designated", StreetTraversalPermission.ALL,
+ 2, 2);
+
+ /*
+ * Automobile speeds in UK. Based on recorded free flow speeds for motorways, trunk and primary and
+ * my (marcusyoung) personal experience in obtaining realistic routes.
+ *
+ */
+ props.setCarSpeed("highway=motorway", 30.4f); // ~=68mph
+ props.setCarSpeed("highway=motorway_link", 22.4f); // ~= 50mph
+ props.setCarSpeed("highway=trunk", 22.4f); // ~=50mph
+ props.setCarSpeed("highway=trunk_link", 17.9f); // ~= 40mph
+ props.setCarSpeed("highway=primary", 22.4f); // ~=50mph
+ props.setCarSpeed("highway=primary_link", 17.9f); // ~= 40mph
+ props.setCarSpeed("highway=secondary", 17.9f); // ~= 40mph
+ props.setCarSpeed("highway=secondary_link", 13.4f); // ~= 30mph
+ props.setCarSpeed("highway=tertiary", 15.7f); // ~= 35mph
+
+ // Read the rest from the default set
+ new DefaultWayPropertySetSource().populateProperties(props);
+ }
+}
diff --git a/src/main/java/org/opentripplanner/graph_builder/module/osm/WalkableAreaBuilder.java b/src/main/java/org/opentripplanner/graph_builder/module/osm/WalkableAreaBuilder.java
index 12ae3cccf6e..998d0d2fe69 100644
--- a/src/main/java/org/opentripplanner/graph_builder/module/osm/WalkableAreaBuilder.java
+++ b/src/main/java/org/opentripplanner/graph_builder/module/osm/WalkableAreaBuilder.java
@@ -39,15 +39,15 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import com.vividsolutions.jts.geom.Coordinate;
-import com.vividsolutions.jts.geom.Geometry;
-import com.vividsolutions.jts.geom.GeometryFactory;
-import com.vividsolutions.jts.geom.LineString;
-import com.vividsolutions.jts.geom.LinearRing;
-import com.vividsolutions.jts.geom.MultiLineString;
-import com.vividsolutions.jts.geom.MultiPolygon;
-import com.vividsolutions.jts.geom.Point;
-import com.vividsolutions.jts.geom.Polygon;
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.Geometry;
+import org.locationtech.jts.geom.GeometryFactory;
+import org.locationtech.jts.geom.LineString;
+import org.locationtech.jts.geom.LinearRing;
+import org.locationtech.jts.geom.MultiLineString;
+import org.locationtech.jts.geom.MultiPolygon;
+import org.locationtech.jts.geom.Point;
+import org.locationtech.jts.geom.Polygon;
import org.opentripplanner.util.I18NString;
/**
diff --git a/src/main/java/org/opentripplanner/graph_builder/module/osm/WayPropertySetSource.java b/src/main/java/org/opentripplanner/graph_builder/module/osm/WayPropertySetSource.java
index 3068b71fbcd..8e2adad4d93 100644
--- a/src/main/java/org/opentripplanner/graph_builder/module/osm/WayPropertySetSource.java
+++ b/src/main/java/org/opentripplanner/graph_builder/module/osm/WayPropertySetSource.java
@@ -21,6 +21,8 @@ public static WayPropertySetSource fromConfig(String type) {
return new DefaultWayPropertySetSource();
} else if ("norway".equals(type)) {
return new NorwayWayPropertySetSource();
+ } else if ("uk".equals(type)) {
+ return new UKWayPropertySetSource();
} else {
throw new IllegalArgumentException(String.format("Unknown osmWayPropertySet: '%s'", type));
}
diff --git a/src/main/java/org/opentripplanner/graph_builder/module/shapefile/ShapefileStreetModule.java b/src/main/java/org/opentripplanner/graph_builder/module/shapefile/ShapefileStreetModule.java
index 50dbf52a0ad..abadb63116a 100644
--- a/src/main/java/org/opentripplanner/graph_builder/module/shapefile/ShapefileStreetModule.java
+++ b/src/main/java/org/opentripplanner/graph_builder/module/shapefile/ShapefileStreetModule.java
@@ -38,10 +38,10 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import com.vividsolutions.jts.geom.Coordinate;
-import com.vividsolutions.jts.geom.Geometry;
-import com.vividsolutions.jts.geom.LineString;
-import com.vividsolutions.jts.geom.MultiLineString;
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.Geometry;
+import org.locationtech.jts.geom.LineString;
+import org.locationtech.jts.geom.MultiLineString;
import org.opentripplanner.util.NonLocalizedString;
/**
diff --git a/src/main/java/org/opentripplanner/graph_builder/services/DefaultStreetEdgeFactory.java b/src/main/java/org/opentripplanner/graph_builder/services/DefaultStreetEdgeFactory.java
index 96b6d677d42..363b996b9d1 100644
--- a/src/main/java/org/opentripplanner/graph_builder/services/DefaultStreetEdgeFactory.java
+++ b/src/main/java/org/opentripplanner/graph_builder/services/DefaultStreetEdgeFactory.java
@@ -7,7 +7,7 @@
import org.opentripplanner.routing.edgetype.StreetWithElevationEdge;
import org.opentripplanner.routing.vertextype.IntersectionVertex;
-import com.vividsolutions.jts.geom.LineString;
+import org.locationtech.jts.geom.LineString;
import org.opentripplanner.util.I18NString;
public class DefaultStreetEdgeFactory implements StreetEdgeFactory {
diff --git a/src/main/java/org/opentripplanner/graph_builder/services/StreetEdgeFactory.java b/src/main/java/org/opentripplanner/graph_builder/services/StreetEdgeFactory.java
index 6e2723e5d3a..13238de574b 100644
--- a/src/main/java/org/opentripplanner/graph_builder/services/StreetEdgeFactory.java
+++ b/src/main/java/org/opentripplanner/graph_builder/services/StreetEdgeFactory.java
@@ -6,7 +6,7 @@
import org.opentripplanner.routing.edgetype.StreetTraversalPermission;
import org.opentripplanner.routing.vertextype.IntersectionVertex;
-import com.vividsolutions.jts.geom.LineString;
+import org.locationtech.jts.geom.LineString;
import org.opentripplanner.util.I18NString;
/**
diff --git a/src/main/java/org/opentripplanner/gtfs/mapping/AreaMapper.java b/src/main/java/org/opentripplanner/gtfs/mapping/AreaMapper.java
new file mode 100644
index 00000000000..dc79f9f49f3
--- /dev/null
+++ b/src/main/java/org/opentripplanner/gtfs/mapping/AreaMapper.java
@@ -0,0 +1,32 @@
+package org.opentripplanner.gtfs.mapping;
+
+import org.opentripplanner.model.FlexArea;
+import org.opentripplanner.util.MapUtils;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+/** Map from the OBA model of GTFS-flex areas to the OTP internal model of areas. */
+class AreaMapper {
+
+ private final Map mappedAreas = new HashMap<>();
+
+ Collection map(Collection agencies) {
+ return MapUtils.mapToList(agencies, this::map);
+ }
+
+ /** Map from the OBA model of GTFS-flex areas to the OTP internal model of areas. */
+ FlexArea map(org.onebusaway.gtfs.model.Area orginal) {
+ return orginal == null ? null : mappedAreas.computeIfAbsent(orginal, this::doMap);
+ }
+
+ private FlexArea doMap(org.onebusaway.gtfs.model.Area rhs) {
+ FlexArea lhs = new FlexArea();
+
+ lhs.setId(AgencyAndIdMapper.mapAgencyAndId(rhs.getId()));
+ lhs.setWkt(rhs.getWkt());
+
+ return lhs;
+ }
+}
diff --git a/src/main/java/org/opentripplanner/gtfs/mapping/GTFSToOtpTransitServiceMapper.java b/src/main/java/org/opentripplanner/gtfs/mapping/GTFSToOtpTransitServiceMapper.java
index 2805052595e..92f1f208bb5 100644
--- a/src/main/java/org/opentripplanner/gtfs/mapping/GTFSToOtpTransitServiceMapper.java
+++ b/src/main/java/org/opentripplanner/gtfs/mapping/GTFSToOtpTransitServiceMapper.java
@@ -42,6 +42,8 @@ public class GTFSToOtpTransitServiceMapper {
routeMapper, fareAttributeMapper
);
+ private final AreaMapper areaMapper = new AreaMapper();
+
/**
* Map from GTFS data to the internal OTP model
*/
@@ -66,6 +68,7 @@ private OtpTransitService map(org.onebusaway.gtfs.services.GtfsRelationalDao dat
builder.getStopTimes().addAll(stopTimeMapper.map(data.getAllStopTimes()));
builder.getTransfers().addAll(transferMapper.map(data.getAllTransfers()));
builder.getTrips().addAll(tripMapper.map(data.getAllTrips()));
+ builder.getFlexAreas().addAll(areaMapper.map(data.getAllAreas()));
return builder.build();
}
diff --git a/src/main/java/org/opentripplanner/gtfs/mapping/RouteMapper.java b/src/main/java/org/opentripplanner/gtfs/mapping/RouteMapper.java
index a9108fd2818..bef952e95c7 100644
--- a/src/main/java/org/opentripplanner/gtfs/mapping/RouteMapper.java
+++ b/src/main/java/org/opentripplanner/gtfs/mapping/RouteMapper.java
@@ -42,6 +42,7 @@ private Route doMap(org.onebusaway.gtfs.model.Route rhs) {
lhs.setBikesAllowed(rhs.getBikesAllowed());
lhs.setSortOrder(rhs.getSortOrder());
lhs.setBrandingUrl(rhs.getBrandingUrl());
+ lhs.setEligibilityRestricted(rhs.getEligibilityRestricted());
return lhs;
}
diff --git a/src/main/java/org/opentripplanner/gtfs/mapping/StopTimeMapper.java b/src/main/java/org/opentripplanner/gtfs/mapping/StopTimeMapper.java
index 82412c33519..ab592f9069d 100644
--- a/src/main/java/org/opentripplanner/gtfs/mapping/StopTimeMapper.java
+++ b/src/main/java/org/opentripplanner/gtfs/mapping/StopTimeMapper.java
@@ -44,6 +44,12 @@ private StopTime doMap(org.onebusaway.gtfs.model.StopTime rhs) {
lhs.setDropOffType(rhs.getDropOffType());
lhs.setShapeDistTraveled(rhs.getShapeDistTraveled());
lhs.setFarePeriodId(rhs.getFarePeriodId());
+ lhs.setContinuousPickup(rhs.getContinuousPickup());
+ lhs.setContinuousDropOff(rhs.getContinuousDropOff());
+ lhs.setStartServiceArea(rhs.getStartServiceArea());
+ lhs.setEndServiceArea(rhs.getEndServiceArea());
+ lhs.setStartServiceAreaRadius(rhs.getStartServiceAreaRadius());
+ lhs.setEndServiceAreaRadius(rhs.getEndServiceAreaRadius());
// Skip mapping of proxy
// private transient StopTimeProxy proxy;
diff --git a/src/main/java/org/opentripplanner/gtfs/mapping/TripMapper.java b/src/main/java/org/opentripplanner/gtfs/mapping/TripMapper.java
index 848ebbdd58d..41ce89d9b35 100644
--- a/src/main/java/org/opentripplanner/gtfs/mapping/TripMapper.java
+++ b/src/main/java/org/opentripplanner/gtfs/mapping/TripMapper.java
@@ -42,6 +42,13 @@ private Trip doMap(org.onebusaway.gtfs.model.Trip rhs) {
lhs.setTripBikesAllowed(rhs.getTripBikesAllowed());
lhs.setBikesAllowed(rhs.getBikesAllowed());
lhs.setFareId(rhs.getFareId());
+ lhs.setDrtMaxTravelTime(rhs.getDrtMaxTravelTime());
+ lhs.setDrtAvgTravelTime(rhs.getDrtAvgTravelTime());
+ lhs.setDrtAdvanceBookMin(rhs.getDrtAdvanceBookMin());
+ lhs.setDrtPickupMessage(rhs.getDrtPickupMessage());
+ lhs.setDrtDropOffMessage(rhs.getDrtDropOffMessage());
+ lhs.setContinuousPickupMessage(rhs.getContinuousPickupMessage());
+ lhs.setContinuousDropOffMessage(rhs.getContinuousDropOffMessage());
return lhs;
}
diff --git a/src/main/java/org/opentripplanner/index/IndexAPI.java b/src/main/java/org/opentripplanner/index/IndexAPI.java
index 81343c806fd..236c1b42b75 100644
--- a/src/main/java/org/opentripplanner/index/IndexAPI.java
+++ b/src/main/java/org/opentripplanner/index/IndexAPI.java
@@ -6,8 +6,9 @@
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.collect.Collections2;
-import com.vividsolutions.jts.geom.Coordinate;
-import com.vividsolutions.jts.geom.Envelope;
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.Envelope;
+import org.locationtech.jts.geom.Geometry;
import org.opentripplanner.model.Agency;
import org.opentripplanner.model.FeedScopedId;
import org.opentripplanner.model.FeedInfo;
@@ -17,6 +18,7 @@
import org.opentripplanner.model.calendar.ServiceDate;
import org.opentripplanner.common.geometry.SphericalDistanceLibrary;
import org.opentripplanner.gtfs.GtfsLibrary;
+import org.opentripplanner.index.model.AreaShort;
import org.opentripplanner.index.model.PatternDetail;
import org.opentripplanner.index.model.PatternShort;
import org.opentripplanner.index.model.RouteShort;
@@ -599,6 +601,29 @@ public Response getStopCluster (@PathParam("clusterId") String clusterIdString)
}
}
+ /**
+ * Return all GTFS-Flex area IDs. Areas are defined in GTFS-Flex to be a lat/lon polygon in
+ * which certain kinds of flex service take place (deviated-route and call-and-ride).
+ */
+ @GET
+ @Path("/flexAreas")
+ public Response getAllFlexAreas() {
+ List ids = new ArrayList<>(index.flexAreasById.keySet());
+ return Response.status(Status.OK).entity(ids).build();
+ }
+
+ /** Return a specific GTFS-Flex area given an ID in Agency:ID format. */
+ @GET
+ @Path("/flexAreas/{id}")
+ public Response getFlexAreaByFeedId(@PathParam("id") String areaIdString) {
+ FeedScopedId id = GtfsLibrary.convertIdFromString(areaIdString);
+ Geometry area = index.flexAreasById.get(id);
+ if (area == null) {
+ return Response.status(Status.NOT_FOUND).build();
+ }
+ return Response.status(Status.OK).entity(new AreaShort(id, area)).build();
+ }
+
@POST
@Path("/graphql")
@Consumes(MediaType.APPLICATION_JSON)
diff --git a/src/main/java/org/opentripplanner/index/IndexGraphQLSchema.java b/src/main/java/org/opentripplanner/index/IndexGraphQLSchema.java
index b9c1900b469..fd7bbcac4be 100644
--- a/src/main/java/org/opentripplanner/index/IndexGraphQLSchema.java
+++ b/src/main/java/org/opentripplanner/index/IndexGraphQLSchema.java
@@ -1,9 +1,9 @@
package org.opentripplanner.index;
import com.google.common.collect.ImmutableMap;
-import com.vividsolutions.jts.geom.Coordinate;
-import com.vividsolutions.jts.geom.Envelope;
-import com.vividsolutions.jts.geom.LineString;
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.Envelope;
+import org.locationtech.jts.geom.LineString;
import graphql.Scalars;
import graphql.relay.Relay;
import graphql.relay.SimpleListConnection;
diff --git a/src/main/java/org/opentripplanner/index/model/AreaShort.java b/src/main/java/org/opentripplanner/index/model/AreaShort.java
new file mode 100644
index 00000000000..39bc530c18d
--- /dev/null
+++ b/src/main/java/org/opentripplanner/index/model/AreaShort.java
@@ -0,0 +1,16 @@
+package org.opentripplanner.index.model;
+
+import org.locationtech.jts.geom.Geometry;
+import org.opentripplanner.model.FeedScopedId;
+import org.opentripplanner.util.PolylineEncoder;
+import org.opentripplanner.util.model.EncodedPolylineBean;
+
+public class AreaShort {
+ public FeedScopedId areaId;
+ public EncodedPolylineBean polygon;
+
+ public AreaShort(FeedScopedId areaId, Geometry polygon) {
+ this.areaId = areaId;
+ this.polygon = PolylineEncoder.createEncodings(polygon.getBoundary());
+ }
+}
diff --git a/src/main/java/org/opentripplanner/index/model/TripTimeShort.java b/src/main/java/org/opentripplanner/index/model/TripTimeShort.java
index 8b030343c2a..a5e1754da43 100644
--- a/src/main/java/org/opentripplanner/index/model/TripTimeShort.java
+++ b/src/main/java/org/opentripplanner/index/model/TripTimeShort.java
@@ -31,6 +31,10 @@ public class TripTimeShort {
public FeedScopedId tripId;
public String blockId;
public String headsign;
+ public int continuousPickup;
+ public int continuousDropOff;
+ public double serviceAreaRadius;
+ public String serviceArea;
/**
* This is stop-specific, so the index i is a stop index, not a hop index.
@@ -50,6 +54,10 @@ public TripTimeShort(TripTimes tt, int i, Stop stop) {
realtimeState = tt.getRealTimeState();
blockId = tt.trip.getBlockId();
headsign = tt.getHeadsign(i);
+ continuousPickup = tt.getContinuousPickup(i);
+ continuousDropOff = tt.getContinuousDropOff(i);
+ serviceAreaRadius = tt.getServiceAreaRadius(i);
+ serviceArea = tt.getServiceArea(i);
}
public TripTimeShort(TripTimes tt, int i, Stop stop, ServiceDay sd) {
diff --git a/src/main/java/org/opentripplanner/inspector/EdgeVertexTileRenderer.java b/src/main/java/org/opentripplanner/inspector/EdgeVertexTileRenderer.java
index 06732e1757e..414aec4040b 100644
--- a/src/main/java/org/opentripplanner/inspector/EdgeVertexTileRenderer.java
+++ b/src/main/java/org/opentripplanner/inspector/EdgeVertexTileRenderer.java
@@ -19,18 +19,18 @@
import com.jhlabs.awt.ShapeStroke;
import com.jhlabs.awt.TextStroke;
-import com.vividsolutions.jts.awt.IdentityPointTransformation;
-import com.vividsolutions.jts.awt.PointShapeFactory;
-import com.vividsolutions.jts.awt.ShapeWriter;
-import com.vividsolutions.jts.geom.Coordinate;
-import com.vividsolutions.jts.geom.Envelope;
-import com.vividsolutions.jts.geom.Geometry;
-import com.vividsolutions.jts.geom.GeometryFactory;
-import com.vividsolutions.jts.geom.LineString;
-import com.vividsolutions.jts.geom.Point;
-import com.vividsolutions.jts.geom.PrecisionModel;
-import com.vividsolutions.jts.operation.buffer.BufferParameters;
-import com.vividsolutions.jts.operation.buffer.OffsetCurveBuilder;
+import org.locationtech.jts.awt.IdentityPointTransformation;
+import org.locationtech.jts.awt.PointShapeFactory;
+import org.locationtech.jts.awt.ShapeWriter;
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.Envelope;
+import org.locationtech.jts.geom.Geometry;
+import org.locationtech.jts.geom.GeometryFactory;
+import org.locationtech.jts.geom.LineString;
+import org.locationtech.jts.geom.Point;
+import org.locationtech.jts.geom.PrecisionModel;
+import org.locationtech.jts.operation.buffer.BufferParameters;
+import org.locationtech.jts.operation.buffer.OffsetCurveBuilder;
/**
* A TileRenderer implementation which get all edges/vertex in the bounding box of the tile, and
diff --git a/src/main/java/org/opentripplanner/inspector/TileRenderer.java b/src/main/java/org/opentripplanner/inspector/TileRenderer.java
index c27649a9ed6..2f529f8dda2 100644
--- a/src/main/java/org/opentripplanner/inspector/TileRenderer.java
+++ b/src/main/java/org/opentripplanner/inspector/TileRenderer.java
@@ -4,8 +4,8 @@
import org.opentripplanner.routing.graph.Graph;
-import com.vividsolutions.jts.geom.Envelope;
-import com.vividsolutions.jts.geom.util.AffineTransformation;
+import org.locationtech.jts.geom.Envelope;
+import org.locationtech.jts.geom.util.AffineTransformation;
/**
* Interface for a slippy map tile renderer.
diff --git a/src/main/java/org/opentripplanner/inspector/TileRendererManager.java b/src/main/java/org/opentripplanner/inspector/TileRendererManager.java
index c08b7939431..3d27a21cb49 100644
--- a/src/main/java/org/opentripplanner/inspector/TileRendererManager.java
+++ b/src/main/java/org/opentripplanner/inspector/TileRendererManager.java
@@ -13,8 +13,8 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import com.vividsolutions.jts.geom.Envelope;
-import com.vividsolutions.jts.geom.util.AffineTransformation;
+import org.locationtech.jts.geom.Envelope;
+import org.locationtech.jts.geom.util.AffineTransformation;
/**
* Process slippy map tile rendering requests. Get the tile renderer for the given layer, setup a
diff --git a/src/main/java/org/opentripplanner/internals/AnalysisUtils.java b/src/main/java/org/opentripplanner/internals/AnalysisUtils.java
index 27395796c2e..5be2bc5693e 100644
--- a/src/main/java/org/opentripplanner/internals/AnalysisUtils.java
+++ b/src/main/java/org/opentripplanner/internals/AnalysisUtils.java
@@ -1,11 +1,11 @@
package org.opentripplanner.internals;
import com.google.common.collect.LinkedListMultimap;
-import com.vividsolutions.jts.algorithm.ConvexHull;
-import com.vividsolutions.jts.geom.Coordinate;
-import com.vividsolutions.jts.geom.Geometry;
-import com.vividsolutions.jts.geom.LineString;
-import com.vividsolutions.jts.geom.Point;
+import org.locationtech.jts.algorithm.ConvexHull;
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.Geometry;
+import org.locationtech.jts.geom.LineString;
+import org.locationtech.jts.geom.Point;
import org.opentripplanner.common.DisjointSet;
import org.opentripplanner.common.geometry.GeometryUtils;
import org.opentripplanner.routing.core.RoutingRequest;
diff --git a/src/main/java/org/opentripplanner/kryo/HashBiMapSerializer.java b/src/main/java/org/opentripplanner/kryo/HashBiMapSerializer.java
new file mode 100644
index 00000000000..0b95c7534aa
--- /dev/null
+++ b/src/main/java/org/opentripplanner/kryo/HashBiMapSerializer.java
@@ -0,0 +1,19 @@
+package org.opentripplanner.kryo;
+
+import com.esotericsoftware.kryo.Kryo;
+import com.esotericsoftware.kryo.io.Input;
+import com.esotericsoftware.kryo.serializers.MapSerializer;
+import com.google.common.collect.HashBiMap;
+
+import java.util.Map;
+
+/**
+ * Created by abyrd on 2018-10-25
+ */
+public class HashBiMapSerializer extends MapSerializer {
+
+ protected Map create(Kryo kryo, Input input, Class