Skip to content

Commit

Permalink
Merge pull request #248 from sashamaryl/zoom-flyto-refactor
Browse files Browse the repository at this point in the history
Zoom flyto refactor
  • Loading branch information
alexjball authored Dec 6, 2019
2 parents 457fb94 + 454f7d9 commit 8c3ec86
Show file tree
Hide file tree
Showing 7 changed files with 169 additions and 151 deletions.
50 changes: 24 additions & 26 deletions src/components/AnimatedMarker/animated-marker.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,23 @@ import mapboxgl from "mapbox-gl";
import { bboxPolygon, point, booleanPointInPolygon } from "@turf/turf";

class AnimatedMarker {
constructor(provider) {
this.provider = provider;
this.markerElement = this.createMarkerElement(
this.provider.id,
this.provider.typeId
);
this.element = this.markerElement.element;
this.markerIcon = this.markerElement.icon;
this.markerIconHighlight = this.markerElement.highlight;
this.marker = new mapboxgl.Marker({
element: this.element
}).setLngLat(provider.coordinates);
}
constructor(provider) {
this.provider = provider;
this.markerElement = this.createMarkerElement(this.provider.id, this.provider.typeId);
this.element = this.markerElement.element;
this.markerIcon = this.markerElement.icon;
this.markerIconHighlight = this.markerElement.highlight;
this.marker = new mapboxgl.Marker({
element: this.element
}).setLngLat(provider.coordinates);
}

isInView = bounds => {
const poly = bboxPolygon(bounds.toArray().flat());
const pt = point(this.provider.coordinates);
return booleanPointInPolygon(pt, poly);
};
isInView = (map) => {
const mapBoundsArray = map.getBounds().toArray().flat();
const poly = bboxPolygon(mapBoundsArray);
const pt = point(this.provider.coordinates);
return booleanPointInPolygon(pt, poly);
};

bounceIcon = () => {
this.markerIcon.classList.add("bounceOn");
Expand All @@ -30,21 +28,21 @@ class AnimatedMarker {
this.markerIcon.addEventListener("animationend", this.remove);
};

addTo(map, bounds) {
this.marker.addTo(map);
if (this.isInView(bounds)) {
this.bounceIcon();
} else {
map.once("moveend", this.bounceIcon);
addTo(map) {
this.marker.addTo(map);
if (this.isInView(map)) {
this.bounceIcon()
} else {
map.once('moveend', this.bounceIcon)
}
}
}

remove = () => {
this.markerIcon.removeEventListener("animationend", this.remove);
this.marker.remove();
};

createMarkerElement = (providerId, typeId) => {
createMarkerElement = (providerId, typeId) => {
const element = document.createElement("div");
element.id = `marker-${providerId}`;
element.className = "marker";
Expand Down
12 changes: 7 additions & 5 deletions src/components/Map/map.container.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { connect } from "react-redux";
import {
initializeVisaFilter,
displayProviderInformation,
setMapObject,
selectProvider
} from "redux/actions";
import { getMapProviders } from "redux/selectors";
import Map from "./map";
Expand All @@ -19,7 +19,9 @@ const mapStateToProps = state => {
highlightedProviders: state.highlightedProviders,
filters: state.filters,
search: state.search,
hoveredProvider: state.hoveredProvider
hoveredProvider: state.hoveredProvider,
selectProviderId: state.providers.selectProviderId,
selectProviderKey: state.providers.selectProviderKey
};
};

Expand All @@ -31,9 +33,9 @@ const mapDispatchToProps = dispatch => {
displayProviderInformation: id => {
dispatch(displayProviderInformation(id));
},
setMapObject: mapObject => {
dispatch(setMapObject(mapObject));
},
selectProvider: id => {
dispatch(selectProvider(id))
}
};
};

Expand Down
223 changes: 108 additions & 115 deletions src/components/Map/map.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,13 @@ import React, { Component } from "react";
import ReactDOM from "react-dom";
import mapboxgl from "./mapbox-gl-wrapper";
import "./map.css";
import { circle, point, transformTranslate } from "@turf/turf";
import {
circle,
point,
transformTranslate,
booleanPointInPolygon,
bboxPolygon
} from "@turf/turf";
import typeImages from "assets/images";
import distances from "assets/distances";
import {
Expand Down Expand Up @@ -327,6 +333,39 @@ class Map extends Component {
});
};

addDistanceIndicatorLayer = () => {
if (!this.map.getSource("distance-indicator-source")) {
this.map.addSource("distance-indicator-source", {
type: "geojson",
data: {
type: "FeatureCollection",
features: []
}
});
}
if (!this.map.getLayer("distance-indicator-fill")) {
this.map.addLayer({
id: "distance-indicator-fill",
type: "fill",
source: "distance-indicator-source",
paint: {
"fill-color": ["get", "color"]
}
});
}
if (!this.map.getLayer("distance-indicator-stroke")) {
this.map.addLayer({
id: "distance-indicator-stroke",
type: "line",
source: "distance-indicator-source",
paint: {
"line-color": "#D561B5",
"line-width": 2
}
});
}
};

geoJSONFeatures = () => {
let { highlightedProviders, visibleProviders = [], hoveredProvider } = this.props;
visibleProviders.forEach(
Expand Down Expand Up @@ -355,7 +394,6 @@ class Map extends Component {
// the user starts interacting with the app.
return;
}
this.updateZoom(this.props.filters.distance);
removeDistanceMarkers(this.markerList);
this.addDistanceIndicatorLayer();
// If no distance filter is set, display all distance indicators.
Expand Down Expand Up @@ -407,47 +445,6 @@ class Map extends Component {
.setData({ type: "FeatureCollection", features: circles });
};

updateZoom = distance => {
const zoom = distance ? distance : 1.5;
this.map.easeTo({
center: this.props.search.coordinates,
zoom: this.getZoomForDistance(zoom)
});
};

addDistanceIndicatorLayer = () => {
if (!this.map.getSource("distance-indicator-source")) {
this.map.addSource("distance-indicator-source", {
type: "geojson",
data: {
type: "FeatureCollection",
features: []
}
});
}
if (!this.map.getLayer("distance-indicator-fill")) {
this.map.addLayer({
id: "distance-indicator-fill",
type: "fill",
source: "distance-indicator-source",
paint: {
"fill-color": ["get", "color"]
}
});
}
if (!this.map.getLayer("distance-indicator-stroke")) {
this.map.addLayer({
id: "distance-indicator-stroke",
type: "line",
source: "distance-indicator-source",
paint: {
"line-color": "#D561B5",
"line-width": 2
}
});
}
};

zoomToFit = providerIds => {
providerIds =
providerIds ||
Expand Down Expand Up @@ -487,42 +484,20 @@ class Map extends Component {
]);
}

/**
* Zooms to fit when there are new providers not currently in view.
*
* TODO: This treats all selections the same. We may want to do different things depending
* on how the provider was selected. For example, when selecting a provider from the list,
* maybe we should zoom to that specific provider if not in view, but when deselecting
* a distance filter, maybe we want to zoom to fit all selected providers. Handling these
* cases is best done using more granular props passed to the map rather than having the map
* track changes to highlighted props.
*/
zoomToShowNewProviders = (prevProps, mapBounds) => {
const prevIds = filterProviderIds(
providersById(prevProps.visibleProviders),
prevProps.highlightedProviders
),
currIds = filterProviderIds(
providersById(this.props.visibleProviders),
this.props.highlightedProviders
),
newIds = currIds.filter(id => !prevIds.includes(id));
if (newIds.length === 0) {
// The set of selected providers stayed the same or got smaller, no need to zoom.
return;
}
const newFeatureBounds = getProviderBoundingBox(
providersById(this.props.visibleProviders),
newIds
);
if (
newFeatureBounds.getNorth() > mapBounds.getNorth() ||
newFeatureBounds.getEast() > mapBounds.getEast() ||
newFeatureBounds.getSouth() < mapBounds.getSouth() ||
newFeatureBounds.getWest() < mapBounds.getWest()
) {
this.zoomToFit(currIds);
}
areProvidersInView = newSelection => {
const mapBounds = this.getPaddedMapBounds()
.toArray()
.flat();
const mapBoundPoly = bboxPolygon(mapBounds);
newSelection.find(providerId => {
const providerObj = providersById(this.props.visibleProviders)[
providerId
];
return !booleanPointInPolygon(
point(providerObj.coordinates),
mapBoundPoly
);
});
};

getZoomForDistance = distance => {
Expand All @@ -536,49 +511,67 @@ class Map extends Component {
);
};

updateMapPosition = prevProps => {
let {
zoomToFitKey,
searchKey,
flyToProviderKey,
flyToProviderId
} = this.props.search;
let { distance } = this.props.filters;

const idLookUp = providersById(this.props.visibleProviders);
const newSelection = this.props.highlightedProviders.filter(
providerId => !prevProps.highlightedProviders.includes(providerId)
);

if (zoomToFitKey && zoomToFitKey !== prevProps.search.zoomToFitKey) {
/*the zoom to fit button has been pressed*/
this.zoomToFit();
} else if (distance || searchKey) {
/* a new dropdown selection has been made */
this.smoothFlyTo(
this.getZoomForDistance(distance || 1.5),
this.props.search.coordinates)
this.updatePinAndDistanceIndicator(prevProps);
} else if (
/*a new selection has been made that is not within the visible area of the map*/
newSelection.length > 0 &&
this.props.highlightedProviders > 1 &&
!this.areProvidersInView(newSelection)
) {
this.zoomToFit();
} else if (
/*an address has been selected for a provider that is not in view*/
flyToProviderKey !== prevProps.flyToProviderKey &&
!this.areProvidersInView(newSelection)
) {
this.smoothFlyTo(
MIN_UNCLUSTERED_ZOOM,
idLookUp[flyToProviderId].coordinates
);
}
};

smoothFlyTo = (zoom, coordinates) => {
return this.map.flyTo({
center: coordinates,
zoom: zoom,
speed: 0.5
});
};

componentDidUpdate(prevProps) {
if (this.state.loaded) {
const features = this.geoJSONFeatures();
this.setSourceFeatures(features);
this.props.loadedProviderTypeIds.map(typeId =>
this.findLayerInMap(typeId)
);
this.setHoveredIconsLayer();
this.setHighlightedIconsLayer();
this.updatePinAndDistanceIndicator(prevProps);
const mapBounds = this.getPaddedMapBounds();
this.markRecentSelection(prevProps, mapBounds);
this.zoomToShowNewProviders(prevProps, mapBounds);
if (
this.props.filters.distance &&
this.props.filters.distance !== prevProps.filters.distance
) {
this.map.flyTo({
center: this.props.search.coordinates,
zoom: this.getZoomForDistance(this.props.filters.distance)
});
}
if (
this.props.search.flyToProviderKey !== prevProps.search.flyToProviderKey
) {
const { flyToProviderId } = this.props.search;
const { coordinates } = providersById(this.props.visibleProviders)[
flyToProviderId
];
this.map.flyTo({
center: coordinates,
zoom: MIN_UNCLUSTERED_ZOOM
});
}
if (this.props.search.zoomToFitKey !== prevProps.search.zoomToFitKey) {
this.zoomToFit();
}
if (this.props.search.searchKey !== prevProps.search.searchKey) {
this.map.flyTo({
center: this.props.search.coordinates,
zoom: this.getZoomForDistance(this.props.filters.distance || 1.5)
});
}
this.setHighlightedIconsLayer("highlighted", "highlighted");

this.markRecentSelection(prevProps);
this.updateMapPosition(prevProps);
}
}

Expand Down
6 changes: 5 additions & 1 deletion src/components/ProviderList/provider-list.container.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import {
changeSortOrder,
changeSortDirection,
flyToProvider,
zoomToFit
zoomToFit,
selectProvider
} from "redux/actions";
import { getProvidersSorted } from "redux/selectors.js";
import ProviderList from "./provider-list";
Expand Down Expand Up @@ -49,6 +50,9 @@ const mapDispatchToProps = dispatch => {
},
zoomToFit: () => {
dispatch(zoomToFit());
},
selectProvider: id => {
dispatch(selectProvider(id))
}
};
};
Expand Down
Loading

0 comments on commit 8c3ec86

Please sign in to comment.