Skip to content

Commit

Permalink
Merge pull request #355 from NYCPlanning/develop
Browse files Browse the repository at this point in the history
Merge final section maps feature to master for prod releease
  • Loading branch information
TylerMatteo committed Jul 3, 2023
2 parents 3b2d907 + c6f856a commit e9688bf
Show file tree
Hide file tree
Showing 18 changed files with 180 additions and 7,006 deletions.
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
v12.22.1
83 changes: 11 additions & 72 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
[![CircleCI](https://circleci.com/gh/NYCPlanning/labs-citymap/tree/develop.svg?style=svg)](https://circleci.com/gh/NYCPlanning/labs-citymap/tree/develop)

# The City Map

The City Map is the official adopted map of the city. It shows the location, dimension and grades of streets, parks, public places, and certain public easements. However, the City Map isn’t one comprehensive map; it’s a collection of many alteration maps for small areas of the City. This app brings all of the pieces of the City Map into one convenient place on the web.
Expand All @@ -10,8 +8,6 @@ The City Map is the official adopted map of the city. It shows the location, dim

[NYC Planning Labs](https://planninglabs.nyc) takes on a single project at a time, working closely with our customers from concept to delivery in a matter of weeks. We conduct regular maintenance between larger projects.

Take a look at our [sprint planning board](https://waffle.io/NYCPlanning/labs-citymap) to get an idea of our current priorities for this project.

## How you can help

In the spirit of free software, everyone is encouraged to help improve this project. Here are some ways you can contribute.
Expand All @@ -33,8 +29,7 @@ In the spirit of free software, everyone is encouraged to help improve this proj
You will need the following things properly installed on your computer.

- [Git](https://git-scm.com/)
- [Node.js](https://nodejs.org/) (with NPM)
- This installation was tested using Node v12.22.1
- [Node.js](https://nodejs.org/) (with NPM) **version listed in .nvmrc**
- [Ember CLI](https://ember-cli.com/)
- [Yarn](https://yarnpkg.com/)

Expand All @@ -43,25 +38,24 @@ You will need the following things properly installed on your computer.
- Clone this repo `git clone git@github.com:NYCPlanning/labs-streets.git`
- Navigate to the repo: `cd labs-streets`
- Install Dependencies `yarn`
- Start the server `ember s`
- Start the server `yarn run start`

## Configuring Carto instance for ad-hoc queries
The Carto instance to be used when the app calls Carto's [SQL API](https://carto.com/developers/sql-api/) is controlled by the `carto-username` key defined on the `ENV` object in `/config/environment.js`. When you run the project locally, by default, that should point to the `planninglabs` instance. To change this, you can temporarily change the `carto-username` value in `environment.js` or pass in a `CARTO_USER` environment variable when you run the project (See `netlify.toml` for an example of how to do this)

## Connecting to local Layers API
## Configuring which Layers API environment to use

By default, in development this app will acquire Layer Group information from the static file at `public/layer-groups/v1`.
This application uses [Layers API](https://github.com/NYCPlanning/labs-layers-api/) to pull down JSON configurations that are used to call Carto's Maps API to serve vector tiles to the client. The base URL specified in the `host` key on `ENV` in `/config/environment.js` determines which environment of Layers API you will be pointing to when running this project locally. By default, the app will point to the production layers API. To change this, you can temporarily update the `host` value on the `ENV` object in `environment.js` or pass in a `API_HOST` environment variable when you run the project (see `netlify.toml` for examples of how to do this).

To retrieve the latest layer group info by targetting a locally run or live Layers API, you can specify the `host` environment variable
within `config/environment.js`.
If you are also running Layers API locally and want to use that, you can specify the localhost with the port for `host`, such as

For example, if you are running a local Layers API at `localhost:3000`, then under the `environment === 'development'` condition in `config/envrinment.js`, add
```
ENV.host = 'http://localhost:3000';
```

## Architecture

City Map is an [Ember.js](https://www.emberjs.com/) single page application (SPA). The frontend handles routing, web mapping, layout, and user interactions, and communicates with various APIs for content and data.

Several dependencies are split up into their own repositories under /lib, including labs-layers, labs-ember-search, and cartobox-promises-utility. These are starting points for possible addons.
Streets is an [Ember.js](https://www.emberjs.com/) single page application (SPA). The frontend handles routing, web mapping, layout, and user interactions, and communicates with various APIs for content and data.

### Models, Layers, and Mutability
In this mapping iteration, we use models to manage individual layer and layer-group state. A layer group is a collection of layers, with a single visibility state. If a layer group's visibility is toggled off, that state is delegated to its related layers. These models allow for developers to reference a layer model from anywhere and manipulate its state, including layers' paint, layout, and filter states. These three properties are part of the MapboxGL Style Specification.
Expand Down Expand Up @@ -124,17 +118,13 @@ layerModelInstance.setFilter(['all', ['>=', 'effective', 100]]);

To style the map in development, we use [Maputnik Dev Server](https://github.com/NYCPlanning/labs-maputnik-dev-server). Check the `README` of maputnik-dev-server for the commands necessary to style the current map.

#### Layer-Groups

{TODO: explain how Layer Groups work}

## Backend services

Carto is the primary backend resource, which provides a PostGIS database, Map Tiler, and JSON/GeoJSON data API. All of the data represented in the app starts out as a PostGIS table in Carto.

- **Carto Maps API** - Spatial data used in the map are served as vector tiles from the Carto Maps API. Vector tiles are defined by a SQL query, and may include several named internal layers. Vector Tiles are defined in config files in app/sources, and the frontend converts each of these into a call to the Maps API, which produces a vector tile template that can be added to Mapbox GL as a 'source'.
- **Carto SQL API** - The SQL API is used to retrieve record-specific data, such as tax lot details when a user navigates to a specific lot. It is also used to cross-reference a selected lot on-the-fly with various other layers to find intersections.
- **Search** - {TODO: description of this service}
- **Search** - The typeahead search feature in the upper left of the UI is powered by [Search API](https://github.com/NYCPlanning/labs-search-api). The configuration for these integration is defined by the `labs-search` object found in `/config/environment.js`

## Testing and checks

Expand All @@ -146,57 +136,6 @@ Carto is the primary backend resource, which provides a PostGIS database, Map Ti
- run `ember test --serve`
- Before creating a Pull Request, make sure your branch is updated with the latest `develop` and passes all tests

## Deployment

The App is deployed to our VPS using Dokku, you need only do a `git push` of the master branch to the `dokku` remote.

- To create a new remote named `dokku`: `git remote add dokku dokku@{domain}:city-map`
- To Deploy: `git push dokku master`
- To deploy a branch other than master, alias it to master: `git push dokku {branchname}:master`

## Uploading New Files to Digital Ocean Spaces

1. Ask the client to send you the new files they want uploaded to the app. This can be over Slack, Box, Dropbox, etc.
2. Download the new files in a folder on your machine.

3. Before uploading these files to Spaces, you need to install s3cmd on your machine. You can download the file here: https://s3tools.org/download
4. Once you have s3cmd downloaded, navigate (in your terminal) to the s3cmd folder you just downloaded
5. Run the command $ sudo python setup.py install
6. Then run $ s3cmd --configure

Fill in the new config options:

Access Key: access key (this will be given to you by another team member)
Secret Key: secret key (this will be given to you by another team member)
Default Region [US]: [press enter]
S3 Endpoint: nyc3.digitaloceanspaces.com
DNS-style bucket+hostname: %(bucket)s.nyc3.digitaloceanspaces.com
Encryption password: [press enter]
Path to GPG program: [press enter]
Use HTTPS protocol [Yes/No]: Yes
HTTP Proxy server name: [press enter]
Test access with supplied credentials? [Y/n] y

//If you see this error about GPG program, type in "n" for Retry configuration

ERROR: Test failed: GPG program not found
Retry configuration? [Y/n] n

Save settings? [y/N] y
Configuration saved to '/Users/YourFile/.s3cfg'

7. After setting up the config, navigate to the directory with the files you downloaded from the client, and run this command in the terminal:

$ s3cmd --acl-public put ./* s3://nycdcp-dcm-alteration-maps --recursive

// “--acl-public” allows anyone to view the file online
// “ ./* ” signifies a specific path to follow to locate the folder
// “s3://nycdcp-dcm-alteration-maps” is the folder on Spaces where you are adding the new files
// "--recursive" is necessary to upload a directory

8. Make sure to have another team member add you to the Digital Ocean account. You will receive an email when you have been added. Create a new account from this email, and now you are able to view the files that have been uploaded to the Spaces folder.


## Contact us

You can find us on Twitter at [@nycplanninglabs](https://twitter.com/nycplanninglabs), or comment on issues and we'll follow up as soon as we can. If you'd like to send an email, use [labs_dl@planning.nyc.gov](mailto:labs_dl@planning.nyc.gov)
You can find us on Twitter at [@NYCPlanningTech](https://twitter.com/nycplanningtech), or comment on issues and we'll follow up as soon as we can. If you'd like to send an email, use [OpenSource_dl@planning.nyc.gov](mailto:opensource_dl@planning.nyc.gov)
16 changes: 8 additions & 8 deletions app/components/custom-controls.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@ export default class CustomControls extends Component {
@argument boundsGeoJSON;

datasets = [
{ tableName: 'citymap_citymap_v1', displayName: 'City Street Features' },
{ tableName: 'citymap_amendments_v3', displayName: 'City Map Amendments' },
{ tableName: 'citymap_streetcenterlines_v1', displayName: 'Street Centerlines' },
{ tableName: 'citymap_streetnamechanges_points_v0', displayName: 'Street Name Changes - Points' },
{ tableName: 'citymap_streetnamechanges_streets_v0', displayName: 'Street Name Changes - Streets' },
{ tableName: 'citymap_streetnamechanges_areas_v0', displayName: 'Street Name Changes - Areas' },
{ tableName: 'citymap_pierheadlines_v1', displayName: 'Pierhead and Bulkhead Lines' },
{ tableName: 'citymap_arterials_v0', displayName: 'Arterials' },
{ tableName: 'dcp_dcm', displayName: 'City Street Features', queryModifier: "feat_type NOT IN ('Pierhead_line', 'Bulkhead_line', 'Rail')" },
{ tableName: 'dcp_dcm_city_map_alterations', displayName: 'City Map Amendments' },
{ tableName: 'dcp_dcm_street_centerline', displayName: 'Street Centerlines' },
{ tableName: 'dcp_dcm_street_name_changes_points', displayName: 'Street Name Changes - Points' },
{ tableName: 'dcp_dcm_street_name_changes_lines', displayName: 'Street Name Changes - Streets' },
{ tableName: 'dcp_dcm_street_name_changes_areas', displayName: 'Street Name Changes - Areas' },
{ tableName: 'dcp_dcm', displayName: 'Pierhead and Bulkhead Lines', queryModifier: "feat_type IN ('Pierhead_line', 'Bulkhead_line')" },
{ tableName: 'dcp_dcm_arterials_major_streets', displayName: 'Arterials' },
]

@computed('boundsGeoJSON')
Expand Down
24 changes: 24 additions & 0 deletions app/components/map-popup-content.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,30 @@ export default class MapPopupContent extends Component {
return streetNameChanges;
}

@computed('features')
get sectionMapLink() {
const features = this.get('features');
if (features === null) return features;

const sectionMapLink = features
.filter(d => d.properties.type === 'streetsect')
.map((feature) => {
const { properties } = feature;
const { do_path, last_date, boro } = properties;

return {
feature,
do_path,
boro,
boroname: boroLookup[boro],
last_date: moment(last_date).format('MMM D, YYYY'),
section_info: do_path === null ? null : do_path.split('.com/')[1],
};
});

return sectionMapLink;
}

@argument
features = [];

Expand Down
27 changes: 21 additions & 6 deletions app/controllers/application.js
Original file line number Diff line number Diff line change
Expand Up @@ -218,16 +218,18 @@ export default class ApplicationController extends ParachuteController {

const amendmentsLayerDisabled = this.get('model').layerGroups.toArray().filter(layerGroup => layerGroup.get('visible')).find(layer => layer.id === 'amendments') === undefined;

const streetSectionLayerDisabled = this.get('model').layerGroups.toArray().filter(layerGroup => layerGroup.get('visible')).find(layer => layer.id === 'street-sections') === undefined;

// Open the popup and clear its content (defaults to showing spinner)
this.set('popupFeatures', null);
this.set('popupLocation', e.lngLat);

// Query and set the popup content
const { lng, lat } = e.lngLat;
const SQL = `
SELECT the_geom, 'alteration' AS type, altmappdf, status, effective, NULL AS bbl, NULL AS address
FROM citymap_amendments_v3
WHERE (effective IS NOT NULL
SELECT the_geom, 'alteration' AS type, altmappdf, status, effect_dt AS effective, NULL AS bbl, NULL AS address, NULL::timestamp AS last_date, NULL AS do_path, NULL AS boro
FROM dcp_dcm_city_map_alterations
WHERE (effect_dt IS NOT NULL
OR status = '13')
AND ST_Intersects(
the_geom,
Expand All @@ -239,7 +241,7 @@ export default class ApplicationController extends ParachuteController {
)
)
UNION ALL
SELECT the_geom, 'taxlot' AS type, NULL as altmappdf, NULL as status, NULL as effective, bbl, address
SELECT the_geom, 'taxlot' AS type, NULL AS altmappdf, NULL AS status, NULL AS effective, bbl, address, NULL::timestamp AS last_date, NULL AS do_path, NULL AS boro
FROM dcp_mappluto
WHERE ST_Intersects(
the_geom,
Expand All @@ -250,6 +252,18 @@ export default class ApplicationController extends ParachuteController {
),4326
)
)
UNION ALL
SELECT the_geom, 'streetsect' AS type, NULL AS altmappdf, NULL AS status, NULL AS effective, NULL AS bbl, NULL AS address, last_date, do_path, boro
FROM dcp_final_section_map_index
WHERE ST_Intersects(
the_geom,
ST_SetSRID(
ST_MakePoint(
${lng},
${lat}
),4326
)
)
`;

carto.SQL(SQL, 'geojson')
Expand All @@ -271,8 +285,9 @@ export default class ApplicationController extends ParachuteController {
let filteredFeatures = FC.features;

if (citymapLayerDisabled) filteredFeatures = filteredFeatures.filter(feature => feature.properties.type !== 'taxlot');
if (amendmentsLayerDisabled) filteredFeatures = filteredFeatures.filter(feature => (feature.properties.type === 'alteration' && feature.properties.effective === null) || feature.properties.type === 'taxlot');
if (pendingAmendmentsLayerDisabled) filteredFeatures = filteredFeatures.filter(feature => (feature.properties.type === 'alteration' && feature.properties.effective !== null) || feature.properties.type === 'taxlot');
if (amendmentsLayerDisabled) filteredFeatures = filteredFeatures.filter(feature => (feature.properties.type === 'alteration' && feature.properties.effective === null) || feature.properties.type === 'taxlot' || feature.properties.type === 'streetsect');
if (pendingAmendmentsLayerDisabled) filteredFeatures = filteredFeatures.filter(feature => (feature.properties.type === 'alteration' && feature.properties.effective !== null) || feature.properties.type === 'taxlot' || feature.properties.type === 'streetsect');
if (streetSectionLayerDisabled) filteredFeatures = filteredFeatures.filter(feature => (feature.properties.type !== 'streetsect'));

this.set('popupFeatures', [...filteredFeatures, ...streetNameChanges]);
});
Expand Down
1 change: 1 addition & 0 deletions app/routes/application.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export default class ApplicationRoute extends Route {
{ id: 'citymap', visible: true },
{ id: 'street-centerlines', visible: true },
{ id: 'pierhead-bulkhead-lines', visible: true },
{ id: 'street-sections', visible: false },
{ id: 'amendments', visible: true },
{ id: 'amendments-pending', visible: false },
{ id: 'arterials', visible: false },
Expand Down
7 changes: 7 additions & 0 deletions app/styles/layouts/_l-default.scss
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,13 @@ hr.small-margin {
}
}

.null-section-map{
display: flex;
flex-wrap: wrap;
max-height: min-content;
max-width: 15rem;
}

// Multi-column lists
.list-float-3 {
@include clearfix;
Expand Down
10 changes: 10 additions & 0 deletions app/templates/application.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,16 @@
{{/labs-ui/layer-group-toggle}}
{{/lookup-layer-group}}

{{#lookup-layer-group for='street-sections' as |layerGroup|}}
{{#labs-ui/layer-group-toggle
label=layerGroup.model.legend.label
tooltip=layerGroup.model.legend.tooltip
active=layerGroup.model.visible
icon=layerGroup.model.legend.icon
}}
{{/labs-ui/layer-group-toggle}}
{{/lookup-layer-group}}

{{#lookup-layer-group for='amendments' as |layerGroup|}}
{{#labs-ui/layer-group-toggle
label=layerGroup.model.legend.label
Expand Down
13 changes: 11 additions & 2 deletions app/templates/components/custom-controls.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,18 @@
<li>
<p>
{{fa-icon 'download'}} {{dataset.displayName}}:
<a href="https://planninglabs.carto.com/api/v2/sql?format=SHP&filename={{dataset.tableName}}&q=SELECT * FROM {{dataset.tableName}}"><strong>All</strong>&nbsp;<small>(SHP)</small></a>
{{#if dataset.queryModifier}}
<a href="https://planninglabs.carto.com/api/v2/sql?format=SHP&filename={{dataset.tableName}}&q=SELECT * FROM {{dataset.tableName}} WHERE {{dataset.queryModifier}}"><strong>All</strong>&nbsp;<small>(SHP)</small></a>
{{else}}
<a href="https://planninglabs.carto.com/api/v2/sql?format=SHP&filename={{dataset.tableName}}&q=SELECT * FROM {{dataset.tableName}}"><strong>All</strong>&nbsp;<small>(SHP)</small></a>
{{/if}}
<span class="medium-gray">|</span>
<a href="https://planninglabs.carto.com/api/v2/sql?format=SHP&filename={{dataset.tableName}}_clipped&q=SELECT * FROM {{dataset.tableName}} WHERE ST_Intersects(the_geom, ST_GeomFromGeoJSON('{{stringifiedBoundsGeoJSON}}'))"><strong>In View</strong>&nbsp;<small>(SHP)</small></a>
{{#if dataset.queryModifier}}
<a href="https://planninglabs.carto.com/api/v2/sql?format=SHP&filename={{dataset.tableName}}_clipped&q=SELECT * FROM {{dataset.tableName}} WHERE ST_Intersects(the_geom, ST_GeomFromGeoJSON('{{stringifiedBoundsGeoJSON}}')) AND {{dataset.queryModifier}}"><strong>In View</strong>&nbsp;<small>(SHP)</small></a>
{{else}}
<a href="https://planninglabs.carto.com/api/v2/sql?format=SHP&filename={{dataset.tableName}}_clipped&q=SELECT * FROM {{dataset.tableName}} WHERE ST_Intersects(the_geom, ST_GeomFromGeoJSON('{{stringifiedBoundsGeoJSON}}'))"><strong>In View</strong>&nbsp;<small>(SHP)</small></a>
{{/if}}

</p>
</li>
{{/each}}
Expand Down
Loading

0 comments on commit e9688bf

Please sign in to comment.