Skip to content

Commit

Permalink
Optional reset of zoom+pan and sync between instances (#63)
Browse files Browse the repository at this point in the history
  • Loading branch information
anders-kiaer authored Oct 18, 2019
1 parent c41a24a commit ee521b9
Show file tree
Hide file tree
Showing 4 changed files with 138 additions and 37 deletions.
15 changes: 2 additions & 13 deletions examples/example_layered_map.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,11 +88,6 @@ def array_to_png(Z, shift=True, colormap=False):
# https://creativecommons.org/licenses/by-nc-sa/4.0/
map_data = np.loadtxt('./example-data/layered-map-data.npz.gz')

map_bounds = [[432204, 6475078],
[437720, 6481112]]

center = [435200, 6478000]

min_value = int(np.nanmin(map_data))
max_value = int(np.nanmax(map_data))

Expand Down Expand Up @@ -160,19 +155,13 @@ def array_to_png(Z, shift=True, colormap=False):
]

with open('../src/demo/example-data/layered-map.json', 'w') as fh:
json.dump({
'map_bounds': map_bounds,
'center': center,
'layers': layers
}, fh)
json.dump({'layers': layers}, fh)

app = dash.Dash(__name__)

app.layout = html.Div(children=[
webviz_subsurface_components.LayeredMap(
id='volve-map',
map_bounds=map_bounds,
center=center,
layers=layers
),
html.Pre(id='polyline'),
Expand All @@ -181,7 +170,7 @@ def array_to_png(Z, shift=True, colormap=False):
])

@app.callback(Output('polyline', 'children'),
[Input('volve-map', 'line_points')])
[Input('volve-map', 'polyline_points')])
def get_edited_line(coords):
return f'Edited polyline: {json.dumps(coords)}'

Expand Down
42 changes: 30 additions & 12 deletions src/demo/LayeredMapDemo.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,36 @@ const data = require("./example-data/layered-map.json");
class LayeredMapDemo extends Component {
render() {
return (
<LayeredMap
id={"layered-map-demo"}
map_bounds={data.map_bounds}
center={data.center}
layers={data.layers}
overlay_layers={data.overlay_layers}
setProps={e => console.log(e)}
draw_toolbar_marker={true}
draw_toolbar_polygon={true}
draw_toolbar_polyline={true}
showScaleY={true}
/>
<div
style={{ display: "grid", gridTemplateColumns: "1fr 1fr 1fr" }}
>
<div>
<LayeredMap
id={"layered-map-demo1"}
sync_ids={["layered-map-demo2"]}
layers={data.layers}
overlay_layers={data.overlay_layers}
setProps={e => console.log(e)}
draw_toolbar_marker={true}
draw_toolbar_polygon={true}
draw_toolbar_polyline={true}
showScaleY={true}
/>
</div>
<div>
<LayeredMap
id={"layered-map-demo2"}
sync_ids={["layered-map-demo1"]}
layers={data.layers}
overlay_layers={data.overlay_layers}
setProps={e => console.log(e)}
draw_toolbar_marker={true}
draw_toolbar_polygon={true}
draw_toolbar_polyline={true}
showScaleY={true}
/>
</div>
</div>
);
}
}
Expand Down
2 changes: 0 additions & 2 deletions src/demo/example-data/layered-map.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
{
"map_bounds": [[432204, 6475078], [437720, 6481112]],
"center": [435200, 6478000],
"layers": [
{
"name": "A seismic horizon with colormap",
Expand Down
116 changes: 106 additions & 10 deletions src/lib/components/LayeredMap.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ const yx = ([x, y]) => {
return [y, x];
};

const _layeredmap_references = {};

class LayeredMap extends Component {
constructor(props) {
super(props);
Expand All @@ -27,6 +29,98 @@ class LayeredMap extends Component {
this.setState({ hillShading: !this.state.hillShading });
}

calculateBounds() {
const x_arr = [];
const y_arr = [];

this.props.layers.map(layer => {
layer.data.map(item => {
if (["polyline", "polygon"].includes(item.type)) {
item.positions.map(xy => {
x_arr.push(xy[0]);
y_arr.push(xy[1]);
});
} else if (item.type === "circle") {
x_arr.push(item.center[0] + item.radius);
x_arr.push(item.center[0] - item.radius);
y_arr.push(item.center[1] + item.radius);
y_arr.push(item.center[1] - item.radius);
} else if (item.type === "image") {
x_arr.push(item.bounds[0][0]);
x_arr.push(item.bounds[1][0]);
y_arr.push(item.bounds[0][1]);
y_arr.push(item.bounds[1][1]);
}
});
});

return [
[Math.min(...x_arr), Math.min(...y_arr)],
[Math.max(...x_arr), Math.max(...y_arr)],
];
}

resetView() {
const [[xmin, ymin], [xmax, ymax]] = this.calculateBounds();
const center = [0.5 * (xmin + xmax), 0.5 * (ymin + ymax)];

const width = this.mapRef.current.container.offsetWidth;
const height = this.mapRef.current.container.offsetHeight;

const initial_zoom = Math.min(
Math.log2(height / (ymax - ymin)),
Math.log2(width / (xmax - xmin))
);

this.mapRef.current.leafletElement.options.minZoom = initial_zoom - 2;
this.mapRef.current.leafletElement.setView(yx(center), initial_zoom);
}

setEvents() {
this.mapRef.current.leafletElement.on("zoomanim", ev => {
this.props.sync_ids
.filter(id => id !== this.props.id)
.map(id => {
if (_layeredmap_references[id].getZoom() !== ev.zoom) {
_layeredmap_references[id].setView(ev.center, ev.zoom);
}
});
});

this.mapRef.current.leafletElement.on("move", ev => {
this.props.sync_ids
.filter(id => id !== this.props.id)
.map(id => {
// Only react if move event is from a real user interaction
// (originalEvent is undefined if viewport is programatically changed).
if (typeof ev.originalEvent !== "undefined") {
_layeredmap_references[id].setView(
ev.target.getCenter()
);
}
});
});
}
componentDidMount() {
this.resetView();

this.setEvents();

_layeredmap_references[
this.props.id
] = this.mapRef.current.leafletElement;
}

componentWillUnmount() {
delete _layeredmap_references[this.props.id];
}

componentDidUpdate(prevProps) {
if (this.props.uirevision !== prevProps.uirevision) {
this.resetView();
}
}

render() {
const {
draw_toolbar_marker,
Expand Down Expand Up @@ -69,9 +163,6 @@ class LayeredMap extends Component {
id={this.props.id}
style={{ height: this.props.height }}
ref={this.mapRef}
center={yx(this.props.center)}
zoom={-3}
minZoom={-5}
attributionControl={false}
crs={CRS.Simple}
>
Expand Down Expand Up @@ -153,13 +244,15 @@ class LayeredMap extends Component {

LayeredMap.defaultProps = {
height: 800,
sync_ids: [],
hillShading: true,
lightDirection: [1, 1, 1],
scaleY: 1,
showScaleY: false,
draw_toolbar_marker: false,
draw_toolbar_polygon: false,
draw_toolbar_polyline: false,
uirevision: "",
};

LayeredMap.propTypes = {
Expand All @@ -171,14 +264,11 @@ LayeredMap.propTypes = {
id: PropTypes.string.isRequired,

/**
* Center [x, y] of map when initially loaded (in physical coordinates).
* IDs of other LayeredMap components which should be updated with same zoom/pan
* as in this one when the user changes zoom/pan in this component instance.
* For convenience, you can include the same ID as this instance (it will be ignored).
*/
center: PropTypes.array,

/**
* The map bounds of the input data, given as [[xmin, ymin], [xmax, ymax]] (in physical coordinates).
*/
map_bounds: PropTypes.array,
sync_ids: PropTypes.array,

/**
* The initial scale of the y axis (relative to the x axis).
Expand Down Expand Up @@ -254,6 +344,12 @@ LayeredMap.propTypes = {
layers: PropTypes.array,

hillShading: PropTypes.bool,

/**
* Following the same approach as Plotly Dash:
* If the string uireivision changes, reset the viewport.
*/
uirevision: PropTypes.string,
};

export default LayeredMap;
Expand Down

0 comments on commit ee521b9

Please sign in to comment.