background tiles
This commit is contained in:
		
							parent
							
								
									a23905c826
								
							
						
					
					
						commit
						e1daee038a
					
				| @ -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/ | ||||||
| @ -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
											
										
									
								
							| @ -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; */ | ||||||
|  | } | ||||||
							
								
								
									
										
											BIN
										
									
								
								app/static/transparentBg.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								app/static/transparentBg.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 386 B | 
| @ -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'> | ||||||
|  | |||||||
| @ -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: '© <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
									
								
							
							
						
						
									
										31
									
								
								web/src/opacityControl.js
									
									
									
									
									
										Normal 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') | ||||||
|  |   } | ||||||
|  | } | ||||||
| @ -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) | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  | |||||||
| @ -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
									
								
							
							
						
						
									
										27
									
								
								web/src/tileLayers.js
									
									
									
									
									
										Normal 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: '© <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: '© <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 © Esri — 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 | ||||||
|  | } | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user