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

[Feat] Create polygons - Geo-Operations 1 #595

Merged
merged 2 commits into from
Jan 21, 2020
Merged

Conversation

macrigiuseppe
Copy link
Collaborator

@macrigiuseppe macrigiuseppe commented Jul 2, 2019

This pull requests introduce a new Edit mode which will allow users to draw custom polygons on a map to be used as layer filter (to be implemented later).

polygons

We support the ability to create/move/delete rectangle or customd shape polygons,

Context menu and delete action
feature-context-menu

Selecting a feature and clicking on delete button will remove the feature.

Layers
Screen Shot 2019-07-18 at 12 37 06 AM

The current implementation supports adding new points to existing features

Implementation details:

In order to implement the dropdown menu for editing mode i re-used the toolbar implementation currently used for sharing features.
I extracted the react component from sharing and moved into it's own file to be re-used in multiple both sharing and editing mode.

I created a new editor folder containing all react components for the editor mode, including a new context menu action panel.

The Editor component which contains react-map-gl-draw is located in the same MapContainer as deck.gl layer to facilitate logic to switch from editor to read mode

** There are still few edge cases to handle but the feature is pretty much integrated.

@macrigiuseppe macrigiuseppe force-pushed the 389_create_polygons branch 2 times, most recently from ae03840 to 78f3e1a Compare July 2, 2019 23:10
@macrigiuseppe macrigiuseppe self-assigned this Jul 2, 2019
@macrigiuseppe macrigiuseppe force-pushed the 389_create_polygons branch from 9a5d7fd to 1376d7a Compare July 8, 2019 22:03
@macrigiuseppe
Copy link
Collaborator Author

Depends on uber/nebula.gl#254

@macrigiuseppe macrigiuseppe requested a review from heshan0131 July 14, 2019 23:40
@macrigiuseppe
Copy link
Collaborator Author

@heshan0131 this is still WIP but it's touching a lof of files and I would love to have an early feedback

@@ -1,6 +1,6 @@
# Polygon
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is it by accident that all Polygon is renamed to DrawPolygon?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, maybe a replace all side effect. I will make sure I revert the right ones

@macrigiuseppe macrigiuseppe force-pushed the 389_create_polygons branch 4 times, most recently from d1289bd to 11fe20e Compare July 15, 2019 02:48
@macrigiuseppe
Copy link
Collaborator Author

@heshan0131 Reverted all changes and fixed lint errors and warnings

@@ -173,86 +259,8 @@ export default function MapContainerFactory(MapPopover, MapControl) {
};

/* component render functions */
/* eslint-disable complexity */
_renderMapPopover() {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i converted this method into a react component to take advantage of react render optimization


const MapLegendPanel = ({items, isActive, scale, toggleMenuPanel, isExport}) =>
const MapLegendPanel = React.memo(({items, isActive, scale, onToggleMenuPanel, isExport}) =>
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

React.memo aka PureComponent for stateless react

@@ -39,8 +39,7 @@ const propTypes = {
onClose: PropTypes.func.isRequired,
onChangeExportSelectedDataset: PropTypes.func.isRequired,
onChangeExportDataType: PropTypes.func.isRequired,
onChangeExportFiltered: PropTypes.func.isRequired,
onChangeExportConfig: PropTypes.func.isRequired
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not used

@macrigiuseppe macrigiuseppe changed the title [WIP] 389 create polygons Create polygons Jul 17, 2019
@macrigiuseppe
Copy link
Collaborator Author

@heshan0131 updated pull request, let me know what you think

font-size: 12px;
line-height: 14px;
padding: 8px;
height: 32px;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

switchHeight and switchWidth is in base.js, this should be added too

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

`;

const ToolbarItem = React.memo(({active, className, icon, label, onClick}) => (
<StyledDiv active={active} className="save-export-dropdown__item" onClick={(e) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

might want to change the class name here

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

</StyledDiv>
));

ToolbarItem.displayName = 'ToolbarItem';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

displayName is not working for memo
Screen Shot 2019-07-18 at 9 59 12 PM

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

indeed an issue with React facebook/react#14319.

I tried to use react-tools on my browser and this is what i have:
Screen Shot 2019-07-21 at 10 22 52 PM

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same thing for ActionPanelItem:
Screen Shot 2019-07-21 at 10 32 20 PM

I wonder what could be the difference

mapStyle={mapStyle.bottomMapStyle}
getCursor={this.props.hoverInfo ? () => 'pointer' : undefined}
transitionDuration={TRANSITION_DURATION}
onMouseMove={this.props.visStateActions.onMouseMove}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

onMouseMove is removed. tooltop can't render without mouse pos

Copy link
Collaborator Author

@macrigiuseppe macrigiuseppe Jul 22, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think i accidentally removed it when i rebased. It's back now

layerHoverProp={layerHoverProp}
coordinate={interactionConfig.coordinate.enabled && ((pinned || {}).coordinate || coordinate)}
freezed={Boolean(clicked || pinned)}
onClose={this._onCloseMapPopover}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

onClose needs to be passed in from prop, this is accessible

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

// project lnglat to screen so that tooltip follows the object on zoom
const viewport = new WebMercatorViewport(mapState);
const lngLat = clicked ? clicked.lngLat : pinned.coordinate;
position = this._getHoverXY(viewport, lngLat);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is not accessible anymore

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

];

export default function MapContainerFactory(MapPopover, MapControl) {
/* eslint-disable complexity */
const MapTooltip = React.memo(({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

instead of defining it inside MapContainer, it makes more sense to create a factory for MapTooltip, pass it to MapContainer as dep, then pass MapPopover as its dep? so

MapContainer -> MapTooltip -> MapPopover

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@@ -244,6 +244,8 @@ function KeplerGlFactory(
mapboxApiAccessToken,
mapboxApiUrl,
mapState,
uiState,
visState,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

items from visState is already picekd out and passed in mapFieds. If you need editor from it, just pass editor

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

</StyledMapContainer>
);
}
}

MapContainer.displayName = 'MapContainer';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you don't need displayName, you automatically get it by defining it as a class

Copy link
Collaborator Author

@macrigiuseppe macrigiuseppe Jul 22, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

eslint was complaining about all missing display names. Either we add displayName to all classes or disable the eslint.


export const setFeaturesUpdater = (state, payload) => ({
...state,
visState: visStateFeaturesUpdater(state.visState, payload),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we don't need a composed updater if an action is handled in each subreducer independently. You can just add [SET_FEATURE] action handlers to visState and uiState reducer

visState = {
  ....
  [ActionTypes.SET_FEATURE]: visStateFeaturesUpdater
}

uiState = {
  ....
  [ActionTypes.SET_FEATURE]: uiStateFeaturesUpdater
}

The time when we need to use combined updaters is when 1 action needs to be handled in multiple reducer is a certain sequence. e.g. in updateVisData we use layers created by visState to update viewport in mapState1

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i initially started with independent actions and I was trying to see whether there was a performance improvement by using a composed action. But at the end i don't think it made any different

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Copy link
Contributor

@heshan0131 heshan0131 Dec 6, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you are still creating setFeaturesComposed and deleteFeatureComposed. this 2 actions don't need to be composed here. They can be handled independently inside each (visState, uiState) reducer...

You can delete all new lines inside this file, and it will work

Copy link
Collaborator Author

@macrigiuseppe macrigiuseppe Dec 6, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idea behind these actions is to be used the way we use addDataToMapComposed, directly accessing kepler.gl store rather than using actions

An application can call these composed updaters when it is directly kepler.gl and want to modify the features. I am not using them at all and since i changed the logic in the second PR i can get rid of them
i am not even using those actions anywhere, i removed them

@macrigiuseppe
Copy link
Collaborator Author

@heshan0131 update with your feedback. Waiting on uber/nebula.gl#260 as well for better interaction handler

@heshan0131
Copy link
Contributor

Selection works after yarn installed latest react-map-gl-draw

Selected polygon should show solid line instead of dash
Image Pasted at 2019-7-22 10-15

@heshan0131
Copy link
Contributor

After drawing polygon, select rectangle, draw and drag doesn't create rectangle
bug_rect

@heshan0131
Copy link
Contributor

heshan0131 commented Jul 22, 2019

The mouse cursor should be crosshair in draw mode. In selection mode, cursor should be pointer, upon selecting a feature, the cursor should change to move, and back to pointer when a corner is hovered

@macrigiuseppe macrigiuseppe changed the title Create polygons [Feat] Create polygons Jul 23, 2019
@macrigiuseppe
Copy link
Collaborator Author

Selection works after yarn installed latest react-map-gl-draw

Selected polygon should show solid line instead of dash
Image Pasted at 2019-7-22 10-15

@heshan0131

Polygons are turned solid when selected

selected-solid-feature

@macrigiuseppe
Copy link
Collaborator Author

crosshair

@heshan0131 Updated cursor pointers

cursor-pointer

@@ -51,15 +51,18 @@ export default class Base extends Component {
width: null,
viewBox: '0 0 64 64',
predefinedClassName: '',
className: ''
className: '',
Copy link
Contributor

@heshan0131 heshan0131 Dec 5, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

setting fill: 'currentColor'in defaultProps means if user passes in a style object, it will override default style (instead of merging with it), not ideal. use style.fill = 'currentColor'; in render simply adds it to user defined value

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem with style.fill = 'currentColor' is that i cannot override the default value;
I need a way to override the fill property.

we can do something like

style = {}
... } = this.props;

style.fill = style.fill || 'currentColor'

or I can create a helper to merge defaults values when no properties exist in the original object


static defaultProps = {
height: '16px',
predefinedClassName: 'data-ex-icons-polygon',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'data-ex-icons-rectangle'

.mapboxgl-map .mapboxgl-missing-css {
display: none;
.mapboxgl-map {
z-index: -1;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why z-index: -1; here?

Copy link
Collaborator Author

@macrigiuseppe macrigiuseppe Dec 6, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is due to an issue with deckgl where deckgl capture mouse click events despite of other existing layers and their z-index value. So I have to make sure deckgl and its container mapbox-gl has -1 when in editor mode in order to be able to use the mouse to draw polygons.

When in regular mode we have the following:
mapboxgl: -1
deckgl: 0
editor: -1

In editor mode:
mapboxgl: -1
deckgl: -1
editor: 0

`;

const Toolbar = React.memo(({children, className, show, direction = 'row'}) => (
<StyledPanelDropdown className={`${className || ''} save-export-dropdown`} show={show} direction={direction}>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here about using classnames

onSelect={uiStateActions.setSelectedFeature}
onUpdate={visStateActions.setFeatures}
style={{zIndex: isEdit ? 0 : -1}}
onToggleFeatureLayer={visStateActions.toggleFeatureLayer}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't find toggleFeatureLayer is not defined in vis-state-actions, which causes this error:

Screen Shot 2019-12-05 at 3 39 25 PM

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh i guess it's in geo-operation 2 pr?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's in geo-operation 2 branch

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed it from this PR

onDeleteFeature={uiStateActions.deleteFeature}
onSelect={uiStateActions.setSelectedFeature}
onUpdate={visStateActions.setFeatures}
style={{zIndex: isEdit ? 0 : -1}}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

making zIndex to -1 when !isEdit cauing deckgl overlay showing on top of the draw element
Screen Shot 2019-12-05 at 3 58 53 PM

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changing the z-index, in this case, will not fix the issue because all layers are rendered onto a canvas and the editor is a regular html element.
In order to have the effect you describe we need to absolute position the editor container but in that case deck.gl will not receive mouse over and the tooltip will not show up

<div style={{position: 'relative'}}>
{isActive ? (
<StyledToolBar show={isActive} direction="column">
<ToolbarItem
Copy link
Contributor

@heshan0131 heshan0131 Dec 6, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that clicking on polygon automatically selects it. and click outside polygon deselects it, Do we still need this to manually enter selection mode?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need this for two reasons:

  • enable drawing on the map
  • the selection of a polygon only works when we are in editor mode. when we turn editor mode off we are not able to select polygons by clicking on it

<ToolbarItem
onClick={() => onSetEditorMode(EDITOR_MODES.DRAW_RECTANGLE)}
label="rectangle"
icon={(<Rectangle height="22px"/>)}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A better way is to pass in Rectangle and render it in <ToolbarItem><props.icon height="22px"></ToolbarItem>

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how would we handle all toolbarItems, each has a different icon.

Should we pass a default prop items with a list of button objects? something like this:

{
  onClick: () => onSetEditorMode(EDITOR_MODES.DRAW_POLYGON),
  label:'polygon',
  icon: Rectangle,
  activeCondition: EDITOR_MODES.DRAW_RECTANGLE
}

@@ -0,0 +1,112 @@
// Copyright (c) 2019 Uber Technologies, Inc.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this used anywhere?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I forgot to add this to MapContainer. this will replace _renderMapPopover method

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will remove it from this diff

@macrigiuseppe
Copy link
Collaborator Author

macrigiuseppe commented Dec 10, 2019

@heshan0131 I found a more simple and elegant solution for z-index and mouse events.

The Draw component will be placed using absolute position over deck.gl (canvas).

Modes

  • Read: Draw pointer-events css property will set to 'none' so deck.gl will capture mouse events and display tooltip correctly.
  • Edit: Draw pointer-events css property will set to 'all', users will be able to draw and apply filters.

Screen Shot 2019-12-10 at 11 14 47 AM

@macrigiuseppe macrigiuseppe force-pushed the 389_create_polygons branch 2 times, most recently from d6972f5 to f755b1e Compare December 10, 2019 19:16
@macrigiuseppe macrigiuseppe force-pushed the 389_create_polygons branch 2 times, most recently from 7edc2c6 to cbbbab9 Compare January 14, 2020 23:14
@macrigiuseppe macrigiuseppe force-pushed the 389_create_polygons branch 3 times, most recently from 4071267 to 5401267 Compare January 17, 2020 22:56
Signed-off-by: Giuseppe Macri <[email protected]>
* Apply Geo-Polygon filters

Co-authored-by: Shan He <[email protected]>
Signed-off-by: Giuseppe Macri <[email protected]>
@macrigiuseppe macrigiuseppe merged commit 3a01a4f into master Jan 21, 2020
@delete-merged-branch delete-merged-branch bot deleted the 389_create_polygons branch January 21, 2020 07:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants