Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Publish web SDK #189

Merged
merged 8 commits into from
Aug 25, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,23 +84,23 @@ if you don't.

### Web

Perform all commands unless otherwise noted from the `web` directory.

1. Install `wasm-pack`:

```shell
cargo install wasm-pack
```

2. Build the NPM package of the core:
2. Build the WASM package for the core:

```shell
cd common
wasm-pack build --target web ferrostar --no-default-features --features wasm_js
npm run prepare:core
```

3. Install dependencies:

```shell
cd ../web
npm install
```

Expand Down
1 change: 1 addition & 0 deletions guide/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
- [Navigation Behavior](./configuring-the-navigation-controller.md)
- [SwiftUI](./swiftui-customization.md)
- [Jetpack Compose](./jetpack-compose-customization.md)
- [Web](./web-customization.md)

# Architecture

Expand Down
91 changes: 91 additions & 0 deletions guide/src/web-customization.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# Web

The [web tutorial](./web-getting-started.md) gets you set up with a “batteries included” UI
and sane defaults (if a bit customized for Stadia Maps at the moment).
This document covers ways you can customize it to your needs.

## Removing or replacing the integrated search box

If you want to use your own code to handle navigation instead of the integrated search box, you can do so by the following steps:

### Disable the integrated search box

You can disable the integrated search box with the `useIntegratedSearchBox` attribute.

```html
<ferrostar-core
id="core"
valhallaEndpointUrl="https://api.stadiamaps.com/route/v1"
styleUrl="https://tiles.stadiamaps.com/styles/outdoors.json"
profile="bicycle"
useIntegratedSearchBox="false"
></ferrostar-core>
```

### Use your own search box/geocoding API

The HTML, JS, and CSS for this is out of scope of this guide,
but here’s an example (without UI)
showing how to retrieve the latitude and longitude of a destination
using the Nominatim API ([note their usage policy](https://operations.osmfoundation.org/policies/nominatim/) before deploying):

```javascript
const destination = "One Apple Park Way";

const { lat, lon } = await fetch("https://nominatim.openstreetmap.org/search?q=" + destination + "&format=json")
.then((response) => response.json())
.then((data) => data[0]);
```

### Get routes manually

Once you have your waypoint(s) geocoded,
create a list of them like this:

```javascript
const waypoints = [{ coordinate: { lat: parseFloat(lat), lng: parseFloat(lon) }, kind: "Break" }];
```

The asynchronous `getRoutes` method on `FerrostarCore`
will fetch routes from your route provider (ex: a Valhalla server).
Here’s an example:

```javascript
const core = document.getElementById("core");
const routes = await core.getRoutes(locationProvider.lastLocation, waypoints);
const route = routes[0];
```

### Starting navigation manually

Once you have a route,
it’s time to start navigating!

```javascript
core.startNavigation(route, config);
```

## Location providers

The “batteries include” defaults will use the web Geolocation API automatically.
However, you can override this for simulation purposes.

### `BrowserLocationProvider`

`BrowserLocationProvider` is a location provider that uses the browser's geolocation API.

```javascript
// Request location permission and start location updates
const locationProvider = new BrowserLocationProvider();
locationProvider.requestPermission();
locationProvider.start();

// TODO: This approach is not ideal, any better way to wait for the locationProvider to acquire the first location?
while (!locationProvider.lastLocation) {
await new Promise((resolve) => setTimeout(resolve, 100));
}
```

### `SimulatedLocationProvider`

TODO: Documentation
156 changes: 51 additions & 105 deletions guide/src/web-getting-started.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
# Getting Started on the Web

This section of the guide covers how to integrate Ferrostar into a web app.
While there are limitations to the web [Geolocation API](https://developer.mozilla.org/en-US/docs/Web/API/Geolocation_API)
(notably no background updates),
PWAs and other mobile-optimized sites
can be a great solution when a native iOS/Android app is impractical or prohibitively expensive.

We'll cover the "batteries included" approach, but flag areas for customization and overrides along the way.

## Add the NPM package dependency
## Add the package dependency

### Installing with `npm`

NOTE: Currently you need to build the package locally.
We intend to publish to npmjs.com very soon.

In your web app, you can add the Ferrostar NPM package as a dependency.
You will need to install [Rust](https://www.rust-lang.org/) and `wasm-pack` to build the NPM package.
Expand All @@ -12,45 +22,53 @@ You will need to install [Rust](https://www.rust-lang.org/) and `wasm-pack` to b
cargo install wasm-pack
```

Then, in your web app, install the Ferrostar NPM package:
Head to the local path where you have checked out Ferrostar,
go to the `web` directory, and build the module:

```shell
npm install && npm run build
```

Then, in your project, install the Ferrostar package using the local path:

```shell
npm install /path/to/ferrostar/web
```

## Add Ferrostar web components to your app
### Using unpkg

TODO after publishing to npm.

## Add Ferrostar web components to your web app

Ferrostar web SDK is provided as web components.
To import the components, add the following line:
The Ferrostar web SDK uses the [Web Components](https://developer.mozilla.org/en-US/docs/Web/API/Web_components)
to ensure maximum compatibility across frontend frameworks.
You can import the components just like other things you’re used to in JavaScript.

```javascript
import { FerrostarCore, BrowserLocationProvider } from "ferrostar-components";
```

### Route providers

You’ll need to decide on a route provider when you set up your `FerrostarCore` instance.
For limited testing, FOSSGIS maintains a public server with the URL `https://valhalla1.openstreetmap.de/route`.
For production use, you’ll need another solution like a [commercial vendor](./vendors.md)
or self-hosting.

### Map style providers

You can get a free Stadia Maps API key at https://client.stadiamaps.com
See https://stadiamaps.github.io/ferrostar/vendors.html for additional vendors.
## Configure the `<ferrostar-core>` component

Then, you can use the components in your HTML like this, for example:
Now you can use Ferrostar in your HTML like this:

```html
<ferrostar-core
id="core"
valhallaEndpointUrl="https://valhalla1.openstreetmap.de/route"
styleUrl="https://tiles.stadiamaps.com/styles/outdoors.json?api_key=YOUR_API_KEY"
valhallaEndpointUrl="https://api.stadiamaps.com/route/v1"
styleUrl="https://tiles.stadiamaps.com/styles/outdoors.json"
profile="bicycle"
></ferrostar-core>
```

NOTE: `<ferrostar-core>` requires setting CSS manually or it will be invisible.
Here we have used Stadia Maps URLs, which should work without authentication for local development.
(Refer to the [authentication docs](https://docs.stadiamaps.com/authentication/)
for network deployment details; you can start with a free account.)

See the [vendors appendix](./vendors.md) for a list of other compatible vendors.

`<ferrostar-core>` additionally requires setting some CSS manually, or it will be invisible!

```css
ferrostar-core {
Expand All @@ -60,100 +78,28 @@ ferrostar-core {
}
```

`<ferrostar-core>` web SDK contains an integrated search box, and you can already use it for navigation without any additional setup.
That’s all you need to get started!

## Configure the `<ferrostar-core>` component
### Configuration explained

`<ferrostar-core>` provides a few properties to configure.
Here are the most important ones:

- `httpClient`: You can set your own fetch-compatible HTTP client to make requests to the Valhalla endpoint.
- `costingOptions`: You can set the costing options for the Ferrostar routing engine.
- `useIntegratedSearchBox`: You can disable the integrated search box and use your own code to handle navigation.

## Use your own code to handle navigation

If you want to use your own code to handle navigation instead of the integrated search box, you can do so by the following steps:

### (Optional) Implement your own search box

You can use this code to retrieve the latitude and longitude of a destination:

```javascript
const destination = "One Apple Park Way";

const { lat, lon } = await fetch("https://nominatim.openstreetmap.org/search?q=" + destination + "&format=json")
.then((response) => response.json())
.then((data) => data[0]);
```
- `valhallaEndpointUrl`: The Valhalla routing endpoint to use. You can use any reasonably up-to-date Valhalla server, including your own. See [vendors](./vendor.md#routing) for a list of known compatible vendors.
- `httpClient`: You can set your own fetch-compatible HTTP client to make requests to the routing API (ex: Valhalla).
- `costingOptions`: You can set the costing options for the route provider (ex: Valhalla JSON options).
- `useIntegratedSearchBox`: Ferrostar web includes a search box powered by Stadia Maps, but you can disable this and replace with your own.
ianthetechie marked this conversation as resolved.
Show resolved Hide resolved

### Configure the Ferrostar Core

(TODO)
Here's an example:

```javascript
const config = {
Copy link
Collaborator Author

@CatMe0w CatMe0w Aug 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This config object is required for core.startNavigation, but I'm not sure how to document it correctly. Also, should we include this as a default value for core.startNavigation in our web component code?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This config object is required for core.startNavigation

Actually we made a change very recently which moves the config requirement to the FerrostarCore constructor on other platforms (Android example). I think doing the same on the web would be reasonable too. Most of the time you want to just set a config once. This makes it an optional override when calling startNavigation.

Also, should we include this as a default value for core.startNavigation in our web component code?

Yeah, the above addresses this, but it should be an optional override with a default set on the FerrostarCore component. Somewhat related: the startNavigationFromSearch internal function currently just uses hard-coded values; storing the config at the component level can fix this.

Re: documentation, I think it's good enough for now as the most common path is clear. We can improve customization docs as we refine the design.

stepAdvance: {
RelativeLineStringDistance: {
minimumHorizontalAccuracy: 25,
automaticAdvanceDistance: 10,
},
},
routeDeviationTracking: {
StaticThreshold: {
minimumHorizontalAccuracy: 25,
maxAcceptableDeviation: 10.0,
},
},
};
```

### Getting a route

Before getting routes, you’ll need the user’s current location.
You can get this from the location provider.
`BrowserLocationProvider` is a location provider that uses the browser's geolocation API.

```javascript
// Request location permission and start location updates
const locationProvider = new BrowserLocationProvider();
locationProvider.requestPermission();
locationProvider.start();

// TODO: This approach is not ideal, any better way to wait for the locationProvider to acquire the first location?
while (!locationProvider.lastLocation) {
await new Promise((resolve) => setTimeout(resolve, 100));
}
```

Next, you’ll need a set of waypoints to visit.

```javascript
const waypoints = [{ coordinate: { lat: parseFloat(lat), lng: parseFloat(lon) }, kind: "Break" }];
```

Finally, you can use the asynchronous `getRoutes` method on `FerrostarCore`.
Here’s an example:

```javascript
const core = document.getElementById("core");
const routes = await core.getRoutes(locationProvider.lastLocation, waypoints);
const route = routes[0];
```

### Start the navigation

Once you or the user has selected a route, it’s time to start navigating!

```javascript
core.locationProvider = locationProvider;
core.startNavigation(route, config);
```
NOTE: The JavaScript API is currently limited to Valhalla,
but support for arbitrary providers (like we already have on iOS and Android)
is [tracked in this issue](https://github.com/stadiamaps/ferrostar/issues/191).

## Demo app

We've put together a minimal [demo app](https://github.com/stadiamaps/ferrostar/tree/main/web/index.html) with an example integration.
We've put together a minimal demo app with an example integration.
Check out the [source code](https://github.com/stadiamaps/ferrostar/tree/main/web/index.html)
or try the [hosted demo](https://stadiamaps.github.io/ferrostar/web-demo)
(works best from a phone if you want to use real geolocation).

## Going deeper

Expand Down