diff --git a/package.json b/package.json index 3cee983..7383f7c 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,8 @@ "immutable": "^3.8.1", "mapbox-gl": "^0.24.0", "mapbox-gl-style-spec": "mapbox/mapbox-gl-style-spec#83b1a3e5837d785af582efd5ed1a212f2df6a4ae", + "ol-mapbox-style": "0.0.11", + "openlayers": "^3.19.1", "randomcolor": "^0.4.4", "react": "^15.4.0", "react-addons-pure-render-mixin": "^15.4.0", diff --git a/src/app.jsx b/src/app.jsx index fe63388..ed9c602 100644 --- a/src/app.jsx +++ b/src/app.jsx @@ -6,7 +6,8 @@ import Container from 'rebass/dist/Container' import Block from 'rebass/dist/Block' import Fixed from 'rebass/dist/Fixed' -import { Map } from './map.jsx' +import { MapboxGlMap } from './gl.jsx' +import { OpenLayers3Map } from './ol3.jsx' import {Toolbar} from './toolbar.jsx' import style from './style.js' import { loadDefaultStyle, SettingsStore, StyleStore } from './stylestore.js' @@ -116,7 +117,7 @@ export default class App extends React.Component { accessToken={this.state.accessToken} onAccessTokenChanged={this.onAccessTokenChanged.bind(this)} /> - diff --git a/src/gl.jsx b/src/gl.jsx new file mode 100644 index 0000000..965d899 --- /dev/null +++ b/src/gl.jsx @@ -0,0 +1,53 @@ +import React from 'react' +import MapboxGl from 'mapbox-gl'; +import { fullHeight } from './theme.js' +import style from './style.js' +import { Map } from './map.jsx' +import Immutable from 'immutable' +import validateColor from 'mapbox-gl-style-spec/lib/validate/validate_color' + +export class MapboxGlMap extends Map { + componentWillReceiveProps(nextProps) { + const tokenChanged = nextProps.accessToken !== MapboxGl.accessToken + + // If the id has changed a new style has been uplaoded and + // it is safer to do a full new render + // TODO: might already be handled in diff algorithm? + const mapIdChanged = this.props.mapStyle.get('id') !== nextProps.mapStyle.get('id') + + if(mapIdChanged || tokenChanged) { + this.state.map.setStyle(style.toJSON(nextProps.mapStyle)) + return + } + + // TODO: If there is no map yet we need to apply the changes later? + if(this.state.map) { + style.diffStyles(this.props.mapStyle, nextProps.mapStyle).forEach(change => { + + //TODO: Invalid outline color can cause map to freeze? + if(change.command === "setPaintProperty" && change.args[1] === "fill-outline-color" ) { + const value = change.args[2] + if(validateColor({value}).length > 0) { + return + } + } + + console.log(change.command, ...change.args) + this.state.map[change.command].apply(this.state.map, change.args); + }); + } + } + + componentDidMount() { + MapboxGl.accessToken = this.props.accessToken + + const map = new MapboxGl.Map({ + container: this.container, + style: style.toJSON(this.props.mapStyle), + }); + + map.on("style.load", (...args) => { + this.setState({ map }); + }); + } +} diff --git a/src/map.jsx b/src/map.jsx index e6634c0..5ab54e3 100644 --- a/src/map.jsx +++ b/src/map.jsx @@ -1,72 +1,24 @@ import React from 'react' -import MapboxGl from 'mapbox-gl'; import { fullHeight } from './theme.js' -import style from './style.js' import Immutable from 'immutable' -import validateColor from 'mapbox-gl-style-spec/lib/validate/validate_color' export class Map extends React.Component { - static propTypes = { - mapStyle: React.PropTypes.instanceOf(Immutable.Map).isRequired, - accessToken: React.PropTypes.string, - } + static propTypes = { + mapStyle: React.PropTypes.instanceOf(Immutable.Map).isRequired, + accessToken: React.PropTypes.string, + } - componentWillReceiveProps(nextProps) { - const hasTokenChanged = nextProps.accessToken !== MapboxGl.accessToken - MapboxGl.accessToken = nextProps.accessToken + shouldComponentUpdate(nextProps, nextState) { + //TODO: If we enable this React mixin for immutable comparison we can remove this? + return nextProps.mapStyle !== this.props.mapStyle + } - // If the id has changed a new style has been uplaoded and - // it is safer to do a full new render - // TODO: might already be handled in diff algorithm? - const mapIdChanged = this.props.mapStyle.get('id') !== nextProps.mapStyle.get('id') - - if(mapIdChanged || hasTokenChanged) { - this.state.map.setStyle(style.toJSON(nextProps.mapStyle)) - return - } - - // TODO: If there is no map yet we need to apply the changes later? - if(this.state.map) { - style.diffStyles(this.props.mapStyle, nextProps.mapStyle).forEach(change => { - - //TODO: Invalid outline color can cause map to freeze? - if(change.command === "setPaintProperty" && change.args[1] === "fill-outline-color" ) { - const value = change.args[2] - if(validateColor({value}).length > 0) { - return - } - } - - console.log(change.command, ...change.args) - this.state.map[change.command].apply(this.state.map, change.args); - }); - } - } - - shouldComponentUpdate(nextProps, nextState) { - //TODO: If we enable this React mixin for immutable comparison we can remove this? - return nextProps.mapStyle !== this.props.mapStyle - } - - componentDidMount() { - MapboxGl.accessToken = this.props.accessToken - - const map = new MapboxGl.Map({ - container: this.container, - style: style.toJSON(this.props.mapStyle), - }); - - map.on("style.load", (...args) => { - this.setState({ map }); - }); - } - - render() { - return
this.container = x} - style={{ - ...fullHeight, - width: "100%", - }}>
- } + render() { + return
this.container = x} + style={{ + ...fullHeight, + width: "100%", + }}>
+ } } diff --git a/src/ol3.jsx b/src/ol3.jsx new file mode 100644 index 0000000..903c1c4 --- /dev/null +++ b/src/ol3.jsx @@ -0,0 +1,48 @@ +import React from 'react' +import style from './style.js' +import { Map } from './map.jsx' +import ol from 'openlayers' +import olms from 'ol-mapbox-style' + +export class OpenLayers3Map extends Map { + constructor(props) { + super(props) + + const tilegrid = ol.tilegrid.createXYZ({tileSize: 512, maxZoom: 22}) + this.resolutions = tilegrid.getResolutions() + this.layer = new ol.layer.VectorTile({ + source: new ol.source.VectorTile({ + attributions: '© Mapbox ' + + '© ' + + 'OpenStreetMap contributors', + format: new ol.format.MVT(), + tileGrid: tilegrid, + tilePixelRatio: 8, + url: 'http://osm2vectortiles-0.tileserver.com/v2/{z}/{x}/{y}.pbf' + }) + }) + } + + componentWillReceiveProps(nextProps) { + const jsonStyle = style.toJSON(nextProps.mapStyle) + const styleFunc = olms.getStyleFunction(jsonStyle, 'mapbox', this.resolutions) + this.layer.setStyle(styleFunc) + this.state.map.render() + } + + + componentDidMount() { + const styleFunc = olms.getStyleFunction(style.toJSON(this.props.mapStyle), 'mapbox', this.resolutions) + this.layer.setStyle(styleFunc) + + const map = new ol.Map({ + target: this.container, + layers: [this.layer], + view: new ol.View({ + center: [949282, 6002552], + zoom: 4 + }) + }) + this.setState({ map }); + } +} diff --git a/webpack.production.config.js b/webpack.production.config.js index 8a9ef94..fbbfc71 100644 --- a/webpack.production.config.js +++ b/webpack.production.config.js @@ -8,88 +8,91 @@ var WebpackCleanupPlugin = require('webpack-cleanup-plugin'); // local css modules loaders.push({ - test: /[\/\\]src[\/\\].*\.css/, - loader: ExtractTextPlugin.extract('style', 'css?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]') + test: /[\/\\]src[\/\\].*\.css/, + loader: ExtractTextPlugin.extract('style', 'css?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]') }); // local scss modules loaders.push({ - test: /[\/\\]src[\/\\].*\.scss/, - loader: ExtractTextPlugin.extract('style', 'css?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]', 'sass') + test: /[\/\\]src[\/\\].*\.scss/, + loader: ExtractTextPlugin.extract('style', 'css?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]', 'sass') }); // global css files loaders.push({ - test: /[\/\\](node_modules|global)[\/\\].*\.css$/, - loader: ExtractTextPlugin.extract('style', 'css') + test: /[\/\\](node_modules|global)[\/\\].*\.css$/, + loader: ExtractTextPlugin.extract('style', 'css') }); module.exports = { - entry: { - app: './src/index.jsx', - vendor: [ - 'file-saver', - 'immutable', - 'mapbox-gl', - //TODO: Cannot resolve migrations file? - //"mapbox-gl-style-spec", - "randomcolor", - 'react', - "react-collapse", - "react-dom", - "react-file-reader-input", - "react-height", - //TODO: Icons raise multi vendor errors? - //"react-icons", - "react-motion", - "rebass", - ] - }, - output: { - path: path.join(__dirname, 'public'), - filename: '[chunkhash].app.js' - }, - resolve: { - alias: { - 'webworkify': 'webworkify-webpack', - }, - extensions: ['', '.js', '.jsx'] - }, - module: { - loaders, - postLoaders: [{ - include: /node_modules\/mapbox-gl-shaders/, - loader: 'transform', - query: 'brfs' - }] - }, - node: { - fs: "empty" - }, - plugins: [ - new webpack.NoErrorsPlugin(), - new webpack.optimize.CommonsChunkPlugin('vendor', '[chunkhash].vendor.js'), - new WebpackCleanupPlugin(), - new webpack.DefinePlugin({ - 'process.env': { - NODE_ENV: '"production"' - } - }), - new webpack.optimize.UglifyJsPlugin({ - compress: { - warnings: false, - screw_ie8: true, - drop_console: true, - drop_debugger: true - } - }), - new webpack.optimize.OccurenceOrderPlugin(), - new ExtractTextPlugin('[contenthash].css', { - allChunks: true - }), - new HtmlWebpackPlugin({ - template: './src/template.html', - title: 'Maputnik' - }), - new webpack.optimize.DedupePlugin() - ] + entry: { + app: './src/index.jsx', + vendor: [ + 'file-saver', + 'immutable', + 'mapbox-gl', + //TODO: Cannot resolve migrations file? + //"mapbox-gl-style-spec", + "randomcolor", + 'react', + "react-collapse", + "react-dom", + "react-file-reader-input", + "react-height", + //TODO: Icons raise multi vendor errors? + //"react-icons", + "react-motion", + "rebass", + // Open Layers + 'openlayers', + 'ol-mapbox-style' + ] + }, + output: { + path: path.join(__dirname, 'public'), + filename: '[chunkhash].app.js' + }, + resolve: { + alias: { + 'webworkify': 'webworkify-webpack', + }, + extensions: ['', '.js', '.jsx'] + }, + module: { + loaders, + postLoaders: [{ + include: /node_modules\/mapbox-gl-shaders/, + loader: 'transform', + query: 'brfs' + }] + }, + node: { + fs: "empty" + }, + plugins: [ + new webpack.NoErrorsPlugin(), + new webpack.optimize.CommonsChunkPlugin('vendor', '[chunkhash].vendor.js'), + new WebpackCleanupPlugin(), + new webpack.DefinePlugin({ + 'process.env': { + NODE_ENV: '"production"' + } + }), + new webpack.optimize.UglifyJsPlugin({ + compress: { + warnings: false, + screw_ie8: true, + drop_console: true, + drop_debugger: true + } + }), + new webpack.optimize.OccurenceOrderPlugin(), + new ExtractTextPlugin('[contenthash].css', { + allChunks: true + }), + new HtmlWebpackPlugin({ + template: './src/template.html', + title: 'Maputnik' + }), + new webpack.optimize.DedupePlugin() + ] };