diff --git a/packages/controlled-vocabulary/package.json b/packages/controlled-vocabulary/package.json index ceee31ec..1ac3016e 100644 --- a/packages/controlled-vocabulary/package.json +++ b/packages/controlled-vocabulary/package.json @@ -1,6 +1,6 @@ { "name": "@performant-software/controlled-vocabulary", - "version": "1.1.2", + "version": "1.1.3", "description": "A package of components to allow user to configure dropdown elements. Use with the \"controlled_vocabulary\" gem.", "license": "MIT", "main": "./build/index.js", @@ -12,8 +12,8 @@ "build": "webpack --mode production && flow-copy-source -v src types" }, "dependencies": { - "@performant-software/semantic-components": "^1.1.2", - "@performant-software/shared-components": "^1.1.2", + "@performant-software/semantic-components": "^1.1.3", + "@performant-software/shared-components": "^1.1.3", "i18next": "^21.9.2", "semantic-ui-react": "^2.1.2", "underscore": "^1.13.2" diff --git a/packages/geospatial/package.json b/packages/geospatial/package.json index 2a9f194c..e37e47ea 100644 --- a/packages/geospatial/package.json +++ b/packages/geospatial/package.json @@ -1,6 +1,6 @@ { "name": "@performant-software/geospatial", - "version": "1.1.2", + "version": "1.1.3", "description": "TODO: ADD", "license": "MIT", "main": "./build/index.js", @@ -12,7 +12,8 @@ "@mapbox/mapbox-gl-draw": "^1.4.3", "@turf/turf": "^6.5.0", "mapbox-gl": "npm:empty-npm-package@1.0.0", - "maplibre-gl": "^3.5.2", + "maplibre-gl": "^3.6.2", + "react-icons": "^5.0.1", "react-map-gl": "^7.1.6", "underscore": "^1.13.6" }, diff --git a/packages/geospatial/src/components/GeoJsonLayer.js b/packages/geospatial/src/components/GeoJsonLayer.js new file mode 100644 index 00000000..f2929748 --- /dev/null +++ b/packages/geospatial/src/components/GeoJsonLayer.js @@ -0,0 +1,118 @@ +// @flow + +import React, { + useCallback, + useEffect, + useMemo, + useState +} from 'react'; +import { Layer, Source } from 'react-map-gl'; +import _ from 'underscore'; + +type Props = { + data?: { [key: string]: any }, + fillStyle?: { [key: string]: any }, + lineStyle?: { [key: string]: any }, + pointStyle?: { [key: string]: any }, + url?: string +}; + +const DEFAULT_COLOR = '#CC3333'; +const HIGHLIGHT_COLOR = '#990000'; + +const DEFAULT_FILL_STYLES = { + 'fill-color': DEFAULT_COLOR, + 'fill-opacity': 0.2 +}; + +const DEFAULT_LINE_STYLES = { + 'line-color': HIGHLIGHT_COLOR, + 'line-opacity': 0.6 +}; + +const DEFAULT_POINT_STYLES = { + 'circle-radius': [ + 'interpolate', + ['linear'], + ['number', ['get', 'point_count'], 1], + 0, 4, + 10, 14 + ], + 'circle-stroke-width': 1, + 'circle-color': DEFAULT_COLOR, + 'circle-stroke-color': HIGHLIGHT_COLOR +}; + +const GeoJsonLayer = (props: Props) => { + const [data, setData] = useState(props.data); + + /** + * Returns the layer style for the passed style and default. + * + * @type {function(*, *): *} + */ + const getLayerStyles = useCallback((style, defaultStyle) => _.defaults(style, defaultStyle), []); + + /** + * Sets the fill layer style. + * + * @type {*} + */ + const fillStyle = useMemo(() => ( + getLayerStyles(props.fillStyle, DEFAULT_FILL_STYLES) + ), [getLayerStyles, props.fillStyle]); + + /** + * Sets the line layer style. + * + * @type {*} + */ + const lineStyle = useMemo(() => ( + getLayerStyles(props.lineStyle, DEFAULT_LINE_STYLES) + ), [getLayerStyles, props.lineStyle]); + + /** + * Sets the point layer style. + * + * @type {*} + */ + const pointStyle = useMemo(() => ( + getLayerStyles(props.pointStyle, DEFAULT_POINT_STYLES) + ), [getLayerStyles, props.pointStyle]); + + /** + * If the data is passed as a URL, fetches the passed URL and sets the response on the state. + */ + useEffect(() => { + if (props.url) { + fetch(props.url) + .then((response) => response.json()) + .then((json) => setData(json)); + } + }, [props.url]); + + return ( + + + + + + ); +}; + +export default GeoJsonLayer; diff --git a/packages/geospatial/src/components/LayerMenu.css b/packages/geospatial/src/components/LayerMenu.css new file mode 100644 index 00000000..859bb6f7 --- /dev/null +++ b/packages/geospatial/src/components/LayerMenu.css @@ -0,0 +1,38 @@ +.maplibregl-ctrl-group.maplibregl-ctrl button.layer-button.mapbox-gl-draw_ctrl-draw-btn { + display: flex; + align-items: center; + justify-content: center; + color: #000000; +} + +.maplibregl-ctrl-group.maplibregl-ctrl .layer-menu { + background-color: #FFFFFF; + border-radius: 4px; + box-shadow: rgba(0, 0, 0, 0.1) 0px 0px 0px 2px; + font-family: 'Lato', 'Helvetica Neue', Arial, Helvetica, sans-serif; + position: absolute; + top: 0px; + left: 40px; + overflow: auto; + width: max-content; +} + +.maplibregl-ctrl-group.maplibregl-ctrl .layer-menu > .menu > .option { + cursor: pointer; + padding: 0.7em 2em 0.7em 2em; + pointer-events: all; + display: flex; + align-items: center; +} + +.maplibregl-ctrl-group.maplibregl-ctrl .layer-menu > .menu > .option:hover { + background-color: rgba(0, 0, 0, .05); +} + +.maplibregl-ctrl-group.maplibregl-ctrl .layer-menu > .menu > .option > .checkmark-container { + color: #009E60; + display: flex; + align-items: center; + height: 100%; + width: 20px; +} \ No newline at end of file diff --git a/packages/geospatial/src/components/LayerMenu.js b/packages/geospatial/src/components/LayerMenu.js new file mode 100644 index 00000000..0dc717be --- /dev/null +++ b/packages/geospatial/src/components/LayerMenu.js @@ -0,0 +1,158 @@ +// @flow + +import React, { + Children, + useCallback, + useEffect, + useMemo, useRef, + useState +} from 'react'; +import { BsStack } from 'react-icons/bs'; +import { IoCheckmarkOutline } from 'react-icons/io5'; +import _ from 'underscore'; +import MapControl from './MapControl'; +import './LayerMenu.css'; + +type Props = { + children: Node, + names: Array, + position: 'top-left' | 'bottom-left' | 'top-right' | 'bottom-right' +}; + +const MENU_PADDING = 30; + +const LayerMenu = (props: Props) => { + const [canvasHeight, setCanvasHeight] = useState(0); + const [visible, setVisible] = useState(); + const [menuOpen, setMenuOpen] = useState(false); + + const mapRef = useRef(); + + /** + * Returns the name of the layer at the passed index. + * + * @type {unknown} + */ + const getLayerName = useCallback((index) => ( + props.names && props.names.length > index && props.names[index] + ), [props.names]); + + /** + * Returns true if the child element at the passed index is visible. + * + * @type {function(*): *} + */ + const isVisible = useCallback((index) => _.includes(visible, index), [visible]); + + /** + * Returns a memoized array of the child elements. + * + * @type {Array<$NonMaybeType>} + */ + const children = useMemo(() => Children.toArray(props.children), [props.children]); + + /** + * Returns a memoized array of visible child elements. + */ + const visibleChildren = useMemo(() => _.filter(children, (child, index) => isVisible(index)), [children, isVisible]); + + /** + * Toggles the visibility for the child element at the passed index. + * + * @type {(function(*): void)|*} + */ + const toggleVisibility = useCallback((index) => { + let value; + + if (isVisible(index)) { + value = _.without(visible, index); + } else { + value = [...visible, index]; + } + + setVisible(value); + }, [isVisible, visible]); + + /** + * Sets all of the child elements to be visible when the component mounts. + */ + useEffect(() => { + setVisible(_.times(children.length, (index) => index)); + }, []); + + /** + * Sets the map canvas height. + */ + useEffect(() => { + const { current: instance } = mapRef; + + if (instance && instance._canvas) { + const { offsetHeight = 0 } = mapRef.current._canvas; + setCanvasHeight(offsetHeight); + } + }, [mapRef.current]); + + if (_.isEmpty(children)) { + return null; + } + + return ( + <> + + setMenuOpen((prevMenuOpen) => !prevMenuOpen)} + type='button' + > + + + { menuOpen && ( + + + { _.map(children, (child, index) => ( + toggleVisibility(index)} + onKeyDown={() => toggleVisibility(index)} + tabIndex={index} + > + + { isVisible(index) && ( + + )} + + { getLayerName(index) } + + ))} + + + )} + + { visibleChildren } + > + ); +}; + +LayerMenu.defaultProps = { + position: 'top-left' +}; + +export default LayerMenu; diff --git a/packages/geospatial/src/components/MapControl.js b/packages/geospatial/src/components/MapControl.js new file mode 100644 index 00000000..69593091 --- /dev/null +++ b/packages/geospatial/src/components/MapControl.js @@ -0,0 +1,121 @@ +// @flow + +import { IControl } from 'maplibre-gl'; +import { + Children, + cloneElement, + useCallback, + useEffect, + useState +} from 'react'; +import { createPortal } from 'react-dom'; +import { MapboxMap, useControl } from 'react-map-gl'; +import _ from 'underscore'; + +/** + * Class to implement the IControl interface to allow custom controls to be drawn on the map. + */ +class IControlImpl implements IControl { + _map: MapboxMap = null; + _container: HTMLElement; + _redraw: () => void; + + /** + * Constructs a new IControlImpl object. + * + * @param redraw + */ + constructor(redraw: () => void) { + this._redraw = redraw; + } + + /** + * Creates the container when the component is added to the passed map. + * + * @param map + * + * @returns {HTMLElement} + */ + onAdd(map) { + this._map = map; + map.on('move', this._redraw); + + this._container = document.createElement('div'); + this._container.className = 'maplibregl-ctrl-group maplibregl-ctrl'; + this._redraw(); + return this._container; + } + + /** + * Removes the container when the component is removed from the map. + */ + onRemove() { + this._container.remove(); + this._map.off('move', this._redraw); + this._map = null; + } + + /** + * Returns the map instance. + * + * @returns {mapboxgl.Map} + */ + getMap() { + return this._map; + } + + /** + * Returns the container instance. + * + * @returns {HTMLElement} + */ + getElement() { + return this._container; + } +} + +const MapControl = (props) => { + const [, setVersion] = useState(0); + + /** + * Forces the component to re-render on map move. + * + * @type {function(): void} + */ + const forceUpdate = useCallback(() => setVersion((v) => v + 1), []); + + /** + * Creates the map control. + * + * @type {IControlImpl} + */ + const ctrl = useControl(() => new IControlImpl(forceUpdate), { position: props.position }); + + /** + * Sets the map instance. + * + * @type {mapboxgl.Map} + */ + const map = ctrl.getMap(); + + /** + * Creates the list of children to be added to the map instance. + * + * @type {Array<$NonMaybeType<*>>} + */ + const children = Children.map(_.compact(Children.toArray(props.children)), (child) => cloneElement(child, { map })); + + /** + * Sets the map ref. + */ + useEffect(() => { + if (props.mapRef) { + // eslint-disable-next-line no-param-reassign + props.mapRef.current = map; + } + }, [map, props.mapRef]); + + return map && createPortal(children, ctrl.getElement()); +}; + +export default MapControl; diff --git a/packages/geospatial/src/components/MapDraw.css b/packages/geospatial/src/components/MapDraw.css index a90c99bd..eae7435e 100644 --- a/packages/geospatial/src/components/MapDraw.css +++ b/packages/geospatial/src/components/MapDraw.css @@ -1,5 +1,2 @@ @import '@mapbox/mapbox-gl-draw/dist/mapbox-gl-draw.css'; - -div[mapboxgl-children] { - height: unset !important; -} +@import 'maplibre-gl/dist/maplibre-gl.css'; \ No newline at end of file diff --git a/packages/geospatial/src/components/MapDraw.js b/packages/geospatial/src/components/MapDraw.js index 922abd95..ae03e550 100644 --- a/packages/geospatial/src/components/MapDraw.js +++ b/packages/geospatial/src/components/MapDraw.js @@ -3,6 +3,8 @@ import MapboxDraw from '@mapbox/mapbox-gl-draw'; import { bbox, + bboxPolygon, + buffer, feature, type FeatureCollection, type GeometryCollection @@ -13,14 +15,30 @@ import React, { useEffect, useMemo, useRef, - useState + useState, + type Node } from 'react'; import Map, { MapRef } from 'react-map-gl'; import _ from 'underscore'; import DrawControl from './DrawControl'; import './MapDraw.css'; +// Override the MapboxDraw components to use MapLibre styles +MapboxDraw.constants.classes.CONTROL_BASE = 'maplibregl-ctrl'; +MapboxDraw.constants.classes.CONTROL_PREFIX = 'maplibregl-ctrl-'; +MapboxDraw.constants.classes.CONTROL_GROUP = 'maplibregl-ctrl-group'; + type Props = { + /** + * The number of miles to buffer the GeoJSON data. + */ + buffer?: number, + + /** + * Additional child nodes to render. + */ + children?: Node, + /** * GeoJSON structured data to be displayed on the map. */ @@ -41,9 +59,17 @@ type Props = { /** * Map style object. */ - style?: any + style?: any, + + /** + * The time in milliseconds to zoom into the location. + */ + zoomDuration?: number }; +const DEFAULT_BUFFER = 2; +const DEFAULT_ZOOM_DELAY = 1000; + const GeometryTypes = { geometryCollection: 'GeometryCollection', point: 'Point' @@ -80,14 +106,24 @@ const MapDraw = (props: Props) => { */ useEffect(() => { if (loaded && props.data) { - // Sets the bounding box for the current geometry. - const boundingBox = bbox(props.data); + // Convert the GeoJSON into a bounding box + const box = bbox(props.data); + + // Convert the bounding box to a polygon + const polygon = bboxPolygon(box); + + // Create a buffer around the polygon + const polygonBuffer = buffer(polygon, props.buffer, { units: 'miles' }); + // Convert the buffer to a bounding box + const boundingBox = bbox(polygonBuffer); + + // Sets the bounding box for the current geometry. if (_.every(boundingBox, _.isFinite)) { const [minLng, minLat, maxLng, maxLat] = boundingBox; const bounds = [[minLng, minLat], [maxLng, maxLat]]; - mapRef.current.fitBounds(bounds, { padding: 40, duration: 1000 }); + mapRef.current.fitBounds(bounds, { duration: props.zoomDuration }); } // Handle special cases for geometry collection (not supported by mabox-gl-draw) and point @@ -122,9 +158,16 @@ const MapDraw = (props: Props) => { onCreate={onChange} onUpdate={onChange} onDelete={onChange} + position='bottom-left' /> + { props.children } ); }; +MapDraw.defaultProps = { + buffer: DEFAULT_BUFFER, + zoomDuration: DEFAULT_ZOOM_DELAY +}; + export default MapDraw; diff --git a/packages/geospatial/src/components/RasterLayer.js b/packages/geospatial/src/components/RasterLayer.js new file mode 100644 index 00000000..d9c2ee35 --- /dev/null +++ b/packages/geospatial/src/components/RasterLayer.js @@ -0,0 +1,38 @@ +// @flow + +import React from 'react'; +import { Layer, Source } from 'react-map-gl'; + +type Props = { + maxzoom?: number, + minzoom?: number, + opacity?: number, + tileSize?: number, + url?: string, +}; + +const RasterLayer = (props: Props) => ( + + + +); + +RasterLayer.defaultProps = { + maxzoom: 22, + minzoom: 0, + opacity: 0.7, + tileSize: 256 +}; + +export default RasterLayer; diff --git a/packages/geospatial/src/index.js b/packages/geospatial/src/index.js index 12209a1b..03cd16c6 100644 --- a/packages/geospatial/src/index.js +++ b/packages/geospatial/src/index.js @@ -2,4 +2,8 @@ // Components export { default as DrawControl } from './components/DrawControl'; +export { default as GeoJsonLayer } from './components/GeoJsonLayer'; +export { default as LayerMenu } from './components/LayerMenu'; +export { default as MapControl } from './components/MapControl'; export { default as MapDraw } from './components/MapDraw'; +export { default as RasterLayer } from './components/RasterLayer'; diff --git a/packages/geospatial/webpack.config.js b/packages/geospatial/webpack.config.js index 2153b36e..60a17bd6 100644 --- a/packages/geospatial/webpack.config.js +++ b/packages/geospatial/webpack.config.js @@ -7,6 +7,10 @@ module.exports = configure(__dirname, { './@mapbox/mapbox-gl-draw/dist/mapbox-gl-draw.css$': path.resolve( __dirname, '../../node_modules/@mapbox/mapbox-gl-draw/dist/mapbox-gl-draw.css' + ), + './maplibre-gl/dist/maplibre-gl.css$': path.resolve( + __dirname, + '../../node_modules/maplibre-gl/dist/maplibre-gl.css' ) } } diff --git a/packages/semantic-ui/package.json b/packages/semantic-ui/package.json index f97fae6c..d4fa3d8a 100644 --- a/packages/semantic-ui/package.json +++ b/packages/semantic-ui/package.json @@ -1,6 +1,6 @@ { "name": "@performant-software/semantic-components", - "version": "1.1.2", + "version": "1.1.3", "description": "A package of shared components based on the Semantic UI Framework.", "license": "MIT", "main": "./build/index.js", @@ -12,7 +12,7 @@ "build": "webpack --mode production && flow-copy-source -v src types" }, "dependencies": { - "@performant-software/shared-components": "^1.1.2", + "@performant-software/shared-components": "^1.1.3", "@react-google-maps/api": "^2.8.1", "axios": "^0.26.1", "i18next": "^19.4.4", diff --git a/packages/shared/package.json b/packages/shared/package.json index 1210c204..222c7b6f 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -1,6 +1,6 @@ { "name": "@performant-software/shared-components", - "version": "1.1.2", + "version": "1.1.3", "description": "A package of shared, framework agnostic, components.", "license": "MIT", "main": "./build/index.js", diff --git a/packages/storybook/src/data/boston.json b/packages/storybook/src/data/Boston.json similarity index 100% rename from packages/storybook/src/data/boston.json rename to packages/storybook/src/data/Boston.json diff --git a/packages/storybook/src/data/MbtaStops.json b/packages/storybook/src/data/MbtaStops.json new file mode 100644 index 00000000..5755ad04 --- /dev/null +++ b/packages/storybook/src/data/MbtaStops.json @@ -0,0 +1,2787 @@ +{ + "type": "FeatureCollection", + "features": [ + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.105682, + 42.333722 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "green-e", + "green" + ], + "id": "fenwood-rd", + "name": "Fenwood Rd" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.149072, + 42.334912 + ] + }, + "type": "Feature", + "properties": { + "area": "brookline", + "lines": [ + "green-d", + "green" + ], + "id": "reservoir", + "name": "Reservoir" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.032, + 42.345182 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "silver", + "sl2" + ], + "id": "northern-ave-amp-tide-st", + "name": "Northern Ave. & Tide St." + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.060327, + 42.279712 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "mattapan", + "red" + ], + "id": "cedar-grove", + "name": "Cedar Grove" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.022476, + 42.379759 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "blue" + ], + "id": "wood-island", + "name": "Wood Island" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.116759, + 42.343366 + ] + }, + "type": "Feature", + "properties": { + "area": "brookline", + "lines": [ + "green", + "green-c", + "green-b" + ], + "id": "saint-paul-st", + "name": "Saint Paul St" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.075724, + 42.347314 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "orange" + ], + "id": "back-bay", + "name": "Back Bay" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.070493, + 42.361279 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "red" + ], + "id": "charlesmgh", + "name": "Charles/MGH" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.034448, + 42.344579 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "silver", + "sl2" + ], + "id": "dry-dock-ave-amp-design-center-place", + "name": "Dry Dock Ave. & Design Center Place" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.057117, + 42.342793 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "red" + ], + "id": "broadway", + "name": "Broadway" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.007034, + 42.232848 + ] + }, + "type": "Feature", + "properties": { + "area": "quincy", + "lines": [ + "red" + ], + "id": "quincy-adams", + "name": "Quincy Adams" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.047514, + 42.352724 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "silver", + "sl2", + "sl1" + ], + "id": "courthouse", + "name": "Courthouse" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.099787, + 42.322893 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "orange" + ], + "id": "jackson-square", + "name": "Jackson Square" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.124702, + 42.351833 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "green", + "green-b" + ], + "id": "packards-corner", + "name": "Packards Corner" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.057421, + 42.359065 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "orange" + ], + "id": "state-street", + "name": "State Street" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.034119, + 42.343927 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "silver", + "sl2" + ], + "id": "black-falcon-ave-amp-design-center-place", + "name": "Black Falcon Ave. & Design Center Place" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.063229, + 42.284219 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "mattapan", + "red" + ], + "id": "ashmont", + "name": "Ashmont" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.252327, + 42.337394 + ] + }, + "type": "Feature", + "properties": { + "area": "newton", + "lines": [ + "green-d", + "green" + ], + "id": "riverside", + "name": "Riverside" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.067612, + 42.270093 + ] + }, + "type": "Feature", + "properties": { + "area": "milton", + "lines": [ + "mattapan", + "red" + ], + "id": "milton", + "name": "Milton" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.142449, + 42.395261 + ] + }, + "type": "Feature", + "properties": { + "area": "cambridge", + "lines": [ + "red" + ], + "id": "alewife", + "name": "Alewife" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.039287, + 42.347928 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "silver", + "sl2", + "sl1" + ], + "id": "silver-line-way", + "name": "Silver Line Way" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.113377, + 42.300023 + ] + }, + "type": "Feature", + "properties": { + "area": "jamaica-plain", + "lines": [ + "orange" + ], + "id": "forest-hills", + "name": "Forest Hills" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.111406, + 42.32996 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "green-e", + "green" + ], + "id": "back-of-the-hill", + "name": "Back of the Hill" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.028456, + 42.344681 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "silver", + "sl2" + ], + "id": "25-dry-dock-ave", + "name": "25 Dry Dock Ave." + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.100235, + 42.349126 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "green", + "green-b" + ], + "id": "blandford-st", + "name": "Blandford St" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.142593, + 42.343886 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "green", + "green-b" + ], + "id": "washington-st", + "name": "Washington St" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.029307, + 42.274957 + ] + }, + "type": "Feature", + "properties": { + "area": "quincy", + "lines": [ + "red" + ], + "id": "north-quincy", + "name": "North Quincy" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.135387, + 42.339258 + ] + }, + "type": "Feature", + "properties": { + "area": "brookline", + "lines": [ + "green", + "green-c" + ], + "id": "washington-sq", + "name": "Washington Sq" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.076593, + 42.370774 + ] + }, + "type": "Feature", + "properties": { + "area": "cambridge", + "lines": [ + "green-e", + "green-d", + "green" + ], + "id": "lechmere", + "name": "Lechmere" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.07052, + 42.372593 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "orange" + ], + "id": "community-college", + "name": "Community College" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.057922, + 42.363222 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "green-e", + "green-d", + "green", + "green-c", + "orange" + ], + "id": "haymarket", + "name": "Haymarket" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.166124, + 42.339902 + ] + }, + "type": "Feature", + "properties": { + "area": "brighton", + "lines": [ + "green", + "green-b" + ], + "id": "boston-college", + "name": "Boston College" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.06955, + 42.34136 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "sl4", + "sl5", + "silver" + ], + "id": "union-park-st", + "name": "Union Park St" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.129327, + 42.339683 + ] + }, + "type": "Feature", + "properties": { + "area": "brookline", + "lines": [ + "green", + "green-c" + ], + "id": "brandon-hall", + "name": "Brandon Hall" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.11068, + 42.32868 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "green-e", + "green" + ], + "id": "heath-st", + "name": "Heath St" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.062129, + 42.361457 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "blue" + ], + "id": "bowdoin", + "name": "Bowdoin" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.141549, + 42.337847 + ] + }, + "type": "Feature", + "properties": { + "area": "brookline", + "lines": [ + "green", + "green-c" + ], + "id": "dean-rd", + "name": "Dean Rd" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.081205, + 42.332709 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "sl4", + "sl5", + "silver" + ], + "id": "melnea-cass-blvd", + "name": "Melnea Cass Blvd" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.077056, + 42.383434 + ] + }, + "type": "Feature", + "properties": { + "area": "charlestown", + "lines": [ + "orange" + ], + "id": "sullivan-square", + "name": "Sullivan Square" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.053175, + 42.311099 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "red" + ], + "id": "savin-hill", + "name": "Savin Hill" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.018148, + 42.367461 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "silver", + "sl1" + ], + "id": "terminals-c-amp-d", + "name": "Terminals C & D" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.113885, + 42.344125 + ] + }, + "type": "Feature", + "properties": { + "area": "brookline", + "lines": [ + "green", + "green-c" + ], + "id": "kent-st", + "name": "Kent St" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.090437, + 42.335742 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "orange" + ], + "id": "ruggles", + "name": "Ruggles" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.118129, + 42.332002 + ] + }, + "type": "Feature", + "properties": { + "area": "brookline", + "lines": [ + "green-d", + "green" + ], + "id": "brookline-village", + "name": "Brookline Village" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.078958, + 42.334958 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "sl4", + "sl5", + "silver" + ], + "id": "lenox-st", + "name": "Lenox St" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.00085, + 42.20855 + ] + }, + "type": "Feature", + "properties": { + "area": "braintree", + "lines": [ + "red" + ], + "id": "braintree", + "name": "Braintree" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.138705, + 42.338469 + ] + }, + "type": "Feature", + "properties": { + "area": "brookline", + "lines": [ + "green", + "green-c" + ], + "id": "tappan-st", + "name": "Tappan St" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.109678, + 42.333106 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "green-e", + "green" + ], + "id": "mission-park", + "name": "Mission Park" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.152961, + 42.338145 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "green", + "green-b" + ], + "id": "chestnut-hill-ave", + "name": "Chestnut Hill Ave" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.131197, + 42.350118 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "green", + "green-b" + ], + "id": "harvard-ave", + "name": "Harvard Ave" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -70.990986, + 42.413963 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "blue" + ], + "id": "wonderland", + "name": "Wonderland" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.06461, + 42.352337 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "sl5", + "green", + "green-e", + "green-d", + "silver", + "green-c", + "green-b" + ], + "id": "boylston", + "name": "Boylston" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.111129, + 42.34487 + ] + }, + "type": "Feature", + "properties": { + "area": "brookline", + "lines": [ + "green", + "green-c" + ], + "id": "hawes-st", + "name": "Hawes St" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.021494, + 42.364422 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "silver", + "sl1" + ], + "id": "terminal-a", + "name": "Terminal A" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.060465, + 42.355453 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "orange", + "red" + ], + "id": "downtown-crossing", + "name": "Downtown Crossing" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.077232, + 42.336378 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "sl4", + "sl5", + "silver" + ], + "id": "mass-ave", + "name": "Mass Ave" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.057421, + 42.359065 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "blue" + ], + "id": "state", + "name": "State" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.065912, + 42.293712 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "red" + ], + "id": "shawmut", + "name": "Shawmut" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.038422, + 42.368343 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "blue" + ], + "id": "maverick", + "name": "Maverick" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.070888, + 42.437134 + ] + }, + "type": "Feature", + "properties": { + "area": "malden", + "lines": [ + "orange" + ], + "id": "oak-grove", + "name": "Oak Grove" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.113833, + 42.350759 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "green", + "green-b" + ], + "id": "boston-university-west", + "name": "Boston University West" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.092021, + 42.267586 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "mattapan", + "red" + ], + "id": "mattapan", + "name": "Mattapan" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.157622, + 42.339455 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "green", + "green-b" + ], + "id": "south-st", + "name": "South St" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.127031, + 42.331133 + ] + }, + "type": "Feature", + "properties": { + "area": "brookline", + "lines": [ + "green-d", + "green" + ], + "id": "brookline-hills", + "name": "Brookline Hills" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.059195, + 42.359868 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "blue", + "green", + "green-e", + "green-d", + "green-c", + "green-b" + ], + "id": "government-center", + "name": "Government Center" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.118917, + 42.373936 + ] + }, + "type": "Feature", + "properties": { + "area": "cambridge", + "lines": [ + "red" + ], + "id": "harvard", + "name": "Harvard" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.146162, + 42.341483 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "green", + "green-b" + ], + "id": "sutherland-rd", + "name": "Sutherland Rd" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.051807, + 42.359634 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "blue" + ], + "id": "aquarium", + "name": "Aquarium" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.125182, + 42.340946 + ] + }, + "type": "Feature", + "properties": { + "area": "brookline", + "lines": [ + "green", + "green-c" + ], + "id": "summit-ave", + "name": "Summit Ave" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.066041, + 42.343845 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "sl4", + "sl5", + "silver" + ], + "id": "east-berkeley-st", + "name": "East Berkeley St" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.100052, + 42.335898 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "green-e", + "green" + ], + "id": "longwood-medical-area", + "name": "Longwood Medical Area" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.121547, + 42.35169 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "green", + "green-b" + ], + "id": "babcock-st", + "name": "Babcock St" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.131304, + 42.339474 + ] + }, + "type": "Feature", + "properties": { + "area": "brookline", + "lines": [ + "green", + "green-c" + ], + "id": "fairbanks-st", + "name": "Fairbanks St" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.081401, + 42.345503 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "green-e", + "green" + ], + "id": "prudential", + "name": "Prudential" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.064651, + 42.346443 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "sl4", + "sl5", + "silver" + ], + "id": "herald-st", + "name": "Herald St" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.106804, + 42.34989 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "green", + "green-b" + ], + "id": "boston-university-central", + "name": "Boston University Central" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.029485, + 42.374542 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "blue" + ], + "id": "airport", + "name": "Airport" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.074144, + 42.426678 + ] + }, + "type": "Feature", + "properties": { + "area": "malden", + "lines": [ + "orange" + ], + "id": "malden", + "name": "Malden" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.122527, + 42.39662 + ] + }, + "type": "Feature", + "properties": { + "area": "somerville", + "lines": [ + "red" + ], + "id": "davis", + "name": "Davis" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.052545, + 42.321065 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "red" + ], + "id": "jfkumass", + "name": "JFK/UMass" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.107125, + 42.310359 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "orange" + ], + "id": "green-street", + "name": "Green Street" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.019721, + 42.265972 + ] + }, + "type": "Feature", + "properties": { + "area": "quincy", + "lines": [ + "red" + ], + "id": "wollaston", + "name": "Wollaston" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.088717, + 42.340307 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "green-e", + "green" + ], + "id": "northeastern", + "name": "Northeastern" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.077463, + 42.349991 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "green-e", + "green-d", + "green", + "green-c", + "green-b" + ], + "id": "copley", + "name": "Copley" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.067666, + 42.366664 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "green-e", + "green-d", + "green" + ], + "id": "science-park", + "name": "Science Park" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.018588, + 42.361325 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "silver", + "sl1" + ], + "id": "terminal-b-stop-1", + "name": "Terminal B Stop 1" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.018820, + 42.364441 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "silver", + "sl1" + ], + "id": "terminal-b-stop-2", + "name": "Terminal B Stop 2" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.083025, + 42.267772 + ] + }, + "type": "Feature", + "properties": { + "area": "milton", + "lines": [ + "mattapan", + "red" + ], + "id": "valley-rd", + "name": "Valley Rd" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.04206, + 42.349098 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "silver", + "sl2", + "sl1" + ], + "id": "world-trade-center", + "name": "World Trade Center" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.061251, + 42.365551 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "green-e", + "green-d", + "green", + "green-c", + "orange" + ], + "id": "north-station", + "name": "North Station" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.083072, + 42.341762 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "orange" + ], + "id": "massachusetts-ave", + "name": "Massachusetts Ave." + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.073783, + 42.338754 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "sl4", + "sl5", + "silver" + ], + "id": "newton-st", + "name": "Newton St" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.070817, + 42.351847 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "green-e", + "green-d", + "green", + "green-c", + "green-b" + ], + "id": "arlington", + "name": "Arlington" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.004798, + 42.250879 + ] + }, + "type": "Feature", + "properties": { + "area": "quincy", + "lines": [ + "red" + ], + "id": "quincy-center", + "name": "Quincy Center" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.103866, + 42.349569 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "green", + "green-b" + ], + "id": "boston-university-east", + "name": "Boston University East" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.084529, + 42.342919 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "green-e", + "green" + ], + "id": "symphony", + "name": "Symphony" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -70.992363, + 42.39745 + ] + }, + "type": "Feature", + "properties": { + "area": "revere", + "lines": [ + "blue" + ], + "id": "beachmont", + "name": "Beachmont" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.062202, + 42.356332 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "green", + "green-c", + "green-e", + "green-d", + "red", + "green-b" + ], + "id": "park-st", + "name": "Park St" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.062453, + 42.272253 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "mattapan", + "red" + ], + "id": "butler", + "name": "Butler" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.140849, + 42.335805 + ] + }, + "type": "Feature", + "properties": { + "area": "brookline", + "lines": [ + "green-d", + "green" + ], + "id": "beaconsfield", + "name": "Beaconsfield" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.140224, + 42.348366 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "green", + "green-b" + ], + "id": "warren-st", + "name": "Warren St" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.088148, + 42.347953 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "green-d", + "green", + "green-c", + "green-b" + ], + "id": "hynes-convention-center", + "name": "Hynes Convention Center" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.120888, + 42.342226 + ] + }, + "type": "Feature", + "properties": { + "area": "brookline", + "lines": [ + "green", + "green-c" + ], + "id": "coolidge-corner", + "name": "Coolidge Corner" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.107385, + 42.345947 + ] + }, + "type": "Feature", + "properties": { + "area": "brookline", + "lines": [ + "green", + "green-c" + ], + "id": "saint-marys-st", + "name": "Saint Mary's St" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.150459, + 42.340839 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "green", + "green-b" + ], + "id": "chiswick-rd", + "name": "Chiswick Rd" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.056979, + 42.329752 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "red" + ], + "id": "andrew", + "name": "Andrew" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.035215, + 42.346621 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "silver", + "sl2" + ], + "id": "northern-ave-amp-harbor-st", + "name": "Northern Ave. & Harbor St." + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.145357, + 42.337049 + ] + }, + "type": "Feature", + "properties": { + "area": "brookline", + "lines": [ + "green", + "green-c" + ], + "id": "englewood-ave", + "name": "Englewood Ave" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.035882, + 42.34769 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "silver", + "sl2" + ], + "id": "306-northern-ave", + "name": "306 Northern Ave." + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.087436, + 42.267622 + ] + }, + "type": "Feature", + "properties": { + "area": "milton", + "lines": [ + "mattapan", + "red" + ], + "id": "capen-st", + "name": "Capen St" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.112004, + 42.331686 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "green-e", + "green" + ], + "id": "riverway", + "name": "Riverway" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.084266, + 42.329379 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "sl4", + "sl5", + "silver" + ], + "id": "dudley-square", + "name": "Dudley Square" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.230712, + 42.325981 + ] + }, + "type": "Feature", + "properties": { + "area": "newton", + "lines": [ + "green-d", + "green" + ], + "id": "waban", + "name": "Waban" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.118783, + 42.351345 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "green", + "green-b" + ], + "id": "pleasant-st", + "name": "Pleasant St" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.062759, + 42.352529 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "orange", + "sl4", + "silver", + "sl5" + ], + "id": "chinatown", + "name": "Chinatown" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.134551, + 42.348542 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "green", + "green-b" + ], + "id": "griggs-st", + "name": "Griggs St" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.063877, + 42.349504 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "orange", + "sl4", + "silver", + "sl5" + ], + "id": "tufts-medical-center", + "name": "Tufts Medical Center" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.086058, + 42.362427 + ] + }, + "type": "Feature", + "properties": { + "area": "cambridge", + "lines": [ + "red" + ], + "id": "kendallmit", + "name": "Kendall/MIT" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.095125, + 42.337732 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "green-e", + "green" + ], + "id": "museum-of-fine-arts", + "name": "Museum of Fine Arts" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.119159, + 42.388353 + ] + }, + "type": "Feature", + "properties": { + "area": "cambridge", + "lines": [ + "red" + ], + "id": "porter-square", + "name": "Porter Square" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.095128, + 42.348783 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "green-d", + "green", + "green-c", + "green-b" + ], + "id": "kenmore", + "name": "Kenmore" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.075846, + 42.337386 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "sl4", + "sl5", + "silver" + ], + "id": "worcester-sq", + "name": "Worcester Sq" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.076969, + 42.405202 + ] + }, + "type": "Feature", + "properties": { + "area": "malden", + "lines": [ + "orange" + ], + "id": "wellington", + "name": "Wellington" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -70.99256, + 42.408389 + ] + }, + "type": "Feature", + "properties": { + "area": "revere", + "lines": [ + "blue" + ], + "id": "revere-beach", + "name": "Revere Beach" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.217023, + 42.318744 + ] + }, + "type": "Feature", + "properties": { + "area": "newton", + "lines": [ + "green-d", + "green" + ], + "id": "eliot", + "name": "Eliot" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -70.997678, + 42.390232 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "blue" + ], + "id": "suffolk-downs", + "name": "Suffolk Downs" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.110215, + 42.341042 + ] + }, + "type": "Feature", + "properties": { + "area": "brookline", + "lines": [ + "green-d", + "green" + ], + "id": "longwood-station", + "name": "Longwood Station" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.060465, + 42.355453 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "sl5", + "silver" + ], + "id": "downtown-crossing-at-temple-place", + "name": "Downtown Crossing at Temple Place" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.24434, + 42.333336 + ] + }, + "type": "Feature", + "properties": { + "area": "newton", + "lines": [ + "green-d", + "green" + ], + "id": "woodland", + "name": "Woodland" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.061516, + 42.299992 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "red" + ], + "id": "fields-corner", + "name": "Fields Corner" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.055428, + 42.352573 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "sl4", + "sl2", + "silver", + "red", + "sl1" + ], + "id": "south-station", + "name": "South Station" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.104021, + 42.317251 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "orange" + ], + "id": "stony-brook", + "name": "Stony Brook" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.004941, + 42.386769 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "blue" + ], + "id": "orient-heights", + "name": "Orient Heights" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.205966, + 42.321539 + ] + }, + "type": "Feature", + "properties": { + "area": "newton", + "lines": [ + "green-d", + "green" + ], + "id": "newton-highlands", + "name": "Newton Highlands" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.073249, + 42.269965 + ] + }, + "type": "Feature", + "properties": { + "area": "milton", + "lines": [ + "mattapan", + "red" + ], + "id": "central-ave", + "name": "Central Ave" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.16529, + 42.326601 + ] + }, + "type": "Feature", + "properties": { + "area": "newton", + "lines": [ + "green-d", + "green" + ], + "id": "chestnut-hill-station-d", + "name": "Chestnut Hill Station D" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.0201340, + 42.3694570 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "silver", + "sl1" + ], + "id": "terminal-e", + "name": "Terminal E" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.149406, + 42.336197 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "green", + "green-c" + ], + "id": "cleveland-circle", + "name": "Cleveland Circle" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.137888, + 42.348468 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "green", + "green-b" + ], + "id": "allston-st", + "name": "Allston St" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.095555, + 42.331388 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "orange" + ], + "id": "roxbury-crossing", + "name": "Roxbury Crossing" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.02724, + 42.344105 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "silver", + "sl2" + ], + "id": "88-black-falcon-ave", + "name": "88 Black Falcon Ave." + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.103474, + 42.365326 + ] + }, + "type": "Feature", + "properties": { + "area": "cambridge", + "lines": [ + "red" + ], + "id": "central-square", + "name": "Central Square" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.10437, + 42.334268 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "green-e", + "green" + ], + "id": "brigham-circle", + "name": "Brigham Circle" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.104156, + 42.345347 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "green-d", + "green" + ], + "id": "fenway", + "name": "Fenway" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.194444, + 42.328354 + ] + }, + "type": "Feature", + "properties": { + "area": "newton", + "lines": [ + "green-d", + "green" + ], + "id": "newton-centre", + "name": "Newton Centre" + } + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -71.031036, + 42.344626 + ] + }, + "type": "Feature", + "properties": { + "area": "boston", + "lines": [ + "silver", + "sl2" + ], + "id": "21-dry-dock-ave", + "name": "21 Dry Dock Ave." + } + } + ] +} \ No newline at end of file diff --git a/packages/storybook/src/geospatial/MapDraw.stories.js b/packages/storybook/src/geospatial/MapDraw.stories.js index 0aabdaf4..0f8dd2a7 100644 --- a/packages/storybook/src/geospatial/MapDraw.stories.js +++ b/packages/storybook/src/geospatial/MapDraw.stories.js @@ -2,8 +2,14 @@ import { action } from '@storybook/addon-actions'; import React from 'react'; +import GeoJsonLayer from '../../../geospatial/src/components/GeoJsonLayer'; +import LayerMenu from '../../../geospatial/src/components/LayerMenu'; +import MapControl from '../../../geospatial/src/components/MapControl'; import MapDraw from '../../../geospatial/src/components/MapDraw'; -import data from '../data/boston.json'; +import RasterLayerComp from '../../../geospatial/src/components/RasterLayer'; + +import bostonBoundaryData from '../data/Boston.json'; +import mbtaStops from '../data/MbtaStops.json'; export default { title: 'Components/Geospatial/MapDraw', @@ -14,20 +20,14 @@ export const Default = () => ( ); export const GeoJSON = () => ( ); @@ -42,8 +42,166 @@ export const Point = () => ( }} mapStyle={`https://api.maptiler.com/maps/basic-v2/style.json?key=${process.env.REACT_APP_MAP_TILER_KEY}`} onChange={action('onChange')} - style={{ - marginBottom: '2em' - }} /> ); + +export const GeoJSONFillLayer = () => ( + + + + + +); + +export const GeoJSONCircleLayer = () => ( + + + + + +); + +export const GeoJSONLayerStyles = () => ( + + + + + +); + +export const RasterLayer = () => ( + + + + + +); + +export const EmptyLayerMenu = () => ( + + + +); + +export const CustomControl = () => ( + + + + Click me! + + + +); diff --git a/packages/user-defined-fields/package.json b/packages/user-defined-fields/package.json index babc9885..46b6a08b 100644 --- a/packages/user-defined-fields/package.json +++ b/packages/user-defined-fields/package.json @@ -1,6 +1,6 @@ { "name": "@performant-software/user-defined-fields", - "version": "1.1.2", + "version": "1.1.3", "description": "A package of components used for allowing end users to define fields on models. Use with the \"user_defined_fields\" gem.", "license": "MIT", "main": "./build/index.js", @@ -9,8 +9,8 @@ "build": "webpack --mode production && flow-copy-source -v src types" }, "dependencies": { - "@performant-software/semantic-components": "^1.1.2", - "@performant-software/shared-components": "^1.1.2", + "@performant-software/semantic-components": "^1.1.3", + "@performant-software/shared-components": "^1.1.3", "i18next": "^21.9.1", "semantic-ui-react": "^2.1.2", "underscore": "^1.13.2" diff --git a/packages/visualize/package.json b/packages/visualize/package.json index febd26d0..8327c66a 100644 --- a/packages/visualize/package.json +++ b/packages/visualize/package.json @@ -1,6 +1,6 @@ { "name": "@performant-software/visualize", - "version": "1.1.2", + "version": "1.1.3", "description": "A package of components used for data visualization", "license": "MIT", "main": "./build/index.js", diff --git a/react-components.json b/react-components.json index 3b890389..51141891 100644 --- a/react-components.json +++ b/react-components.json @@ -7,5 +7,5 @@ "packages/user-defined-fields", "packages/visualize" ], - "version": "1.1.2" + "version": "1.1.3" } diff --git a/yarn.lock b/yarn.lock index ab578f15..368defa2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5632,7 +5632,7 @@ resolved "https://registry.yarnpkg.com/@types/find-cache-dir/-/find-cache-dir-3.2.1.tgz#7b959a4b9643a1e6a1a5fe49032693cc36773501" integrity sha512-frsJrz2t/CeGifcu/6uRo4b+SzAwT4NYCVPu1GN8IB9XTzrpPkGuV0tmh9mN+/L0PklAlsC3u5Fxt0ju00LXIw== -"@types/geojson@*", "@types/geojson@^7946.0.12": +"@types/geojson@*", "@types/geojson@^7946.0.13": version "7946.0.13" resolved "https://registry.yarnpkg.com/@types/geojson/-/geojson-7946.0.13.tgz#e6e77ea9ecf36564980a861e24e62a095988775e" integrity sha512-bmrNrgKMOhM3WsafmbGmC+6dsF2Z308vLFsQ3a/bT8X8Sv5clVYpPars/UPq+sAaJP+5OoLAYgwbkS5QEJdLUQ== @@ -5755,12 +5755,12 @@ dependencies: "@types/geojson" "*" -"@types/mapbox__point-geometry@*", "@types/mapbox__point-geometry@^0.1.3": +"@types/mapbox__point-geometry@*", "@types/mapbox__point-geometry@^0.1.4": version "0.1.4" resolved "https://registry.yarnpkg.com/@types/mapbox__point-geometry/-/mapbox__point-geometry-0.1.4.tgz#0ef017b75eedce02ff6243b4189210e2e6d5e56d" integrity sha512-mUWlSxAmYLfwnRBmgYV86tgYmMIICX4kza8YnE/eIlywGe2XoOxlpVnXWwir92xRLjwyarqwpu2EJKD2pk0IUA== -"@types/mapbox__vector-tile@^1.3.3": +"@types/mapbox__vector-tile@^1.3.4": version "1.3.4" resolved "https://registry.yarnpkg.com/@types/mapbox__vector-tile/-/mapbox__vector-tile-1.3.4.tgz#ad757441ef1d34628d9e098afd9c91423c1f8734" integrity sha512-bpd8dRn9pr6xKvuEBQup8pwQfD4VUyqO/2deGjfpe6AwC8YRlyEipvefyRJUSiCJTZuCb8Pl1ciVV5ekqJ96Bg== @@ -5835,7 +5835,7 @@ resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== -"@types/pbf@*", "@types/pbf@^3.0.4": +"@types/pbf@*", "@types/pbf@^3.0.5": version "3.0.5" resolved "https://registry.yarnpkg.com/@types/pbf/-/pbf-3.0.5.tgz#a9495a58d8c75be4ffe9a0bd749a307715c07404" integrity sha512-j3pOPiEcWZ34R6a6mN07mUkM4o4Lwf6hPNt8eilOeZhTFbxFXmKhvXl9Y28jotFPaI1bpPDJsbCprUoNke6OrA== @@ -5934,7 +5934,7 @@ resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c" integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw== -"@types/supercluster@^7.1.2": +"@types/supercluster@^7.1.3": version "7.1.3" resolved "https://registry.yarnpkg.com/@types/supercluster/-/supercluster-7.1.3.tgz#1a1bc2401b09174d9c9e44124931ec7874a72b27" integrity sha512-Z0pOY34GDFl3Q6hUFYf3HkTwKEE02e7QgtJppBt+beEAxnyOpJua+voGFvxINBHa06GwLFFym7gRPY2SiKIfIA== @@ -11174,10 +11174,10 @@ map-or-similar@^1.5.0: resolved "https://registry.yarnpkg.com/empty-npm-package/-/empty-npm-package-1.0.0.tgz#fda29eb6de5efa391f73d578697853af55f6793a" integrity sha512-q4Mq/+XO7UNDdMiPpR/LIBIW1Zl4V0Z6UT9aKGqIAnBCtCb3lvZJM1KbDbdzdC8fKflwflModfjR29Nt0EpcwA== -maplibre-gl@^3.5.2: - version "3.5.2" - resolved "https://registry.yarnpkg.com/maplibre-gl/-/maplibre-gl-3.5.2.tgz#84a352f1845c6ccf6fe5d86aaa0d0e8b0f18923c" - integrity sha512-deqYA/RiEyXMGroZMDbOWNQTLnFsxREC+mDkQnuyCUNdBWm1KHafsXJYZP7rlLa5RLQNq05IAUAizY9aHTpIUw== +maplibre-gl@^3.6.2: + version "3.6.2" + resolved "https://registry.yarnpkg.com/maplibre-gl/-/maplibre-gl-3.6.2.tgz#abc2f34bddecabef8c20028eff06d62e36d75ccc" + integrity sha512-krg2KFIdOpLPngONDhP6ixCoWl5kbdMINP0moMSJFVX7wX1Clm2M9hlNKXS8vBGlVWwR5R3ZfI6IPrYz7c+aCQ== dependencies: "@mapbox/geojson-rewind" "^0.5.2" "@mapbox/jsonlint-lines-primitives" "^2.0.2" @@ -11187,11 +11187,11 @@ maplibre-gl@^3.5.2: "@mapbox/vector-tile" "^1.3.1" "@mapbox/whoots-js" "^3.1.0" "@maplibre/maplibre-gl-style-spec" "^19.3.3" - "@types/geojson" "^7946.0.12" - "@types/mapbox__point-geometry" "^0.1.3" - "@types/mapbox__vector-tile" "^1.3.3" - "@types/pbf" "^3.0.4" - "@types/supercluster" "^7.1.2" + "@types/geojson" "^7946.0.13" + "@types/mapbox__point-geometry" "^0.1.4" + "@types/mapbox__vector-tile" "^1.3.4" + "@types/pbf" "^3.0.5" + "@types/supercluster" "^7.1.3" earcut "^2.2.4" geojson-vt "^3.2.1" gl-matrix "^3.4.3" @@ -12615,6 +12615,11 @@ react-i18next@^11.4.0: "@babel/runtime" "^7.14.5" html-parse-stringify "^3.0.1" +react-icons@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/react-icons/-/react-icons-5.0.1.tgz#1694e11bfa2a2888cab47dcc30154ce90485feee" + integrity sha512-WqLZJ4bLzlhmsvme6iFdgO8gfZP17rfjYEJ2m9RsZjZ+cc4k1hTzknEz63YS1MeT50kVzoa1Nz36f4BEx+Wigw== + react-inspector@^6.0.0: version "6.0.2" resolved "https://registry.yarnpkg.com/react-inspector/-/react-inspector-6.0.2.tgz#aa3028803550cb6dbd7344816d5c80bf39d07e9d"