Skip to content

Commit

Permalink
feat: event polygons (#114)
Browse files Browse the repository at this point in the history
* Event polygons support

* Refactoring event handling

* Event handling

* Code cleaning

* Server cluster polygons

* GIS API upgrade with event polygons support

* Update event layer fixture
  • Loading branch information
turban committed Apr 9, 2019
1 parent 6f3df6d commit 39b79d6
Show file tree
Hide file tree
Showing 7 changed files with 381 additions and 391 deletions.
536 changes: 268 additions & 268 deletions cypress/fixtures/eventlayer.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -51,7 +51,7 @@
"@dhis2/d2-ui-interpretations": "5.2.10",
"@dhis2/d2-ui-org-unit-dialog": "5.2.10",
"@dhis2/d2-ui-org-unit-tree": "5.2.10",
"@dhis2/gis-api": "^32.0.17",
"@dhis2/gis-api": "^32.0.18",
"@dhis2/ui": "^1.0.0-beta.15",
"@material-ui/core": "^3.4.0",
"@material-ui/icons": "^3.0.1",
Expand Down
50 changes: 25 additions & 25 deletions src/components/map/EventLayer.js
Expand Up @@ -40,15 +40,15 @@ class EventLayer extends Layer {

// Default props = no cluster
const config = {
type: 'dots',
type: 'events',
id,
index,
opacity,
isVisible,
data,
color: color || EVENT_COLOR,
radius: eventPointRadius || EVENT_RADIUS,
popup: this.onEventClick.bind(this),
onClick: this.onEventClick.bind(this),
};

if (eventClustering) {
Expand Down Expand Up @@ -144,9 +144,10 @@ class EventLayer extends Layer {
}
}

onEventClick(feature, callback) {
const coord = feature.geometry.coordinates;
const props = feature.properties;
onEventClick(evt) {
const { feature, coordinates } = evt;
const { type, coordinates: coord } = feature.geometry;
const { value } = feature.properties;
const { styleDataItem } = this.props;

apiFetch('/events/' + feature.id).then(data => {
Expand All @@ -159,9 +160,9 @@ class EventLayer extends Layer {

// Output value if styled by data item, and item is not included in display elements
if (styleDataItem && !this.displayElements[styleDataItem.id]) {
content += `<tr><th>${
styleDataItem.name
}</th><td>${props.value || i18n.t('Not set')}</td></tr>`;
content += `<tr><th>${styleDataItem.name}</th><td>${
value !== undefined ? value : i18n.t('Not set')
}</td></tr>`;
}

if (Array.isArray(dataValues)) {
Expand All @@ -185,26 +186,33 @@ class EventLayer extends Layer {
content += '<tr style="height:5px;"><th></th><td></td></tr>';
}

// Show event location for points
if (type === 'Point') {
content += `
<tr>
<th>${this.eventCoordinateFieldName ||
i18n.t('Event location')}</th>
<td>${coord[0]}, ${coord[1]}</td>
</tr>`;
}

content += `<tr>
<th>${i18n.t('Organisation unit')}</th>
<td>${data.orgUnitName}</td>
</tr>
<tr>
<th>${i18n.t('Event time')}</th>
<td>${time}</td>
</tr>
<tr>
<th>${this.eventCoordinateFieldName ||
i18n.t('Event location')}</th>
<td>${coord[0]}, ${coord[1]}</td>
</tr>
</tbody></table>`;
</tr>`;

content += '</tbody></table>';

// Remove all line breaks as it's not working for map download
callback(removeLineBreaks(content));
this.context.map.openPopup(removeLineBreaks(content), coordinates);
});
}

// Convert surver cluster response to GeoJSON
toGeoJson(data) {
const header = {};
const features = [];
Expand All @@ -215,19 +223,11 @@ class EventLayer extends Layer {
if (Array.isArray(data.rows)) {
data.rows.forEach(row => {
const extent = row[header.extent].match(/([-\d.]+)/g);
const coords = row[header.center].match(/([-\d.]+)/g);

// Round to 6 decimals - http://www.jacklmoore.com/notes/rounding-in-javascript/
coords[0] = Number(Math.round(coords[0] + 'e6') + 'e-6');
coords[1] = Number(Math.round(coords[1] + 'e6') + 'e-6');

features.push({
type: 'Feature',
id: row[header.points],
geometry: {
type: 'Point',
coordinates: coords,
},
geometry: JSON.parse(row[header.center]),
properties: {
count: parseInt(row[header.count], 10),
bounds: [
Expand Down
4 changes: 2 additions & 2 deletions src/reducers/layers.js
Expand Up @@ -11,14 +11,14 @@ const defaultLayers = [
layer: 'event',
type: 'Events',
img: 'images/events.png',
opacity: 0.95,
opacity: 0.8,
eventClustering: true,
},
{
layer: 'trackedEntity',
type: 'Tracked entities',
img: 'images/trackedentities.png',
opacity: 0.4,
opacity: 0.5,
},
{
layer: 'facility',
Expand Down
103 changes: 50 additions & 53 deletions src/util/__tests__/geojson.spec.js
Expand Up @@ -5,7 +5,7 @@ import {
getBounds,
addStyleDataItem,
createEventFeature,
buildEventCoordinateGetter,
buildEventGeometryGetter,
createEventFeatures,
} from '../geojson';

Expand Down Expand Up @@ -75,16 +75,18 @@ describe('geojson utils', () => {
const headers = [{ name: 'C1' }, { name: 'C2' }];
const dummyID = 'IAmAnID';
const dummyEventRow = ['What is the question?', 42];
const dummyCoordinates = [0, 0];
const dummyGetCoordinates = jest.fn(x => dummyCoordinates.map(String)); // Stringify
const dummyGeometry = {
geometry: { coordinates: [0, 0], type: 'Point' },
};
const dummyGetGeometry = jest.fn(x => dummyGeometry);
it('Should create a single feature from a single event with no Names passed', () => {
expect(
createEventFeature(
headers,
{},
dummyEventRow,
dummyID,
dummyGetCoordinates
dummyGetGeometry
)
).toEqual({
type: 'Feature',
Expand All @@ -93,10 +95,7 @@ describe('geojson utils', () => {
[headers[0].name]: dummyEventRow[0],
[headers[1].name]: dummyEventRow[1],
},
geometry: {
type: 'Point',
coordinates: dummyCoordinates,
},
geometry: dummyGeometry,
});
});

Expand All @@ -110,7 +109,7 @@ describe('geojson utils', () => {
names,
dummyEventRow,
dummyID,
dummyGetCoordinates
dummyGetGeometry
)
).toEqual({
type: 'Feature',
Expand All @@ -119,65 +118,70 @@ describe('geojson utils', () => {
[names[headers[0].name]]: dummyEventRow[0],
[headers[1].name]: dummyEventRow[1],
},
geometry: {
type: 'Point',
coordinates: dummyCoordinates,
},
geometry: dummyGeometry,
});
});
});

describe('buildEventCoordinateGetter', () => {
describe('buildEventGeometryGetter', () => {
const headers = [
{ name: 'id' },
{ name: 'latitude' },
{ name: 'geometry' },
{ name: 'myStringCoordinates' },
{ name: 'longitude' },
{ name: 'SomeField' },
{ name: 'myArrayCoordinates' },
{ name: 'SomeOtherField' },
];
const coords = [12.9, 5.4];

const stringCoords = [9, 14.3];
const arrayCoords = [21.1, 42.2];
const point = {
type: 'Point',
coordinates: [0, 0],
};
const dummyEvent = [
'MyID',
coords[1],
JSON.stringify(point),
JSON.stringify(stringCoords),
coords[0],
54321,
arrayCoords,
1234,
];
it('Should default to fetching longitude and latitude columns', () => {
const getter = buildEventCoordinateGetter(headers);
expect(getter(dummyEvent)).toEqual(coords);
it('Should default to fetching geometry column', () => {
const getter = buildEventGeometryGetter(headers);
expect(getter(dummyEvent)).toEqual(point);
});

it('Should parse a string coordinate', () => {
const getter = buildEventCoordinateGetter(
const getter = buildEventGeometryGetter(
headers,
'myStringCoordinates'
);
expect(getter(dummyEvent)).toEqual(stringCoords);
expect(getter(dummyEvent)).toEqual({
type: 'Point',
coordinates: stringCoords,
});
});

it('Should parse a coordinate array', () => {
const getter = buildEventCoordinateGetter(
const getter = buildEventGeometryGetter(
headers,
'myArrayCoordinates'
);
expect(getter(dummyEvent)).toEqual(arrayCoords);
expect(getter(dummyEvent)).toEqual({
type: 'Point',
coordinates: arrayCoords,
});
});

it('Should return an empty array on integer value', () => {
const getter = buildEventCoordinateGetter(headers, 'SomeField');
expect(getter(dummyEvent)).toEqual([]);
it('Should return null on integer value', () => {
const getter = buildEventGeometryGetter(headers, 'SomeField');
expect(getter(dummyEvent)).toEqual(null);
});

it('Should return an empty array on invalid string value', () => {
const getter = buildEventCoordinateGetter(headers, 'id');
expect(getter(dummyEvent)).toEqual([]);
const getter = buildEventGeometryGetter(headers, 'id');
expect(getter(dummyEvent)).toEqual(null);
});
});

Expand All @@ -186,8 +190,7 @@ describe('geojson utils', () => {
{ name: 'SomeField', column: 'SomeField Column' },
{ name: 'psi', column: 'psi Column' },
{ name: 'TheRealID', column: 'TheRealID Column' },
{ name: 'latitude', column: 'latitude Column' },
{ name: 'longitude', column: 'longitude Column' },
{ name: 'geometry', column: 'Geometry Column' },
{ name: 'SomeOtherField', column: 'SomeOtherField Column' },
];
const metaData = {
Expand All @@ -200,11 +203,17 @@ describe('geojson utils', () => {
),
};

const point = {
type: 'Point',
coordinates: [0, 0],
};
const pointString = JSON.stringify(point);

const rows = [
['ping', 'psi0', 'id0', 21.1, 42.2, 'pong'],
['foo', 'psi1', 'id1', 21.2, 42.3, 'bar'],
['bill', 'psi2', 'id2', 21.3, 42.4, 'gates'],
['paul', 'psi3', 'id3', 21.4, 42.5, 'allen'],
['ping', 'psi0', 'id0', pointString, 'pong'],
['foo', 'psi1', 'id1', pointString, 'bar'],
['bill', 'psi2', 'id2', pointString, 'gates'],
['paul', 'psi3', 'id3', pointString, 'allen'],
];
const response = {
headers,
Expand Down Expand Up @@ -234,10 +243,7 @@ describe('geojson utils', () => {
}),
{}
),
geometry: {
type: 'Point',
coordinates: [row[4], row[3]],
},
geometry: point,
}))
);
});
Expand All @@ -256,10 +262,7 @@ describe('geojson utils', () => {
}),
{}
),
geometry: {
type: 'Point',
coordinates: [row[4], row[3]],
},
geometry: point,
}))
);
});
Expand All @@ -280,10 +283,7 @@ describe('geojson utils', () => {
}),
{}
),
geometry: {
type: 'Point',
coordinates: [row[4], row[3]],
},
geometry: point,
}))
);
});
Expand Down Expand Up @@ -311,10 +311,7 @@ describe('geojson utils', () => {
}),
{}
),
geometry: {
type: 'Point',
coordinates: [row[4], row[3]],
},
geometry: point,
}))
);
});
Expand Down

0 comments on commit 39b79d6

Please sign in to comment.