@opentripplanner/vehicle-rental-overlay 1.4.3 → 2.0.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,284 +0,0 @@
1
- // Removed as core-utils is typescripted. TODO: Remove when typescripting!
2
- /* eslint-disable react/forbid-prop-types */
3
- import BaseMap from "@opentripplanner/base-map";
4
- import PropTypes from "prop-types";
5
- import React from "react";
6
- import { CircleMarker } from "react-leaflet";
7
- import { action } from "@storybook/addon-actions";
8
- import { boolean } from "@storybook/addon-knobs";
9
-
10
- import VehicleRentalOverlay from ".";
11
- import bikeRentalStations from "../__mocks__/bike-rental-stations.json";
12
- import carRentalStations from "../__mocks__/car-rental-stations.json";
13
- import eScooterStations from "../__mocks__/e-scooter-rental-stations.json";
14
- import { HubAndFloatingBike } from "./DefaultMarkers";
15
-
16
- import "../../../node_modules/leaflet/dist/leaflet.css";
17
-
18
- const center = [45.518092, -122.671202];
19
-
20
- /**
21
- * Creates an example Circle component to render entities
22
- * using a fixed size, fill color, and stroke color.
23
- */
24
- const MyCircle = ({ fillColor = "gray", pixels, strokeColor }) => {
25
- const newStrokeColor = strokeColor || fillColor;
26
-
27
- const GeneratedCircle = ({ children, entity: station }) => (
28
- <CircleMarker
29
- center={[station.y, station.x]}
30
- color={newStrokeColor}
31
- fillColor={fillColor}
32
- fillOpacity={1}
33
- radius={pixels}
34
- weight={1}
35
- >
36
- {children}
37
- </CircleMarker>
38
- );
39
- GeneratedCircle.propTypes = {
40
- children: PropTypes.node,
41
- // entity: coreUtils.types.stationType.isRequired
42
- entity: PropTypes.object.isRequired
43
- };
44
- GeneratedCircle.defaultProps = {
45
- children: null
46
- };
47
- return GeneratedCircle;
48
- };
49
-
50
- const bikeMapSymbols = [
51
- {
52
- dockStrokeColor: "#000000",
53
- fillColor: "#FF2E28",
54
- minZoom: 0,
55
- pixels: 4,
56
- type: "circle"
57
- },
58
- {
59
- dockStrokeColor: "#000000",
60
- fillColor: "#FF2E28",
61
- minZoom: 14,
62
- pixels: 6,
63
- type: "circle"
64
- },
65
- {
66
- minZoom: 18,
67
- type: "hubAndFloatingBike"
68
- }
69
- ];
70
- // Bike symbols using new symbols prop.
71
- const bikeSymbols = [
72
- {
73
- getType: station => (station.isFloatingBike ? "floatingBike" : "dock"),
74
- minZoom: 0,
75
- symbol: MyCircle({ fillColor: "#FF2E28", pixels: 3 }),
76
- symbolByType: {
77
- dock: MyCircle({
78
- fillColor: "#FF2E28",
79
- pixels: 4,
80
- strokeColor: "#000000"
81
- })
82
- }
83
- },
84
- {
85
- getType: station => (station.isFloatingBike ? "floatingBike" : "dock"),
86
- minZoom: 14,
87
- symbol: MyCircle({ fillColor: "#FF2E28", pixels: 5 }),
88
- symbolByType: {
89
- dock: MyCircle({
90
- fillColor: "#FF2E28",
91
- pixels: 6,
92
- strokeColor: "#000000"
93
- })
94
- }
95
- },
96
- {
97
- minZoom: 18,
98
- symbol: HubAndFloatingBike
99
- }
100
- ];
101
- const carMapSymbols = [
102
- {
103
- fillColor: "#009cde",
104
- minZoom: 0,
105
- pixels: 4,
106
- type: "circle"
107
- },
108
- {
109
- fillColor: "#009cde",
110
- minZoom: 14,
111
- pixels: 6,
112
- type: "circle"
113
- },
114
- {
115
- fillColor: "#009cde",
116
- minZoom: 18,
117
- type: "marker"
118
- }
119
- ];
120
- const configCompanies = [
121
- {
122
- id: "BIKETOWN",
123
- label: "Biketown",
124
- modes: "BICYCLE_RENT"
125
- },
126
- {
127
- id: "CAR2GO",
128
- label: "car2go",
129
- modes: "CAR_RENT"
130
- },
131
- {
132
- id: "RAZOR",
133
- label: "Razor",
134
- modes: "MICROMOBILITY_RENT"
135
- },
136
- {
137
- id: "SHARED",
138
- label: "Shared",
139
- modes: "MICROMOBILITY_RENT"
140
- }
141
- ];
142
- const EScooterMapSymbols = [
143
- {
144
- fillColor: "#F80600",
145
- minZoom: 0,
146
- pixels: 4,
147
- strokeColor: "#CCCCCC",
148
- type: "circle"
149
- },
150
- // You can combine predefined symbols (type = "<type>")
151
- // and external symbols (symbol = Component<({ entity, zoom })>.
152
- // (the color and pixel properties are ignored if you use the symbol syntax.).
153
- {
154
- minZoom: 14,
155
- symbol: MyCircle({
156
- fillColor: "#F80600",
157
- pixels: 6,
158
- strokeColor: "#CCCCCC"
159
- })
160
- },
161
- {
162
- fillColor: "#F80600",
163
- minZoom: 18,
164
- type: "marker"
165
- }
166
- ];
167
- const setLocation = action("setLocation");
168
-
169
- const INITIAL_ZOOM = 13;
170
-
171
- const ZoomControlledMapWithVehicleRentalOverlay = ({
172
- companies,
173
- getStationName,
174
- mapSymbols,
175
- refreshVehicles,
176
- stations,
177
- visible
178
- }) => (
179
- // Caution, <BaseMap> must be a direct parent of <VehicleRentalOverlay>.
180
- // Therefore, do not place <BaseMap> in a decorator at this time.
181
- <BaseMap center={center} zoom={INITIAL_ZOOM}>
182
- <VehicleRentalOverlay
183
- configCompanies={configCompanies}
184
- companies={companies}
185
- getStationName={getStationName}
186
- setLocation={setLocation}
187
- mapSymbols={mapSymbols}
188
- name="Rentals"
189
- refreshVehicles={refreshVehicles}
190
- stations={stations}
191
- visible={visible}
192
- />
193
- </BaseMap>
194
- );
195
-
196
- ZoomControlledMapWithVehicleRentalOverlay.propTypes = {
197
- companies: PropTypes.arrayOf(PropTypes.string.isRequired),
198
- getStationName: PropTypes.func,
199
- // mapSymbols: coreUtils.types.vehicleRentalMapOverlaySymbolsType,
200
- mapSymbols: PropTypes.object,
201
- refreshVehicles: PropTypes.func.isRequired,
202
- // stations: PropTypes.arrayOf(coreUtils.types.stationType.isRequired)
203
- // .isRequired,
204
- stations: PropTypes.arrayOf(PropTypes.object.isRequired).isRequired,
205
- visible: PropTypes.bool
206
- };
207
-
208
- ZoomControlledMapWithVehicleRentalOverlay.defaultProps = {
209
- companies: null,
210
- getStationName: undefined,
211
- mapSymbols: null,
212
- visible: true
213
- };
214
-
215
- function customStationName(_, station) {
216
- return `🛴 (ID: ${station.id})`;
217
- }
218
-
219
- export default {
220
- title: "VehicleRentalOverlay",
221
- component: VehicleRentalOverlay
222
- };
223
-
224
- export const RentalBicycles = () => (
225
- <ZoomControlledMapWithVehicleRentalOverlay
226
- companies={["BIKETOWN"]}
227
- mapSymbols={bikeMapSymbols}
228
- refreshVehicles={action("refresh bicycles")}
229
- stations={bikeRentalStations}
230
- />
231
- );
232
-
233
- export const RentalBicyclesVisibilityControlledByKnob = () => {
234
- const isOverlayVisible = boolean(
235
- "Toggle visibility of vehicle rental overlay",
236
- false
237
- );
238
- return (
239
- <ZoomControlledMapWithVehicleRentalOverlay
240
- companies={["BIKETOWN"]}
241
- mapSymbols={bikeMapSymbols}
242
- refreshVehicles={action("refresh bicycles")}
243
- stations={bikeRentalStations}
244
- visible={isOverlayVisible}
245
- />
246
- );
247
- };
248
-
249
- export const RentalBicyclesUsingNewSymbolsProp = () => (
250
- <ZoomControlledMapWithVehicleRentalOverlay
251
- companies={["BIKETOWN"]}
252
- refreshVehicles={action("refresh bicycles")}
253
- mapSymbols={bikeSymbols}
254
- stations={bikeRentalStations}
255
- />
256
- );
257
-
258
- export const RentalCars = () => (
259
- <ZoomControlledMapWithVehicleRentalOverlay
260
- companies={["CAR2GO"]}
261
- mapSymbols={carMapSymbols}
262
- refreshVehicles={action("refresh cars")}
263
- stations={carRentalStations}
264
- />
265
- );
266
-
267
- export const RentalEScooters = () => (
268
- <ZoomControlledMapWithVehicleRentalOverlay
269
- companies={["SHARED"]}
270
- mapSymbols={EScooterMapSymbols}
271
- refreshVehicles={action("refresh E-scooters")}
272
- stations={eScooterStations}
273
- />
274
- );
275
-
276
- export const RentalEScootersWithCustomNaming = () => (
277
- <ZoomControlledMapWithVehicleRentalOverlay
278
- companies={["SHARED"]}
279
- getStationName={customStationName}
280
- mapSymbols={EScooterMapSymbols}
281
- refreshVehicles={action("refresh E-scooters")}
282
- stations={eScooterStations}
283
- />
284
- );
package/src/bike-icons.js DELETED
@@ -1,23 +0,0 @@
1
- import { divIcon } from "leaflet";
2
- import React from "react";
3
- import ReactDOMServer from "react-dom/server";
4
-
5
- import * as S from "./styled";
6
-
7
- export const floatingBikeIcon = divIcon({
8
- iconSize: [24, 24],
9
- iconAnchor: [12, 24],
10
- popupAnchor: [0, -12],
11
- html: ReactDOMServer.renderToStaticMarkup(<S.OutOfHubBikeIcon />),
12
- className: ""
13
- });
14
-
15
- export const hubIcons = S.hubIcons.map(StyledIcon =>
16
- divIcon({
17
- iconSize: [24, 24],
18
- iconAnchor: [12, 24],
19
- popupAnchor: [0, -12],
20
- html: ReactDOMServer.renderToStaticMarkup(<StyledIcon />),
21
- className: ""
22
- })
23
- );
package/src/index.js DELETED
@@ -1,377 +0,0 @@
1
- import flatten from "flat";
2
- import { Styled as BaseMapStyled } from "@opentripplanner/base-map";
3
- import coreUtils from "@opentripplanner/core-utils";
4
- import FromToLocationPicker from "@opentripplanner/from-to-location-picker";
5
- import ZoomBasedMarkers from "@opentripplanner/zoom-based-markers";
6
- import PropTypes from "prop-types";
7
- import React from "react";
8
- import { FormattedMessage, injectIntl } from "react-intl";
9
- import { FeatureGroup, MapLayer, Popup, withLeaflet } from "react-leaflet";
10
-
11
- import {
12
- GenericMarker,
13
- HubAndFloatingBike,
14
- SharedBikeCircle
15
- } from "./DefaultMarkers";
16
-
17
- // Load the default messages.
18
- import defaultEnglishMessages from "../i18n/en-US.yml";
19
-
20
- // HACK: We should flatten the messages loaded above because
21
- // the YAML loaders behave differently between webpack and our version of jest:
22
- // - the yaml loader for webpack returns a nested object,
23
- // - the yaml loader for jest returns messages with flattened ids.
24
- const defaultMessages = flatten(defaultEnglishMessages);
25
-
26
- function makeDefaultGetStationName(intl) {
27
- return function defaultGetStationName(configCompanies, station) {
28
- const stationNetworks = coreUtils.itinerary.getCompaniesLabelFromNetworks(
29
- station.networks,
30
- configCompanies
31
- );
32
- let stationName = station.name || station.id;
33
- // If the station name or id is a giant UUID (with more than 3 "-" characters)
34
- // best not to show that at all and instead use the network name.
35
- if ((stationName.match(/-/g) || []).length > 3) {
36
- stationName = stationNetworks;
37
- }
38
-
39
- if (station.isFloatingBike) {
40
- stationName = intl.formatMessage(
41
- {
42
- defaultMessage:
43
- defaultEnglishMessages["otpUi.VehicleRentalOverlay.floatingBike"],
44
- description: "Popup title for a free-floating bike",
45
- id: "otpUi.VehicleRentalOverlay.floatingBike"
46
- },
47
- { name: stationName }
48
- );
49
- } else if (station.isFloatingCar) {
50
- stationName = intl.formatMessage(
51
- {
52
- defaultMessage:
53
- defaultEnglishMessages["otpUi.VehicleRentalOverlay.floatingCar"],
54
- description: "Popup title for a free-floating car",
55
- id: "otpUi.VehicleRentalOverlay.floatingCar"
56
- },
57
- {
58
- company: stationNetworks,
59
- name: stationName
60
- }
61
- );
62
- } else if (station.isFloatingVehicle) {
63
- // assumes that all floating vehicles are E-scooters
64
- stationName = intl.formatMessage(
65
- {
66
- defaultMessage:
67
- defaultEnglishMessages[
68
- "otpUi.VehicleRentalOverlay.floatingEScooter"
69
- ],
70
- description: "Popup title for a free-floating e-scooter",
71
- id: "otpUi.VehicleRentalOverlay.floatingEScooter"
72
- },
73
- { company: stationNetworks }
74
- );
75
- }
76
- return stationName;
77
- };
78
- }
79
-
80
- /**
81
- * This vehicle rental overlay can be used to render vehicle rentals of various
82
- * types. This layer can be configured to show different styles of markers at
83
- * different zoom levels.
84
- */
85
- class VehicleRentalOverlay extends MapLayer {
86
- constructor(props) {
87
- super(props);
88
- this.state = {
89
- zoom: null
90
- };
91
- }
92
-
93
- /**
94
- * This helper method will be passed to the ZoomBasedMarkers symbolTransform prop.
95
- * It creates a component that inserts a popup
96
- * as a child of the specified symbol from the mapSymbols prop.
97
- */
98
- renderSymbolWithPopup = Symbol => {
99
- const SymbolWrapper = ({ entity: station, zoom }) => (
100
- <Symbol entity={station} zoom={zoom}>
101
- {this.renderPopupForStation(
102
- station,
103
- station.bikesAvailable !== undefined && !station.isFloatingBike
104
- )}
105
- </Symbol>
106
- );
107
- SymbolWrapper.propTypes = {
108
- // entity: coreUtils.types.stationType.isRequired,
109
- zoom: PropTypes.number.isRequired
110
- };
111
-
112
- return SymbolWrapper;
113
- };
114
-
115
- /**
116
- * Convert map symbols to zoomBasedSymbolType.
117
- */
118
- convertToZoomMarkerSymbols = mapSymbols =>
119
- mapSymbols.map(mapSymbol => {
120
- // If mapSymbol uses zoomBasedSymbolType, use it as is.
121
- if (mapSymbol.symbol) {
122
- return mapSymbol;
123
- }
124
-
125
- // Otherwise, convert into zoomBasedType (no support for symbols by type).
126
- let symbol;
127
- switch (mapSymbol.type) {
128
- case "circle":
129
- symbol = SharedBikeCircle(mapSymbol);
130
- break;
131
- case "hubAndFloatingBike":
132
- symbol = HubAndFloatingBike;
133
- break;
134
- default:
135
- symbol = GenericMarker(mapSymbol);
136
- }
137
-
138
- return {
139
- minZoom: mapSymbol.minZoom,
140
- symbol
141
- };
142
- });
143
-
144
- createLeafletElement() {}
145
-
146
- updateLeafletElement() {}
147
-
148
- startRefreshing() {
149
- const { refreshVehicles } = this.props;
150
-
151
- // Create the timer only if refreshVehicles is a valid function.
152
- if (typeof refreshVehicles === "function") {
153
- // initial station retrieval
154
- refreshVehicles();
155
-
156
- // set up timer to refresh stations periodically
157
- this.refreshTimer = setInterval(() => {
158
- refreshVehicles();
159
- }, 30000); // defaults to every 30 sec. TODO: make this configurable?
160
- }
161
- }
162
-
163
- stopRefreshing() {
164
- if (this.refreshTimer) clearInterval(this.refreshTimer);
165
- }
166
-
167
- /**
168
- * When the layer is added (or toggled on, or its visibility becomes true),
169
- * start refreshing vehicle positions.
170
- */
171
- onOverlayAdded = () => {
172
- this.startRefreshing();
173
- };
174
-
175
- /**
176
- * When the layer is removed (or toggled off, or its visibility becomes false),
177
- * stop refreshing vehicle positions.
178
- */
179
- onOverlayRemoved = () => {
180
- this.stopRefreshing();
181
- };
182
-
183
- /**
184
- * Listen to changes on the BaseMap's center or zoom.
185
- * @param viewport The viewport data. See https://github.com/PaulLeCam/react-leaflet/blob/master/example/components/viewport.js for details.
186
- */
187
- onViewportChanged = viewport => {
188
- const { zoom: newZoom } = viewport;
189
- if (this.state.zoom !== newZoom) {
190
- this.setState({ zoom: newZoom });
191
- }
192
- };
193
-
194
- /**
195
- * Upon mounting, see whether the vehicles should be fetched,
196
- * and also call the register overlay prop that the
197
- * @opentripplanner/base-map package has injected to listen to zoom/position changes.
198
- */
199
- componentDidMount() {
200
- const { leaflet, registerOverlay, visible } = this.props;
201
- this.setState({
202
- zoom: leaflet.map.getZoom()
203
- });
204
- if (visible) this.startRefreshing();
205
- if (typeof registerOverlay === "function") {
206
- registerOverlay(this);
207
- }
208
- }
209
-
210
- componentWillUnmount() {
211
- this.stopRefreshing();
212
- }
213
-
214
- /**
215
- * Render some popup html for a station. This contains custom logic for
216
- * displaying rental vehicles in the TriMet MOD website that might not be
217
- * applicable to other regions.
218
- */
219
- renderPopupForStation = (station, stationIsHub = false) => {
220
- const { configCompanies, getStationName, intl, setLocation } = this.props;
221
- const getStationNameFunc =
222
- getStationName || makeDefaultGetStationName(intl);
223
- const stationName = getStationNameFunc(configCompanies, station);
224
- const location = {
225
- lat: station.y,
226
- lon: station.x,
227
- name: stationName
228
- };
229
- return (
230
- <Popup>
231
- <BaseMapStyled.MapOverlayPopup>
232
- <BaseMapStyled.PopupTitle>{stationName}</BaseMapStyled.PopupTitle>
233
-
234
- {/* render dock info if it is available */}
235
- {stationIsHub && (
236
- <BaseMapStyled.PopupRow>
237
- <div>
238
- <FormattedMessage
239
- defaultMessage={
240
- defaultMessages["otpUi.VehicleRentalOverlay.availableBikes"]
241
- }
242
- description="Label text for the number of bikes available"
243
- id="otpUi.VehicleRentalOverlay.availableBikes"
244
- values={{ value: station.bikesAvailable }}
245
- />
246
- </div>
247
- <div>
248
- <FormattedMessage
249
- defaultMessage={
250
- defaultMessages["otpUi.VehicleRentalOverlay.availableDocks"]
251
- }
252
- description="Label text for the number of docks available"
253
- id="otpUi.VehicleRentalOverlay.availableDocks"
254
- values={{ value: station.spacesAvailable }}
255
- />
256
- </div>
257
- </BaseMapStyled.PopupRow>
258
- )}
259
-
260
- {/* Set as from/to toolbar */}
261
- <BaseMapStyled.PopupRow>
262
- <FromToLocationPicker
263
- label
264
- location={location}
265
- setLocation={setLocation}
266
- />
267
- </BaseMapStyled.PopupRow>
268
- </BaseMapStyled.MapOverlayPopup>
269
- </Popup>
270
- );
271
- };
272
-
273
- render() {
274
- const { companies, leaflet, mapSymbols, stations } = this.props;
275
- const { zoom = leaflet.map.getZoom() } = this.state;
276
- // Render an empty FeatureGroup if the rental vehicles should not be visible
277
- // on the map. Otherwise previous stations may still be shown due to some
278
- // react-leaflet internals, maybe? Also, do not return null because that will
279
- // prevent the overlay from appearing in the layer controls.
280
-
281
- let filteredStations = stations;
282
- if (companies) {
283
- filteredStations = stations.filter(
284
- station =>
285
- station.networks.filter(value => companies.includes(value)).length > 0
286
- );
287
- }
288
-
289
- if (!filteredStations || filteredStations.length === 0) {
290
- return <FeatureGroup />;
291
- }
292
-
293
- // Convert map symbols for this overlay to zoomBasedSymbolType.
294
- const symbols = this.convertToZoomMarkerSymbols(mapSymbols);
295
-
296
- return (
297
- <FeatureGroup>
298
- <ZoomBasedMarkers
299
- entities={filteredStations}
300
- symbols={symbols}
301
- symbolTransform={this.renderSymbolWithPopup}
302
- zoom={zoom}
303
- />
304
- </FeatureGroup>
305
- );
306
- }
307
- }
308
-
309
- VehicleRentalOverlay.props = {
310
- /**
311
- * The entire companies config array.
312
- */
313
- // configCompanies: PropTypes.arrayOf(coreUtils.types.companyType.isRequired)
314
- // .isRequired,
315
- /**
316
- * A list of companies that are applicable to just this instance of the
317
- * overlay.
318
- */
319
- companies: PropTypes.arrayOf(PropTypes.string.isRequired),
320
- /**
321
- * An optional custom function to create a string name of a particular vehicle
322
- * rental station. This function takes two arguments of the configCompanies
323
- * prop and a vehicle rental station. The function must return a string.
324
- */
325
- getStationName: PropTypes.func,
326
- /**
327
- * A configuration of what map markers or symbols to show at various
328
- * zoom levels.
329
- */
330
- // mapSymbols: coreUtils.types.vehicleRentalMapOverlaySymbolsType,
331
- /**
332
- * If specified, a function that will be triggered every 30 seconds whenever this layer is
333
- * visible.
334
- */
335
- refreshVehicles: PropTypes.func,
336
- /**
337
- * A callback for when a user clicks on setting this stop as either the from
338
- * or to location of a new search.
339
- *
340
- * This will be dispatched with the following argument:
341
- *
342
- * ```js
343
- * {
344
- * location: {
345
- * lat: number,
346
- * lon: number,
347
- * name: string
348
- * },
349
- * locationType: "from" or "to"
350
- * }
351
- * ```
352
- */
353
- setLocation: PropTypes.func.isRequired,
354
- /**
355
- * A list of the vehicle rental stations specific to this overlay instance.
356
- */
357
- // stations: PropTypes.arrayOf(coreUtils.types.stationType),
358
- /**
359
- * Whether the overlay is currently visible.
360
- */
361
- visible: PropTypes.bool
362
- };
363
-
364
- VehicleRentalOverlay.defaultProps = {
365
- getStationName: null,
366
- mapSymbols: [
367
- {
368
- zoom: 0,
369
- symbol: GenericMarker
370
- }
371
- ],
372
- refreshVehicles: null,
373
- stations: [],
374
- visible: false
375
- };
376
-
377
- export default withLeaflet(injectIntl(VehicleRentalOverlay));