diff --git a/client/cypress/e2e/map.spec.js b/client/cypress/e2e/map.spec.js
index 9b7cd22a..ee0dface 100644
--- a/client/cypress/e2e/map.spec.js
+++ b/client/cypress/e2e/map.spec.js
@@ -39,7 +39,7 @@ describe('Tests for the Explore the Map page', () => {
return map.getFeatureState(
{
'id': id,
- 'source': constants.SCORE_SOURCE_NAME,
+ 'source': constants.HIGH_SCORE_SOURCE_NAME,
'sourceLayer': constants.SCORE_SOURCE_LAYER,
},
);
diff --git a/client/src/components/J40Map.tsx b/client/src/components/J40Map.tsx
index 5f6b9f71..691cf97c 100644
--- a/client/src/components/J40Map.tsx
+++ b/client/src/components/J40Map.tsx
@@ -8,7 +8,6 @@ import maplibregl, {LngLatBoundsLike,
LngLatLike,
MapboxGeoJSONFeature} from 'maplibre-gl';
import mapStyle from '../data/mapStyle';
-import ZoomWarning from './zoomWarning';
import PopupContent from './popupContent';
import * as constants from '../data/constants';
import ReactDOM from 'react-dom';
@@ -76,7 +75,7 @@ const J40Map = () => {
const map = e.target;
const clickedCoord = e.point;
const features = map.queryRenderedFeatures(clickedCoord, {
- layers: ['score'],
+ layers: [constants.HIGH_SCORE_LAYER_NAME],
});
const feature = features && features[0];
if (feature) {
@@ -107,10 +106,10 @@ const J40Map = () => {
mapRef.current.on('move', () => {
setZoom(mapRef.current.getZoom());
});
- mapRef.current.on('mouseenter', 'score', () => {
+ mapRef.current.on('mouseenter', constants.HIGH_SCORE_LAYER_NAME, () => {
mapRef.current.getCanvas().style.cursor = 'pointer';
});
- mapRef.current.on('mouseleave', 'score', () => {
+ mapRef.current.on('mouseleave', constants.HIGH_SCORE_LAYER_NAME, () => {
mapRef.current.getCanvas().style.cursor = '';
});
}, [mapRef]);
@@ -177,7 +176,6 @@ const J40Map = () => {
-
);
};
diff --git a/client/src/data/constants.tsx b/client/src/data/constants.tsx
index 7bb0ae1a..5da6fd4b 100644
--- a/client/src/data/constants.tsx
+++ b/client/src/data/constants.tsx
@@ -1,13 +1,22 @@
// URLS
-export const FEATURE_TILE_BASE_URL = 'https://d2zjid6n5ja2pt.cloudfront.net/0629_demo';
+export const FEATURE_TILE_BASE_URL = 'https://d2zjid6n5ja2pt.cloudfront.net';
+const XYZ_SUFFIX = '{z}/{x}/{y}.pbf';
+export const FEATURE_TILE_HIGH_ZOOM_URL = `${FEATURE_TILE_BASE_URL}/0629_demo/${XYZ_SUFFIX}`;
+export const FEATURE_TILE_LOW_ZOOM_URL = `${FEATURE_TILE_BASE_URL}/tiles_low/${XYZ_SUFFIX}`;
+
// Performance markers
export const PERFORMANCE_MARKER_MAP_IDLE = 'MAP_IDLE';
// Properties
-export const SCORE_PROPERTY = 'Score D (percentile)';
+export const SCORE_PROPERTY_HIGH = 'Score D (percentile)';
+export const SCORE_PROPERTY_LOW = 'D_SCORE';
export const GEOID_PROPERTY = 'GEOID10';
-export const SCORE_SOURCE_NAME = 'score';
+export const HIGH_SCORE_SOURCE_NAME = 'score-high';
+export const HIGH_SCORE_LAYER_NAME = 'score-high-layer';
+export const LOW_SCORE_SOURCE_NAME = 'score-low';
+export const LOW_SCORE_LAYER_NAME = 'score-low-layer';
+
// The name of the layer within the tiles that contains the score
export const SCORE_SOURCE_LAYER = 'blocks';
@@ -18,8 +27,10 @@ export type J40Properties = { [key: string]: any };
export const GLOBAL_MIN_ZOOM = 3;
export const GLOBAL_MAX_ZOOM = 22;
export const GLOBAL_MIN_ZOOM_LOW = 3;
-export const GLOBAL_MAX_ZOOM_LOW = 9;
-export const GLOBAL_MIN_ZOOM_HIGH = 9;
+export const GLOBAL_MAX_ZOOM_LOW = 7;
+export const GLOBAL_MIN_ZOOM_HIGHLIGHT = 9;
+export const GLOBAL_MAX_ZOOM_HIGHLIGHT = 22;
+export const GLOBAL_MIN_ZOOM_HIGH = 7;
export const GLOBAL_MAX_ZOOM_HIGH = 11;
// Bounds
@@ -74,3 +85,13 @@ export const MIN_COLOR = '#FFFFFF';
export const MED_COLOR = '#D1DAE6';
export const MAX_COLOR = '#768FB3';
export const BORDER_HIGHLIGHT_COLOR = '#00BDE3';
+
+// Widths
+export const HIGHLIGHT_BORDER_WIDTH = 5.0;
+
+// Score boundaries
+export const SCORE_BOUNDARY_LOW = 0.0;
+export const SCORE_BOUNDARY_THRESHOLD = 0.6;
+export const SCORE_BOUNDARY_PRIORITIZED = 0.75;
+
+export const isMobile = typeof window !== 'undefined' && (window.innerWidth < 400);
diff --git a/client/src/data/mapStyle.tsx b/client/src/data/mapStyle.tsx
index 5a81c06f..1b872c0d 100644
--- a/client/src/data/mapStyle.tsx
+++ b/client/src/data/mapStyle.tsx
@@ -43,47 +43,70 @@ function makePaint({
return paintDescriptor;
}
+const imageSuffix = constants.isMobile ? '' : '@2x';
+
const mapStyle : Style = {
'version': 8,
'sources': {
'carto': {
'type': 'raster',
- 'tiles': [
- 'https://a.basemaps.cartocdn.com/light_nolabels/{z}/{x}/{y}.png',
- 'https://b.basemaps.cartocdn.com/light_nolabels/{z}/{x}/{y}.png',
- 'https://c.basemaps.cartocdn.com/light_nolabels/{z}/{x}/{y}.png',
- 'https://d.basemaps.cartocdn.com/light_nolabels/{z}/{x}/{y}.png',
+ 'tiles':
+ [
+ `https://a.basemaps.cartocdn.com/light_nolabels/{z}/{x}/{y}${imageSuffix}.png`,
+ `https://b.basemaps.cartocdn.com/light_nolabels/{z}/{x}/{y}${imageSuffix}.png`,
+ `https://c.basemaps.cartocdn.com/light_nolabels/{z}/{x}/{y}${imageSuffix}.png`,
+ `https://d.basemaps.cartocdn.com/light_nolabels/{z}/{x}/{y}${imageSuffix}.png`,
],
+ 'minzoom': constants.GLOBAL_MIN_ZOOM,
+ 'maxzoom': constants.GLOBAL_MAX_ZOOM,
},
'geo': {
'type': 'raster',
'tiles': [
'https://mt0.google.com/vt/lyrs=p&hl=en&x={x}&y={y}&z={z}',
],
+ 'minzoom': constants.GLOBAL_MIN_ZOOM,
+ 'maxzoom': constants.GLOBAL_MAX_ZOOM,
},
- 'score': {
+ 'score-high': {
+ // "Score-high" represents the full set of data
+ // at the census block group level. It is only shown
+ // at high zoom levels to avoid performance issues at lower zooms
'type': 'vector',
// Our current tippecanoe command does not set an id.
// The below line promotes the GEOID10 property to the ID
- 'promoteId': 'GEOID10',
+ 'promoteId': constants.GEOID_PROPERTY,
'tiles': [
- `${constants.FEATURE_TILE_BASE_URL}/{z}/{x}/{y}.pbf`,
- // For local development, use:
- // 'http://localhost:8080/data/tl_2010_bg_with_data/{z}/{x}/{y}.pbf',
+ constants.FEATURE_TILE_HIGH_ZOOM_URL,
],
-
// Seeting maxzoom here enables 'overzooming'
// e.g. continued zooming beyond the max bounds.
// More here: https://docs.mapbox.com/help/glossary/overzoom/
+ 'minzoom': constants.GLOBAL_MIN_ZOOM_HIGH,
'maxzoom': constants.GLOBAL_MAX_ZOOM_HIGH,
},
+ 'score-low': {
+ // "Score-low" represents a tileset at the level of bucketed tracts.
+ // census block group information is `dissolve`d into tracts, then
+ // each tract is `dissolve`d into one of ten buckets. It is meant
+ // to give us a favorable tradeoff between performance and fidelity.
+ 'type': 'vector',
+ 'promoteId': constants.GEOID_PROPERTY,
+ 'tiles': [
+ constants.FEATURE_TILE_LOW_ZOOM_URL,
+ // For local development, use:
+ // 'http://localhost:8080/data/tl_2010_bg_with_data/{z}/{x}/{y}.pbf',
+ ],
+ 'minzoom': constants.GLOBAL_MIN_ZOOM_LOW,
+ 'maxzoom': constants.GLOBAL_MAX_ZOOM_LOW,
+ },
'labels': {
'type': 'raster',
'tiles': [
- 'https://cartodb-basemaps-a.global.ssl.fastly.net/light_only_labels/{z}/{x}/{y}@2x.png',
- 'https://cartodb-basemaps-b.global.ssl.fastly.net/light_only_labels/{z}/{x}/{y}@2x.png',
- 'https://cartodb-basemaps-c.global.ssl.fastly.net/light_only_labels/{z}/{x}/{y}@2x.png',
- 'https://cartodb-basemaps-d.global.ssl.fastly.net/light_only_labels/{z}/{x}/{y}@2x.png',
+ `https://cartodb-basemaps-a.global.ssl.fastly.net/light_only_labels/{z}/{x}/{y}${imageSuffix}.png`,
+ `https://cartodb-basemaps-b.global.ssl.fastly.net/light_only_labels/{z}/{x}/{y}${imageSuffix}.png`,
+ `https://cartodb-basemaps-c.global.ssl.fastly.net/light_only_labels/{z}/{x}/{y}${imageSuffix}.png`,
+ `https://cartodb-basemaps-d.global.ssl.fastly.net/light_only_labels/{z}/{x}/{y}${imageSuffix}.png`,
],
},
},
@@ -92,42 +115,60 @@ const mapStyle : Style = {
'id': 'carto',
'source': 'carto',
'type': 'raster',
- 'minzoom': constants.GLOBAL_MIN_ZOOM - 1,
+ 'minzoom': constants.GLOBAL_MIN_ZOOM,
+ 'maxzoom': constants.GLOBAL_MAX_ZOOM,
},
{
'id': 'geo',
'source': 'geo',
'type': 'raster',
- 'minzoom': constants.GLOBAL_MIN_ZOOM - 1,
'layout': {
- // Make the layer visible by default.
+ // Make the layer invisible by default.
'visibility': 'none',
},
- },
- {
- 'id': 'score',
- 'source': constants.SCORE_SOURCE_NAME,
- 'source-layer': constants.SCORE_SOURCE_LAYER,
- 'type': 'fill',
- 'filter': ['all',
- ['>', constants.SCORE_PROPERTY, 0.6],
- // ['in', 'STATEFP10', '01', '30', '34', '35', '36'],
- ],
- 'paint': makePaint({
- field: constants.SCORE_PROPERTY,
- minRamp: 0,
- medRamp: 0.6,
- maxRamp: 0.75,
- }),
'minzoom': constants.GLOBAL_MIN_ZOOM,
'maxzoom': constants.GLOBAL_MAX_ZOOM,
},
{
+ 'id': constants.HIGH_SCORE_LAYER_NAME,
+ 'source': constants.HIGH_SCORE_SOURCE_NAME,
+ 'source-layer': constants.SCORE_SOURCE_LAYER,
+ 'type': 'fill',
+ 'filter': ['all',
+ ['>', constants.SCORE_PROPERTY_HIGH, constants.SCORE_BOUNDARY_THRESHOLD],
+ ],
+ 'paint': makePaint({
+ field: constants.SCORE_PROPERTY_HIGH,
+ minRamp: constants.SCORE_BOUNDARY_LOW,
+ medRamp: constants.SCORE_BOUNDARY_THRESHOLD,
+ maxRamp: constants.SCORE_BOUNDARY_PRIORITIZED,
+ }),
+ 'minzoom': constants.GLOBAL_MIN_ZOOM_HIGH,
+ },
+ {
+ 'id': constants.LOW_SCORE_LAYER_NAME,
+ 'source': constants.LOW_SCORE_SOURCE_NAME,
+ 'source-layer': constants.SCORE_SOURCE_LAYER,
+ 'type': 'fill',
+ 'filter': ['all',
+ ['>', constants.SCORE_PROPERTY_LOW, constants.SCORE_BOUNDARY_THRESHOLD],
+ ],
+ 'paint': makePaint({
+ field: constants.SCORE_PROPERTY_LOW,
+ minRamp: constants.SCORE_BOUNDARY_LOW,
+ medRamp: constants.SCORE_BOUNDARY_THRESHOLD,
+ maxRamp: constants.SCORE_BOUNDARY_PRIORITIZED,
+ }),
+ 'minzoom': constants.GLOBAL_MIN_ZOOM_LOW,
+ 'maxzoom': constants.GLOBAL_MAX_ZOOM_LOW,
+ },
+ {
+ // "Score-highlights" represents the border
+ // around given tiles that appears at higher zooms
'id': 'score-highlights',
- 'source': 'score',
+ 'source': constants.HIGH_SCORE_SOURCE_NAME,
'source-layer': constants.SCORE_SOURCE_LAYER,
'type': 'line',
- 'minzoom': constants.GLOBAL_MIN_ZOOM_HIGH,
'layout': {
'visibility': 'visible',
'line-join': 'round',
@@ -138,13 +179,15 @@ const mapStyle : Style = {
'line-width': 0.8,
'line-opacity': 0.5,
},
+ 'minzoom': constants.GLOBAL_MIN_ZOOM_HIGHLIGHT,
+ 'maxzoom': constants.GLOBAL_MAX_ZOOM_HIGHLIGHT,
},
{
- // This layer queries the feature-state property "selected" and
- // highlights the border of the selected region if true
+ // "score-border-highlight" is used to highlight
+ // the currently-selected feature
'id': 'score-border-highlight',
'type': 'line',
- 'source': 'score',
+ 'source': constants.HIGH_SCORE_SOURCE_NAME,
'source-layer': constants.SCORE_SOURCE_LAYER,
'layout': {},
'paint': {
@@ -152,16 +195,20 @@ const mapStyle : Style = {
'line-width': [
'case',
['boolean', ['feature-state', 'selected'], false],
- 5.0,
+ constants.HIGHLIGHT_BORDER_WIDTH,
0,
],
},
+ 'minzoom': constants.GLOBAL_MIN_ZOOM_HIGH,
+ 'maxzoom': constants.GLOBAL_MAX_ZOOM_HIGH,
},
{
+ // We put labels last to ensure prominence
'id': 'labels-only',
'type': 'raster',
'source': 'labels',
'minzoom': constants.GLOBAL_MIN_ZOOM,
+ 'maxzoom': constants.GLOBAL_MAX_ZOOM,
},
],
};