Skip to content

Commit

Permalink
Add hillshading props (#54)
Browse files Browse the repository at this point in the history
  • Loading branch information
anders-kiaer authored Sep 21, 2019
1 parent f8eac7d commit 7eb7c92
Show file tree
Hide file tree
Showing 7 changed files with 52 additions and 20 deletions.
1 change: 1 addition & 0 deletions examples/example_layered_map.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ def array_to_png(Z, shift=True, colormap=False):
{
'type': 'image',
'url': map_data,
'allowHillshading': True,
'colormap': colormap,
'unit': 'm',
'minvalue': min_value,
Expand Down
2 changes: 1 addition & 1 deletion src/demo/example-data/layered-map.json

Large diffs are not rendered by default.

26 changes: 22 additions & 4 deletions src/lib/components/LayeredMap.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ class LayeredMap extends Component {
render() {
const {draw_toolbar_marker, draw_toolbar_polygon, draw_toolbar_polyline, setProps} = this.props
const showDrawControls = (draw_toolbar_marker || draw_toolbar_polygon || draw_toolbar_polyline) ? true : false
const showHillshadingSwitch = this.props.layers.some(layer => layer.data.some(item => item.allowHillshading))

return (
<Map id={this.props.id} style={{height: this.props.height}}
ref={this.mapRef}
Expand All @@ -41,20 +43,24 @@ class LayeredMap extends Component {
<VerticalZoom position='topleft' scaleY={this.props.scaleY} minScaleY={1} maxScaleY={10} />
}
<ScaleControl position='bottomright' imperial={false} metric={true} />
<Switch position='topright' label='Hillshading' checked={this.props.hillShading} onChange={this.handleHillshadingChange.bind(this)}/>
<LayersControl position='topright'>
{this.props.layers.filter(layer => layer.base_layer).map((layer) => (
<BaseLayer checked={layer.checked} name={layer.name} key={layer.name}>
<CompositeMapLayer layer={layer} hillShading={this.state.hillShading} />
<CompositeMapLayer
layer={layer}
hillShading={this.state.hillShading}
lightDirection={this.props.lightDirection}
/>
</BaseLayer>
))}
{this.props.layers.filter(layer => !layer.base_layer).map((layer) => (
<Overlay checked={layer.checked} name={layer.name} key={layer.name}>
<CompositeMapLayer
lineCoords={(coords) => setProps({'polyline_points': coords})}
polygonCoords={(coords) => setProps({'polygon_points': coords})}
layer={layer}
hillShading={this.state.hillShading}
lightDirection={this.props.lightDirection}
lineCoords={(coords) => setProps({'polyline_points': coords})}
polygonCoords={(coords) => setProps({'polygon_points': coords})}
/>
</Overlay>
))}
Expand All @@ -71,6 +77,9 @@ class LayeredMap extends Component {
/>
</FeatureGroup>
)}
{ showHillshadingSwitch &&
<Switch position='bottomleft' label='Hillshading' checked={this.props.hillShading} onChange={this.handleHillshadingChange.bind(this)} />
}
</Map>
);
}
Expand All @@ -79,6 +88,7 @@ class LayeredMap extends Component {
LayeredMap.defaultProps = {
height: 800,
hillShading: true,
lightDirection: [1, 1, 1],
scaleY: 1,
showScaleY: false,
draw_toolbar_marker: false,
Expand Down Expand Up @@ -155,6 +165,11 @@ LayeredMap.propTypes = {
*/
marker_point: PropTypes.array,

/**
* Light direction.
*/
lightDirection: PropTypes.array,

/**
* Dash-assigned callback that should be called whenever any of the
* properties change
Expand Down Expand Up @@ -212,7 +227,10 @@ export default LayeredMap;
* {
* 'type': 'image',
* 'url': either base64 encoding of the picture or a path to hosted image,
* 'colormap': optional - base64 encoding of a 256 x 1 picture representing the colormap
* 'bounds': [[xmin, ymin], // The extent of the picture in the rendered map
* [xmax, ymax]]
'allowHillshading': false // optional - if the image is to have hill shading or not (false is default)
'elevationScale': scale // optional - see ../private-components/layered-map-resources/ImageOverlayWebGL for definition
* }
*/
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,9 @@ class CompositeMapLayer extends Component {
url={item.url}
colormap={item.colormap}
bounds={item.bounds.map(xy => yx(xy))}
hillShading={this.props.hillShading}
hillShading={this.props.hillShading && item.allowHillshading}
elevationScale={item.elevationScale || 0.03}
lightDirection={this.props.lightDirection}
key={index}/>
{ 'colormap' in item && <Colormap colormap={item.colormap} unit={item.unit} minvalue={item.minvalue} maxvalue={item.maxvalue} position='bottomleft' key={'colormap' + index} /> }
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,33 @@ class ImageOverlayWebGL extends Component {
return <ImageOverlay url={this.props.url} bounds={this.props.bounds} />
}
return <CanvasOverlay
drawMethod={(canvas) => alter_image(this.props.url, this.props.colormap, this.props.hillShading, canvas)}
drawMethod={(canvas) => alter_image(canvas, this.props.url, this.props.colormap, this.props.hillShading, this.props.elevationScale, this.props.lightDirection)}
bounds={this.props.bounds}
/>
}

}

ImageOverlayWebGL.propTypes = {
url: PropTypes.string,
colormap: PropTypes.string,
bounds: PropTypes.array,
hillShading: PropTypes.bool
url: PropTypes.string,
colormap: PropTypes.string,
bounds: PropTypes.array,
hillShading: PropTypes.bool,

/**
* Used in hillshading. Dictates relative ratio between vertical elevation
* axis (z) and horizontal axes (x and y). The correct physical value would
* be |(max z - min z) * (width image) / (max x - min x)|, or equivalently
* be |(max z - min z) * (height image) / (max y - min y)|.
* Note however that it is not crucial that the value is physically correct,
* as the value here can be seen as an artistic choice.
*/
elevationScale: PropTypes.number,

/**
* Light direction (array of length 3), used when hillShading is true.
*/
lightDirection: PropTypes.array
}

export default ImageOverlayWebGL;
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import vertexShaderSource from './vertexShader.vs.glsl'
import fragmentShaderSourceWithHillshading from './fragmentShaderWithHillshading.fs.glsl'
import fragmentShaderSourceWithoutHillshading from './fragmentShaderWithoutHillshading.fs.glsl'

function alter_image(map_base64, colormap_base64, hillshading, canvas){
function alter_image(canvas, map_base64, colormap_base64, hillshading, elevation_scale, light_direction){

const createShader = (gl, shaderType, shaderSource) => {
/**
Expand Down Expand Up @@ -141,7 +141,6 @@ function alter_image(map_base64, colormap_base64, hillshading, canvas){
gl.canvas.height
)

const light_direction = [1, 1, 1]
const vectorLength = Math.sqrt(light_direction[0]**2 + light_direction[1]**2 + light_direction[2]**2)

gl.uniform3f(
Expand All @@ -151,10 +150,9 @@ function alter_image(map_base64, colormap_base64, hillshading, canvas){
light_direction[2] / vectorLength
)

const elevationScale = 0.03
gl.uniform1f(
gl.getUniformLocation(program, 'u_elevation_scale'),
elevationScale
elevation_scale
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,11 @@ uniform vec3 u_light_direction;
varying vec2 v_texCoord;

void main() {
vec2 dl = 1.0/u_resolution_fragment;

vec2 pixelPos = vec2(gl_FragCoord.x, u_resolution_fragment.y - gl_FragCoord.y);

float v0 = texture2D(u_image, dl * pixelPos).r;
float vx = texture2D(u_image, dl * (pixelPos + vec2(1.0, 0.0))).r;
float vy = texture2D(u_image, dl * (pixelPos + vec2(0.0, 1.0))).r;
float v0 = texture2D(u_image, pixelPos / u_resolution_fragment).r;
float vx = texture2D(u_image, (pixelPos + vec2(1.0, 0.0)) / u_resolution_fragment).r;
float vy = texture2D(u_image, (pixelPos + vec2(0.0, 1.0)) / u_resolution_fragment).r;

// Create tangent vector components along terrain
// in x and y directions respectively:
Expand Down

0 comments on commit 7eb7c92

Please sign in to comment.