Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

[WIP] Add geocoding using NYC GeoSearch API with autosuggest #95

Open
wants to merge 31 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
707c0f2
fix error of geojson poly detection in LeafletMap
clhenrick Jan 29, 2018
ae31283
added start of location search component
clhenrick Jan 29, 2018
4cf0417
added actions & reducer for handling geocoding
clhenrick Jan 29, 2018
9461f50
made search a connected component
clhenrick Jan 29, 2018
5fe0727
added search result readout component
clhenrick Jan 29, 2018
6a3fca4
added actions for filter by location & clear search
clhenrick Jan 29, 2018
da1f869
added behavior to set map view to search result & display marker
clhenrick Jan 29, 2018
f2d7b9b
start of sql for lat lon buffer spatial join
clhenrick Jan 29, 2018
17d9596
#76 geocoder UI color + border
gregallensworth Jan 30, 2018
85e4da8
#76 geocoder results panel X & tweaks
gregallensworth Jan 30, 2018
f654583
#76 geocoder marker adds circle / geoPolygonStyle constant
gregallensworth Jan 30, 2018
0eb2da2
#76 geocoder results panel now clearly shows search radius
gregallensworth Jan 30, 2018
a8b36e6
added redux boilerplate for async location search suggestions
clhenrick May 18, 2018
5dccb24
added dep react-autosuggest
clhenrick May 18, 2018
392c965
added search fetch action & fix reducer
clhenrick May 18, 2018
fc093a2
modified search component to use autosuggest and nyc geosearch api
clhenrick May 18, 2018
9e254c0
added react-autosuggest styling
clhenrick May 18, 2018
d39ab40
added redux boilerplate for storing selected location
clhenrick May 18, 2018
247e39f
search component stores selected search result
clhenrick May 18, 2018
4d65c81
removed dead search-form component
clhenrick May 18, 2018
566883f
rename clear location search -> clear search suggestions
clhenrick May 18, 2018
65a3d33
fixed small typo in geosearch url
clhenrick May 18, 2018
fed0d4e
added fetch http error handling for async actions
clhenrick May 20, 2018
e99fed6
pipe search.selectedFeature to leaflet map component
clhenrick May 20, 2018
049fb33
removed previous redux geocoding boilerplate
clhenrick May 20, 2018
bedb1f9
redux boilerplate for resetting state.search
clhenrick May 20, 2018
5660931
re-implement search results panel, still needs filter by location
clhenrick May 20, 2018
8d4b2bf
added react-autosuggest to webpack vendor bundle list
clhenrick May 20, 2018
c7398bc
added redux boilerplate for filter by location search
clhenrick May 20, 2018
8e5d9a3
leaflet map component receives filter coords
clhenrick May 20, 2018
9d12a4c
filter by location sql where clause constructed from filter coords array
clhenrick May 20, 2018
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"normalize-scss": "^6.0.0",
"query-string": "^5.0.1",
"react": "^15.4.2",
"react-autosuggest": "^9.3.4",
"react-collapse": "^2.3.3",
"react-copy-to-clipboard": "^4.2.3",
"react-dom": "^15.4.2",
Expand Down
32 changes: 32 additions & 0 deletions scss/components/_search-results.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
.SearchResults {
width: 100%;
position: relative;
padding-right: 10px;

border: 1px solid $marine-light;

p {
margin-bottom: 10px;
}

p.address {
font-size: 14px;
}

button.close {
float: right;

height: 20px;
padding: 0 5px;
margin: 5px 5px 10px 10px;
font-size: 11px;
line-height: 11px;
background-color: $off-white;
border-color: transparent;
color: $marine-light;
text-align: left;
text-transform: capitalize;
letter-spacing: 0.5px;
border: none;
}
}
73 changes: 73 additions & 0 deletions scss/components/_search.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
.Search {
width: 240px;
position: absolute;
top: $margin-5 + $app-header-height - 10;
left: $margin-25 + $zoom-controls-width + $padding-10 - 10;
z-index: 5;
padding-right: 10px;
background-color: transparent;

.status {
line-height: 52px;
}

.react-autosuggest__container {
position: relative;
}

.react-autosuggest__input {
width: 240px;
height: 30px;
padding: 10px 20px;
font-family: Helvetica, sans-serif;
font-weight: 300;
font-size: 16px;
border: 1px solid #aaa;
border-radius: 4px;
}

.react-autosuggest__input--focused {
outline: none;
}

.react-autosuggest__input--open {
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}

.react-autosuggest__suggestions-container {
display: none;
}

.react-autosuggest__suggestions-container--open {
display: block;
position: absolute;
top: 51px;
width: 280px;
border: 1px solid #aaa;
background-color: #fff;
font-family: Helvetica, sans-serif;
font-weight: 300;
font-size: 16px;
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
z-index: 2;
}

.react-autosuggest__suggestions-list {
margin: 0;
padding: 0;
list-style-type: none;
}

.react-autosuggest__suggestion {
cursor: pointer;
padding: 5px 10px;
color: #333;
}

.react-autosuggest__suggestion--highlighted {
background-color: #ddd;
}

}
2 changes: 2 additions & 0 deletions scss/main.scss
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,5 @@
@import 'components/modal';
@import 'components/small-device-message';
@import 'components/about-copy';
@import 'components/search';
@import 'components/search-results';
15 changes: 15 additions & 0 deletions src/actions/async_actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,15 @@ import {

polyfill();

// fetch() has an annoying way of handling http errors, so make sure to check for them
function maybeHandleHTTPError (response) {
if (!response.ok) {
throw Error(response.statusText);
} else {
return response;
}
}

const requestCrashStatsData = () => ({
type: actions.CRASHES_ALL_REQUEST
});
Expand All @@ -32,6 +41,7 @@ export const fetchCrashStatsData = (params) => {
return (dispatch) => {
dispatch(requestCrashStatsData());
return fetch(url)
.then(maybeHandleHTTPError)
.then(res => res.json())
.then(json => dispatch(receiveCrashStatsData(json.rows[0])))
.catch(error => dispatch(receiveCrashStatsError(error)));
Expand All @@ -58,6 +68,7 @@ export const fetchContributingFactors = (params) => {
return (dispatch) => {
dispatch(requestContributingFactors());
return fetch(url)
.then(maybeHandleHTTPError)
.then(res => res.json())
.then(json => dispatch(receiveContributingFactors(json.rows)))
.catch(error => dispatch(receiveContributingFactorsError(error)));
Expand All @@ -84,6 +95,7 @@ export const fetchCrashesYearRange = () => {
return (dispatch) => {
dispatch(requestCrashesYearRange());
return fetch(url)
.then(maybeHandleHTTPError)
.then(res => res.json())
.then(json => dispatch(receiveCrashesYearRange(json.rows)))
.catch(error => dispatch(receiveCrashesYearRangeError(error)));
Expand All @@ -110,6 +122,7 @@ export const fetchCrashesDateRange = () => {
return (dispatch) => {
dispatch(requestCrashesDateRange());
return fetch(url)
.then(maybeHandleHTTPError)
.then(res => res.json())
.then(json => dispatch(receiveCrashesDateRange(json.rows)))
.catch(error => dispatch(receiveCrashesDateRangeError(error)));
Expand All @@ -136,6 +149,7 @@ export const fetchCrashesMaxDate = () => {
return (dispatch) => {
dispatch(requestCrashesMaxDate());
return fetch(url)
.then(maybeHandleHTTPError)
.then(res => res.json())
.then(json => dispatch(receiveCrashesMaxDate(json.rows)))
.catch(error => dispatch(receiveCrashesMaxDateError(error)));
Expand All @@ -162,6 +176,7 @@ export const fetchGeoPolygons = (geo) => {
return (dispatch) => {
dispatch(requestGeoPolygons());
return fetch(url)
.then(maybeHandleHTTPError)
.then(res => res.json())
.then((json) => {
// tack on the geography name so that it may be diff'd in LeafletMap propTypes
Expand Down
15 changes: 15 additions & 0 deletions src/actions/filter_by_search_location.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import {
FILTER_BY_LOCATION,
CLEAR_FILTER_BY_LOCATION,
} from '../constants/action_types';

export const filterByLocation = coordinates => ({
type: FILTER_BY_LOCATION,
coordinates
});

export const clearFilterByLocation = () => ({
type: CLEAR_FILTER_BY_LOCATION,
});

export default filterByLocation;
3 changes: 3 additions & 0 deletions src/actions/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export { fetchCrashStatsData,
fetchCrashesDateRange,
fetchCrashesMaxDate,
fetchGeoPolygons,
fetchLocationGeocode
} from './async_actions';
export { startDateChange, endDateChange } from './filter_by_date_actions';
export {
Expand All @@ -19,3 +20,5 @@ export {
} from './filter_by_area_actions';
export filterByContributingFactor from './filter_contributing_factor_actions';
export { openModal, closeModal } from './modal_actions';
export * from './location_search_actions';
export * from './filter_by_search_location';
67 changes: 67 additions & 0 deletions src/actions/location_search_actions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import fetch from 'isomorphic-fetch';
import { polyfill } from 'es6-promise';
import {
LOCATION_SEARCH_REQUEST,
LOCATION_SEARCH_SUCCESS,
LOCATION_SEARCH_ERROR,
CLEAR_SEARCH_SUGGESTIONS,
UPDATE_AUTOSUGGEST_VALUE,
LOCATION_SEARCH_SELECT,
RESET_LOCATION_SEARCH,
} from '../constants/action_types';

polyfill();

export const requestSearchResults = () => ({
type: LOCATION_SEARCH_REQUEST
});

export const receiveSearchResults = payload => ({
type: LOCATION_SEARCH_SUCCESS,
payload
});

export const receiveSearchError = error => ({
type: LOCATION_SEARCH_ERROR,
error
});

export const fetchSearchResults = () => {
const url = 'https://geosearch.planninglabs.nyc/v1/autocomplete?text=';
return (dispatch, getState) => {
const { autosuggestValue } = getState().search;
dispatch(requestSearchResults());
return fetch(`${url}${autosuggestValue}`)
.then((res) => {
// fetch has an annoying way of handling http errors...
if (!res.ok) {
throw Error(res.statusText);
} else {
return res.json();
}
})
.then((json) => {
const payload = json.features;
return dispatch(receiveSearchResults(payload));
})
.catch(error => dispatch(receiveSearchError(error.message)));
};
};

export const updateAutosuggestValue = value => ({
type: UPDATE_AUTOSUGGEST_VALUE,
value
});

export const selectSearchResult = feature => ({
type: LOCATION_SEARCH_SELECT,
feature
});

export const clearSearchSuggestions = () => ({
type: CLEAR_SEARCH_SUGGESTIONS
});

export const resetLocationSearch = () => ({
type: RESET_LOCATION_SEARCH
});
2 changes: 2 additions & 0 deletions src/components/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import OptionsFiltersConnected from '../containers/OptionsFiltersConnected';
import ModalConnected from '../containers/ModalConnected';
import SmallDeviceMessage from './SmallDeviceMessage';
import LoadingIndicator from './LoadingIndicator';
import Search from '../containers/SearchConnected';

class App extends Component {
constructor() {
Expand Down Expand Up @@ -96,6 +97,7 @@ class App extends Component {
<SmallDeviceMessage /> :
[
<AppHeader key="app-header" openModal={openModal} />,
<Search key="search-ui" />,
<LeafletMapConnected
key="leaflet-map"
location={location}
Expand Down
Loading