background tiles

This commit is contained in:
Alfred Melch 2019-05-17 18:05:58 +02:00
parent a23905c826
commit e1daee038a
11 changed files with 599 additions and 60 deletions

View File

@ -0,0 +1,7 @@
# Wundertile
## Other sketch libs:
* sketchpad 5.1: https://sketch.io/sketchpad/
* wpaint: http://wpaint.websanova.com/ (jQuery)
* sketchpad: https://yiom.github.io/sketchpad/ (animation)
* literally canvas: http://literallycanvas.com/

View File

@ -18,8 +18,8 @@ tiles_bp = Blueprint('tiles', __name__)
def valid_tile(z, x, y): def valid_tile(z, x, y):
"""Returns true if the tile coordinates are valid""" """Returns true if the tile coordinates are valid"""
# check z in [0, 22] # check z in [0, 14]
if z < 0 or z > 22: if z < 0 or z > 14:
return False return False
# check x in [0, 2^zoom[ # check x in [0, 2^zoom[
if x < 0 or x >= 2**z: if x < 0 or x >= 2**z:

File diff suppressed because one or more lines are too long

View File

@ -35,7 +35,34 @@ main {
height: 256px; height: 256px;
} }
.transparent-tile-bg {
background: url('/static/transparentBg.png');
}
.current-tile-upload { .current-tile-upload {
display: flex; display: flex;
justify-content: space-around; justify-content: space-around;
flex-wrap: wrap;
} }
canvas {
cursor: crosshair;
}
.opacity-control {
display: flex;
align-items: center;
background-color: white;
padding: 5px;
}
.background-image {
position: absolute;
/* top: 0; */
/* left: 0; */
/* bottom: 0; */
/* right: 0; */
width: 256px;
height: 256px;
/* z-index: -1; */
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 386 B

View File

@ -21,15 +21,11 @@ class CurrentTileComponent extends Component {
</div> </div>
) )
} }
handleClick (e) {
console.log(this.scratchpad.image)
}
} }
@observer
class SubmitDrawingForm extends Component { class SubmitDrawingForm extends Component {
render (props) { render (props) {
console.log('submDrawing', props)
return ( return (
( (
<div className='uploadForm'> <div className='uploadForm'>
@ -47,14 +43,14 @@ class SubmitDrawingForm extends Component {
) )
} }
handleSubmit (e) { handleSubmit (e) {
console.log(this.file, this.scratchpad)
this.file.value = this.scratchpad.image this.file.value = this.scratchpad.image
} }
} }
class UploadForm extends Component { class UploadForm extends Component {
render (props) { render (props) {
console.log(props)
return ( return (
<div className='uploadForm'> <div className='uploadForm'>
<form action={props.tilePath} method='POST' enctype='multipart/form-data'> <form action={props.tilePath} method='POST' enctype='multipart/form-data'>

View File

@ -1,32 +1,53 @@
import { map as LMap, tileLayer } from 'leaflet' import { reaction } from 'mobx'
import { map as LMap, control } from 'leaflet'
import 'leaflet/dist/leaflet.css' import 'leaflet/dist/leaflet.css'
import state from './state.js' import state from './state.js'
import { OpacityControl } from './opacityControl.js'
import { wundertiles, baseLayers } from './tileLayers.js'
export const map = LMap('map') export const map = LMap('map', {
maxZoom: 14
})
map.setView([0, 0], 1) map.setView([0, 0], 1)
// background tilelayer (OSM) control.layers(baseLayers).addTo(map)
tileLayer('https://{s}.tile.openstreetmap.de/tiles/osmde/{z}/{x}/{y}.png', { control.scale().addTo(map)
maxZoom: 18, new OpacityControl({ 'position': 'bottomright' }).addTo(map)
attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors', wundertiles.addTo(map)
opacity: 0.9
}).addTo(map)
// wundertiles tilelayer
tileLayer('/tiles/{z}/{x}/{y}', {
attribution: '',
maxZoom: 22
}).addTo(map)
// compute Tile Coordinates // compute Tile Coordinates
const toRad = (num) => num * Math.PI / 180 const toRad = (num) => num * Math.PI / 180
const modulo = (n, mod) => ((n % mod) + mod) % mod
const getTileCoords = (lat, lon, zoom) => { const getTileCoords = (lat, lon, zoom) => {
let coordMax = Math.pow(2, zoom) - 1
let xtile = parseInt(Math.floor((lon + 180) / 360 * (1 << zoom))) let xtile = parseInt(Math.floor((lon + 180) / 360 * (1 << zoom)))
let ytile = parseInt(Math.floor((1 - Math.log(Math.tan(toRad(lat)) + 1 / Math.cos(toRad(lat))) / Math.PI) / 2 * (1 << zoom))) let ytile = parseInt(Math.floor((1 - Math.log(Math.tan(toRad(lat)) + 1 / Math.cos(toRad(lat))) / Math.PI) / 2 * (1 << zoom)))
xtile = modulo(xtile, coordMax + 1)
ytile = modulo(ytile, coordMax + 1)
return { x: xtile, y: ytile, z: zoom } return { x: xtile, y: ytile, z: zoom }
} }
// demo: how to get tile coords on click // get tile coords on click
map.on('click', (e) => { map.on('click', (e) => {
state.coords = getTileCoords(e.latlng.lat, e.latlng.lng, map.getZoom()) state.coords = getTileCoords(e.latlng.lat, e.latlng.lng, map.getZoom())
}) })
// react on base layer change
map.on('baselayerchange', (e) => {
state.backgroundTileServerUrl = e.layer._url
wundertiles.bringToFront()
e.layer.setOpacity(state.backgroundOpacity)
})
// react on opacity change
reaction(
() => state.backgroundOpacity,
opacity => {
map.eachLayer((l) => {
if (l.options.name !== 'wundertiles') {
l.setOpacity(opacity)
}
})
}
)

31
web/src/opacityControl.js Normal file
View File

@ -0,0 +1,31 @@
import { Control, DomUtil, DomEvent } from 'leaflet'
import state from './state.js'
export class OpacityControl extends Control {
onAdd (map) {
const container = DomUtil.create('div', 'leaflet-bar leaflet-control opacity-control')
container.setAttribute('id', 'opacityControl')
const label = DomUtil.create('label', '', container)
label.innerText = 'Background opacity: '
const slider = DomUtil.create('input', '', container)
slider.setAttribute('type', 'range')
slider.addEventListener('input', (e) => {
state.backgroundOpacity = Number(e.target.value) / 100
})
// container.innerHTML = `<label>Background opacity: </label><input type="range">`
// prevent map dragging when mouse is over control.
// Otherwise slider-adjustments would pan map.
DomEvent.on(container, 'mousedown mouseup click touchstart', DomEvent.stopPropagation)
DomEvent.on(container, 'mouseenter', (e) => { map.dragging.disable() })
DomEvent.on(container, 'mouseleave', (e) => { map.dragging.enable() })
return container
}
test () {
console.log('lala')
}
}

View File

@ -1,24 +1,61 @@
import { Component, h } from 'preact' import { Component, h } from 'preact'
import { observer } from 'mobx-preact'
import state from './state.js'
@observer
export class Scratchpad extends Component { export class Scratchpad extends Component {
draw = false draw = false
render () { render () {
let bgTileStyele = {
backgroundImage: `url(${state.backgroundTileUrl})`,
opacity: state.backgroundOpacity,
}
return ( return (
<div> <div>
<canvas <div className='tile-image' style={{ position: '' }}>
width='256' height='256' <div className='background-image transparent-tile-bg' />
className='tile-image' <div className='background-image' style={bgTileStyele} />
onMouseDown={(e) => this.handleMouseDown(e)} <canvas
onMouseMove={(e) => this.handleMouseMove(e)} className='background-image'
onMouseUp={(e) => this.handleMouseUp(e)} width='256' height='256'
onMouseOut={(e) => this.handleMouseOut(e)} onMouseDown={(e) => this.handleMouseDown(e)}
ref={c => { this.canvas = c }} onMouseMove={(e) => this.handleMouseMove(e)}
/> onMouseUp={(e) => this.handleMouseUp(e)}
onMouseOut={(e) => this.handleMouseOut(e)}
onMouseEnter={(e) => this.handleMouseEnter(e)}
onTouchStart={(e) => this.handleTouchStart(e)}
onTouchEnd={(e) => this.handleTouchEnd(e)}
onTouchMove={(e) => this.handleTouchMove(e)}
ref={c => { this.canvas = c }}
style={{ position: 'absolute'}}
/>
</div>
<br />
<label>Thickness: </label>
<input
type='range'
onChange={(e) => this.handleThicknessInput(e)}
min='1' max='100' value='10' />
<br />
<label>Color: </label>
<input
type='color'
onChange={(e) => this.handleColorChange(e)} />
<br />
<button onClick={() => this.clear()}>Clear</button>
</div> </div>
) )
} }
componentDidMount () {
this.ctx.lineWidth = 10
this.ctx.lineJoin = 'round'
this.ctx.lineCap = 'round'
}
get ctx () { get ctx () {
return this.canvas.getContext('2d') return this.canvas.getContext('2d')
} }
@ -27,21 +64,72 @@ export class Scratchpad extends Component {
return this.canvas.toDataURL() return this.canvas.toDataURL()
} }
handleMouseDown (e) { getCoords (e) {
this.draw = true // handle touchevents
this.ctx.beginPath() e = e.touches ? e.touches[0] : e
this.ctx.moveTo(e.layerX, e.layerY) return {
x: e.pageX - this.canvas.offsetLeft,
y: e.pageY - this.canvas.offsetTop
}
} }
handleMouseDown (e) {
let { x, y } = this.getCoords(e)
this.draw = true
this.ctx.lineWidth = this.thickness
this.ctx.beginPath()
this.ctx.moveTo(x, y)
}
handleMouseMove (e) { handleMouseMove (e) {
if (this.draw === true) { if (this.draw === true) {
this.ctx.lineTo(e.layerX, e.layerY) let { x, y } = this.getCoords(e)
this.ctx.lineTo(x, y)
this.ctx.stroke() this.ctx.stroke()
} }
} }
handleMouseUp (e) { handleMouseUp (e) {
this.draw = false this.draw = false
} }
handleMouseOut (e) {
handleMouseOut (e) {
this.handleMouseUp(e)
}
handleMouseEnter (e) {
if (e.buttons) {
this.handleMouseDown(e)
}
}
handleThicknessInput (e) {
this.ctx.lineWidth = parseInt(e.target.value)
}
handleColorChange (e) {
this.ctx.strokeStyle = e.target.value
}
handleTouchStart (e) {
if (!e.cancelable) return
e.preventDefault()
this.handleMouseDown(e)
}
handleTouchEnd (e) {
if (!e.cancelable) return
e.preventDefault()
this.handleMouseUp(e)
}
handleTouchMove (e) {
if (!e.cancelable) return
e.preventDefault()
this.handleMouseMove(e)
}
clear () {
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height)
} }
} }

View File

@ -1,10 +1,18 @@
import { observable } from 'mobx' import { observable, computed } from 'mobx'
class State { class State {
@observable coords = { x: 0, y: 0, z: 0 } @observable coords = { x: 0, y: 0, z: 0 }
@observable backgroundTileServerUrl = ''
@observable backgroundOpacity = 0.5
setState (newState) { @computed
this.state = { ...this.state, ...newState } get backgroundTileUrl () {
let { x, y, z } = this.coords
let url = this.backgroundTileServerUrl.replace('{s}', 'a')
url = url.replace('{x}', x)
url = url.replace('{y}', y)
url = url.replace('{z}', z)
return url
} }
} }

27
web/src/tileLayers.js Normal file
View File

@ -0,0 +1,27 @@
import { tileLayer } from 'leaflet'
const osmDE = tileLayer('https://{s}.tile.openstreetmap.de/tiles/osmde/{z}/{x}/{y}.png', {
maxZoom: 18,
attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
})
const osmMap = tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 19,
attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
})
const esriSat = tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', {
attribution: 'Tiles &copy; Esri &mdash; Source: Esri, i-cubed, USDA, USGS, AEX, GeoEye, Getmapping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community'
})
export const wundertiles = tileLayer('/tiles/{z}/{x}/{y}', {
name: 'wundertiles',
attribution: '',
maxZoom: 14
})
export const baseLayers = {
'OSM DE': osmDE,
'OSM Map': osmMap,
'ESRI Sattelite': esriSat
}