mirror of
https://github.com/google-map-react/google-map-react.git
synced 2025-12-08 18:26:32 +00:00
new: google map react
This commit is contained in:
commit
8fdc6dd692
232
.eslintrc
Normal file
232
.eslintrc
Normal file
@ -0,0 +1,232 @@
|
||||
{
|
||||
"parser": 'babel-eslint',
|
||||
"env": {
|
||||
"browser": true,
|
||||
"node": true,
|
||||
"es6": true
|
||||
},
|
||||
"globals": {
|
||||
"__DEV__": false
|
||||
},
|
||||
"ecmaFeatures": {
|
||||
"arrowFunctions": true,
|
||||
"binaryLiterals": true,
|
||||
"blockBindings": true,
|
||||
"classes": true,
|
||||
"defaultParams": true,
|
||||
"destructuring": true,
|
||||
"forOf": true,
|
||||
"generators": true,
|
||||
"modules": true,
|
||||
"objectLiteralComputedProperties": true,
|
||||
"objectLiteralDuplicateProperties": true,
|
||||
"objectLiteralShorthandMethods": true,
|
||||
"objectLiteralShorthandProperties": true,
|
||||
"octalLiterals": true,
|
||||
"regexUFlag": true,
|
||||
"regexYFlag": true,
|
||||
"spread": true,
|
||||
"superInFunctions": true,
|
||||
"templateStrings": true,
|
||||
"unicodeCodePointEscapes": true,
|
||||
"globalReturn": true,
|
||||
"jsx": true
|
||||
},
|
||||
"plugins": [
|
||||
"react"
|
||||
],
|
||||
"rules": {
|
||||
"react/display-name": 0,
|
||||
"react/jsx-boolean-value": 1,
|
||||
"react/jsx-quotes": 1,
|
||||
"react/jsx-no-undef": 1,
|
||||
"react/jsx-sort-props": 0,
|
||||
"react/jsx-sort-prop-types": 0,
|
||||
"react/jsx-uses-react": 1,
|
||||
"react/jsx-uses-vars": 1,
|
||||
"react/no-did-mount-set-state": 1,
|
||||
"react/no-did-update-set-state": 1,
|
||||
"react/no-multi-comp": 1,
|
||||
"react/no-unknown-property": 1,
|
||||
"react/prop-types": 1,
|
||||
"react/react-in-jsx-scope": 1,
|
||||
"react/self-closing-comp": 1,
|
||||
"react/wrap-multilines": 1,
|
||||
/**
|
||||
* Strict mode
|
||||
*/
|
||||
// babel inserts "use strict"; for us
|
||||
// http://eslint.org/docs/rules/strict
|
||||
"strict": [2, "never"],
|
||||
/**
|
||||
* ES6
|
||||
*/
|
||||
"no-var": 2, // http://eslint.org/docs/rules/no-var
|
||||
|
||||
/**
|
||||
* Variables
|
||||
*/
|
||||
"no-shadow": 1, // http://eslint.org/docs/rules/no-shadow
|
||||
"no-shadow-restricted-names": 2, // http://eslint.org/docs/rules/no-shadow-restricted-names
|
||||
"no-unused-vars": [
|
||||
1,
|
||||
{
|
||||
// http://eslint.org/docs/rules/no-unused-vars
|
||||
"vars": "local",
|
||||
"args": "after-used"
|
||||
}
|
||||
],
|
||||
"no-use-before-define": 2, // http://eslint.org/docs/rules/no-use-before-define
|
||||
|
||||
/**
|
||||
* Possible errors
|
||||
*/
|
||||
"comma-dangle": [2, "never"], // http://eslint.org/docs/rules/comma-dangle
|
||||
"no-cond-assign": [2, "always"], // http://eslint.org/docs/rules/no-cond-assign
|
||||
"no-console": 1, // http://eslint.org/docs/rules/no-console
|
||||
"no-debugger": 1, // http://eslint.org/docs/rules/no-debugger
|
||||
"no-alert": 1, // http://eslint.org/docs/rules/no-alert
|
||||
"no-constant-condition": 1, // http://eslint.org/docs/rules/no-constant-condition
|
||||
"no-dupe-keys": 2, // http://eslint.org/docs/rules/no-dupe-keys
|
||||
"no-duplicate-case": 2, // http://eslint.org/docs/rules/no-duplicate-case
|
||||
"no-empty": 2, // http://eslint.org/docs/rules/no-empty
|
||||
"no-ex-assign": 2, // http://eslint.org/docs/rules/no-ex-assign
|
||||
"no-extra-boolean-cast": 0, // http://eslint.org/docs/rules/no-extra-boolean-cast
|
||||
"no-extra-semi": 2, // http://eslint.org/docs/rules/no-extra-semi
|
||||
"no-func-assign": 2, // http://eslint.org/docs/rules/no-func-assign
|
||||
"no-inner-declarations": 2, // http://eslint.org/docs/rules/no-inner-declarations
|
||||
"no-invalid-regexp": 2, // http://eslint.org/docs/rules/no-invalid-regexp
|
||||
"no-irregular-whitespace": 2, // http://eslint.org/docs/rules/no-irregular-whitespace
|
||||
"no-obj-calls": 2, // http://eslint.org/docs/rules/no-obj-calls
|
||||
"no-reserved-keys": 2, // http://eslint.org/docs/rules/no-reserved-keys
|
||||
"no-sparse-arrays": 2, // http://eslint.org/docs/rules/no-sparse-arrays
|
||||
"no-unreachable": 2, // http://eslint.org/docs/rules/no-unreachable
|
||||
"use-isnan": 2, // http://eslint.org/docs/rules/use-isnan
|
||||
"block-scoped-var": 2, // http://eslint.org/docs/rules/block-scoped-var
|
||||
|
||||
/**
|
||||
* Best practices
|
||||
*/
|
||||
"consistent-return": 2, // http://eslint.org/docs/rules/consistent-return
|
||||
"curly": [2, "multi-line"], // http://eslint.org/docs/rules/curly
|
||||
"default-case": 2, // http://eslint.org/docs/rules/default-case
|
||||
"dot-notation": [
|
||||
2,
|
||||
{
|
||||
// http://eslint.org/docs/rules/dot-notation
|
||||
"allowKeywords": true
|
||||
}
|
||||
],
|
||||
"eqeqeq": 2, // http://eslint.org/docs/rules/eqeqeq
|
||||
"guard-for-in": 2, // http://eslint.org/docs/rules/guard-for-in
|
||||
"no-caller": 2, // http://eslint.org/docs/rules/no-caller
|
||||
"no-else-return": 2, // http://eslint.org/docs/rules/no-else-return
|
||||
"no-eq-null": 2, // http://eslint.org/docs/rules/no-eq-null
|
||||
"no-eval": 2, // http://eslint.org/docs/rules/no-eval
|
||||
"no-extend-native": 2, // http://eslint.org/docs/rules/no-extend-native
|
||||
"no-extra-bind": 2, // http://eslint.org/docs/rules/no-extra-bind
|
||||
"no-fallthrough": 2, // http://eslint.org/docs/rules/no-fallthrough
|
||||
"no-floating-decimal": 2, // http://eslint.org/docs/rules/no-floating-decimal
|
||||
"no-implied-eval": 2, // http://eslint.org/docs/rules/no-implied-eval
|
||||
"no-lone-blocks": 2, // http://eslint.org/docs/rules/no-lone-blocks
|
||||
"no-loop-func": 2, // http://eslint.org/docs/rules/no-loop-func
|
||||
"no-multi-str": 2, // http://eslint.org/docs/rules/no-multi-str
|
||||
"no-native-reassign": 2, // http://eslint.org/docs/rules/no-native-reassign
|
||||
"no-new": 2, // http://eslint.org/docs/rules/no-new
|
||||
"no-new-func": 2, // http://eslint.org/docs/rules/no-new-func
|
||||
"no-new-wrappers": 2, // http://eslint.org/docs/rules/no-new-wrappers
|
||||
"no-octal": 2, // http://eslint.org/docs/rules/no-octal
|
||||
"no-octal-escape": 2, // http://eslint.org/docs/rules/no-octal-escape
|
||||
"no-param-reassign": 2, // http://eslint.org/docs/rules/no-param-reassign
|
||||
"no-proto": 2, // http://eslint.org/docs/rules/no-proto
|
||||
"no-redeclare": 2, // http://eslint.org/docs/rules/no-redeclare
|
||||
"no-return-assign": 2, // http://eslint.org/docs/rules/no-return-assign
|
||||
"no-script-url": 2, // http://eslint.org/docs/rules/no-script-url
|
||||
"no-self-compare": 2, // http://eslint.org/docs/rules/no-self-compare
|
||||
"no-sequences": 2, // http://eslint.org/docs/rules/no-sequences
|
||||
"no-throw-literal": 2, // http://eslint.org/docs/rules/no-throw-literal
|
||||
"no-with": 2, // http://eslint.org/docs/rules/no-with
|
||||
"radix": 2, // http://eslint.org/docs/rules/radix
|
||||
"vars-on-top": 2, // http://eslint.org/docs/rules/vars-on-top
|
||||
"wrap-iife": [2, "any"], // http://eslint.org/docs/rules/wrap-iife
|
||||
"yoda": 2, // http://eslint.org/docs/rules/yoda
|
||||
|
||||
/**
|
||||
* Style
|
||||
*/
|
||||
"indent": [2, 2], // http://eslint.org/docs/rules/
|
||||
"brace-style": [
|
||||
2, // http://eslint.org/docs/rules/brace-style
|
||||
"1tbs",
|
||||
{
|
||||
"allowSingleLine": true
|
||||
}
|
||||
],
|
||||
"quotes": [
|
||||
2, "single", "avoid-escape" // http://eslint.org/docs/rules/quotes
|
||||
],
|
||||
"camelcase": [
|
||||
2,
|
||||
{
|
||||
// http://eslint.org/docs/rules/camelcase
|
||||
"properties": "never"
|
||||
}
|
||||
],
|
||||
"comma-spacing": [
|
||||
2,
|
||||
{
|
||||
// http://eslint.org/docs/rules/comma-spacing
|
||||
"before": false,
|
||||
"after": true
|
||||
}
|
||||
],
|
||||
"comma-style": [2, "last"], // http://eslint.org/docs/rules/comma-style
|
||||
"eol-last": 2, // http://eslint.org/docs/rules/eol-last
|
||||
"func-names": 1, // http://eslint.org/docs/rules/func-names
|
||||
"key-spacing": [
|
||||
2,
|
||||
{
|
||||
// http://eslint.org/docs/rules/key-spacing
|
||||
"beforeColon": false,
|
||||
"afterColon": true
|
||||
}
|
||||
],
|
||||
"new-cap": [
|
||||
2,
|
||||
{
|
||||
// http://eslint.org/docs/rules/new-cap
|
||||
"newIsCap": true
|
||||
}
|
||||
],
|
||||
"no-multiple-empty-lines": [
|
||||
2,
|
||||
{
|
||||
// http://eslint.org/docs/rules/no-multiple-empty-lines
|
||||
"max": 2
|
||||
}
|
||||
],
|
||||
"no-nested-ternary": 2, // http://eslint.org/docs/rules/no-nested-ternary
|
||||
"no-new-object": 2, // http://eslint.org/docs/rules/no-new-object
|
||||
"no-spaced-func": 2, // http://eslint.org/docs/rules/no-spaced-func
|
||||
"no-trailing-spaces": 2, // http://eslint.org/docs/rules/no-trailing-spaces
|
||||
"no-wrap-func": 2, // http://eslint.org/docs/rules/no-wrap-func
|
||||
"no-underscore-dangle": 0, // http://eslint.org/docs/rules/no-underscore-dangle
|
||||
"one-var": [2, "never"], // http://eslint.org/docs/rules/one-var
|
||||
"padded-blocks": [2, "never"], // http://eslint.org/docs/rules/padded-blocks
|
||||
"semi": [2, "always"], // http://eslint.org/docs/rules/semi
|
||||
"semi-spacing": [
|
||||
2,
|
||||
{
|
||||
// http://eslint.org/docs/rules/semi-spacing
|
||||
"before": false,
|
||||
"after": true
|
||||
}
|
||||
],
|
||||
"space-after-keywords": 2, // http://eslint.org/docs/rules/space-after-keywords
|
||||
"space-before-blocks": 2, // http://eslint.org/docs/rules/space-before-blocks
|
||||
"space-before-function-paren": [2, "never"], // http://eslint.org/docs/rules/space-before-function-paren
|
||||
"space-infix-ops": 2, // http://eslint.org/docs/rules/space-infix-ops
|
||||
"space-return-throw-case": 2, // http://eslint.org/docs/rules/space-return-throw-case
|
||||
"spaced-line-comment": 2 // http://eslint.org/docs/rules/spaced-line-comment
|
||||
}
|
||||
}
|
||||
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/node_modules
|
||||
/modules
|
||||
6
.npmignore
Normal file
6
.npmignore
Normal file
@ -0,0 +1,6 @@
|
||||
src
|
||||
scripts
|
||||
__tests__
|
||||
examples
|
||||
.babelrc
|
||||
.eslintrc
|
||||
2
README.md
Normal file
2
README.md
Normal file
@ -0,0 +1,2 @@
|
||||
#google map react
|
||||
isomorphic google map react component, allows render react components on the google map
|
||||
47
package.json
Normal file
47
package.json
Normal file
@ -0,0 +1,47 @@
|
||||
{
|
||||
"name": "google-map-react",
|
||||
"version": "0.3.0",
|
||||
"description": "isomorphic google map react component, allows render react components on the google map",
|
||||
"main": "modules/google_map.js",
|
||||
"scripts": {
|
||||
"build": "./scripts/build.sh",
|
||||
"prepublish": "npm run build",
|
||||
"eyetest": "babel-node ./src/__tests__/eye_test.js",
|
||||
"es5eyetest": "node ./modules/__tests__/eye_test.js"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/istarkov/google-map-react.git"
|
||||
},
|
||||
"keywords": [
|
||||
"react",
|
||||
"reactjs",
|
||||
"google",
|
||||
"map",
|
||||
"maps",
|
||||
"isomorphic",
|
||||
"render",
|
||||
"component",
|
||||
"javascript",
|
||||
"react-component"
|
||||
],
|
||||
"author": "istarkov https://github.com/istarkov",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/istarkov/google-map-react/issues"
|
||||
},
|
||||
"homepage": "https://github.com/istarkov/google-map-react#readme",
|
||||
"dependencies": {
|
||||
"eventemitter3": "^1.1.0",
|
||||
"point-geometry": "0.0.0",
|
||||
"react-pure-render": "^1.0.1",
|
||||
"scriptjs": "^2.5.7"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=0.13.0 <0.15.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel": "^5.5.6",
|
||||
"react": "^0.13.3"
|
||||
}
|
||||
}
|
||||
3
scripts/build.sh
Executable file
3
scripts/build.sh
Executable file
@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
rm -rf modules
|
||||
./node_modules/.bin/babel src --out-dir modules
|
||||
37
src/__tests__/eye_test.js
Normal file
37
src/__tests__/eye_test.js
Normal file
@ -0,0 +1,37 @@
|
||||
// "eye test" (c)somebody - means check output by your eyes :-)
|
||||
// TODO read how to test react components
|
||||
|
||||
import React, {PropTypes, Component} from 'react';
|
||||
import GoogleMap from '../google_map.js';
|
||||
|
||||
export default class SimpleTest extends Component {
|
||||
static propTypes = {
|
||||
center: PropTypes.array,
|
||||
zoom: PropTypes.number,
|
||||
greatPlaceCoords: PropTypes.any
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
center: [59.938043, 30.337157],
|
||||
zoom: 9,
|
||||
greatPlaceCoords: {lat: 59.724465, lng: 30.080121}
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<GoogleMap
|
||||
center={this.props.center}
|
||||
zoom={this.props.zoom}>
|
||||
<div lat={59.955413} lng={30.337844}>----------I-PROMISE-TO---------------</div>
|
||||
<div {...this.props.greatPlaceCoords}>-------WRITE-SOME-TESTS-------------</div>
|
||||
</GoogleMap>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const html = React.renderToString(<SimpleTest />);
|
||||
console.log(html); // eslint-disable-line no-console
|
||||
436
src/google_map.js
Normal file
436
src/google_map.js
Normal file
@ -0,0 +1,436 @@
|
||||
import React, {PropTypes, Component} from 'react';
|
||||
import shouldPureComponentUpdate from 'react-pure-render/function';
|
||||
|
||||
import MarkerDispatcher from './marker_dispatcher.js';
|
||||
|
||||
import GoogleMapMap from './google_map_map.js';
|
||||
import GoogleMapMarkers from './google_map_markers.js';
|
||||
import GoogleMapMarkersPrerender from './google_map_markers_prerender.js';
|
||||
|
||||
import googleMapLoader from './utils/loaders/google_map_loader.js';
|
||||
import detectBrowser from './utils/detect.js';
|
||||
|
||||
import Geo from './utils/geo.js';
|
||||
import isArraysEqualEps from './utils/array_helper.js';
|
||||
|
||||
const kEPS = 0.00001;
|
||||
const K_GOOGLE_TILE_SIZE = 256;
|
||||
|
||||
const K_MAP_CONTROL_OPTIONS = {
|
||||
overviewMapControl: false,
|
||||
streetViewControl: false,
|
||||
rotateControl: true,
|
||||
mapTypeControl: false,
|
||||
// disable poi
|
||||
styles: [{ featureType: 'poi', elementType: 'labels', stylers: [{ visibility: 'off' }]}],
|
||||
minZoom: 3 // i need to dynamically calculate possible zoom value
|
||||
};
|
||||
|
||||
const style = {
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
margin: 0,
|
||||
padding: 0,
|
||||
position: 'relative'
|
||||
};
|
||||
|
||||
|
||||
function isNumber(n) {
|
||||
return !Number.isNaN(parseFloat(n)) && Number.isFinite(n);
|
||||
}
|
||||
|
||||
|
||||
export default class GoogleMap extends Component {
|
||||
|
||||
static propTypes = {
|
||||
apiKey: PropTypes.string,
|
||||
center: PropTypes.array.isRequired,
|
||||
zoom: PropTypes.number.isRequired,
|
||||
onBoundsChange: PropTypes.func,
|
||||
onChildClick: PropTypes.func,
|
||||
onChildMouseEnter: PropTypes.func,
|
||||
onChildMouseLeave: PropTypes.func,
|
||||
options: PropTypes.any,
|
||||
distanceToMouse: PropTypes.func,
|
||||
hoverDistance: PropTypes.number,
|
||||
debounced: PropTypes.bool,
|
||||
margin: PropTypes.array,
|
||||
googleMapLoader: PropTypes.any
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
distanceToMouse(pt, mousePos /*, markerProps*/) {
|
||||
const x = pt.x;
|
||||
const y = pt.y; // - 20;
|
||||
return Math.sqrt((x - mousePos.x) * (x - mousePos.x) + (y - mousePos.y) * (y - mousePos.y));
|
||||
},
|
||||
hoverDistance: 30,
|
||||
debounced: true,
|
||||
options: K_MAP_CONTROL_OPTIONS,
|
||||
googleMapLoader
|
||||
};
|
||||
|
||||
shouldComponentUpdate = shouldPureComponentUpdate;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.mounted_ = false;
|
||||
|
||||
this.map_ = null;
|
||||
this.maps_ = null;
|
||||
this.prevBounds_ = null;
|
||||
|
||||
this.mouse_ = null;
|
||||
this.mouseMoveTime_ = 0;
|
||||
this.boundingRect_ = null;
|
||||
this.mouseInMap_ = true;
|
||||
|
||||
this.dragTime_ = 0;
|
||||
this.fireMouseEventOnIdle_ = false;
|
||||
this.updateCounter_ = 0;
|
||||
|
||||
this.markersDispatcher_ = new MarkerDispatcher(this);
|
||||
this.geoService_ = new Geo(K_GOOGLE_TILE_SIZE);
|
||||
if (this._isCenterDefined(this.props.center)) {
|
||||
this.geoService_.setView(this.props.center, this.props.zoom, 0);
|
||||
}
|
||||
|
||||
this.state = {
|
||||
overlayCreated: false
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
_initMap = () => {
|
||||
const center = this.props.center;
|
||||
this.geoService_.setView(center, this.props.zoom, 0);
|
||||
|
||||
this._onBoundsChanged(); // now we can calculate map bounds center etc...
|
||||
|
||||
this.props.googleMapLoader(this.props.apiKey)
|
||||
.then(maps => {
|
||||
if (!this.mounted_) {
|
||||
return;
|
||||
}
|
||||
|
||||
const centerLatLng = this.geoService_.getCenter();
|
||||
|
||||
const propsOptions = {
|
||||
zoom: this.props.zoom,
|
||||
center: new maps.LatLng(centerLatLng.lat, centerLatLng.lng)
|
||||
};
|
||||
|
||||
const mapOptions = {...this.props.options, ...propsOptions};
|
||||
|
||||
const map = new maps.Map(React.findDOMNode(this.refs.google_map_dom), mapOptions);
|
||||
this.map_ = map;
|
||||
this.maps_ = maps;
|
||||
|
||||
// render in overlay
|
||||
const this_ = this;
|
||||
const overlay = Object.assign(new maps.OverlayView(), {
|
||||
onAdd() {
|
||||
const K_MAX_WIDTH = (typeof screen !== 'undefined') ? `${screen.width}px` : '2000px';
|
||||
const K_MAX_HEIGHT = (typeof screen !== 'undefined') ? `${screen.height}px` : '2000px';
|
||||
|
||||
const div = document.createElement('div');
|
||||
this.div = div;
|
||||
div.style.backgroundColor = 'transparent';
|
||||
div.style.position = 'absolute';
|
||||
div.style.left = '0px';
|
||||
div.style.top = '0px';
|
||||
div.style.width = K_MAX_WIDTH; // prevents some chrome draw defects
|
||||
div.style.height = K_MAX_HEIGHT;
|
||||
|
||||
const panes = this.getPanes();
|
||||
panes.overlayMouseTarget.appendChild(div);
|
||||
|
||||
React.render((
|
||||
<GoogleMapMarkers
|
||||
onChildClick={this_._onChildClick}
|
||||
onChildMouseEnter={this_._onChildMouseEnter}
|
||||
onChildMouseLeave={this_._onChildMouseLeave}
|
||||
geoService={this_.geoService_}
|
||||
projectFromLeftTop={true}
|
||||
distanceToMouse={this_.props.distanceToMouse}
|
||||
hoverDistance={this_.props.hoverDistance}
|
||||
dispatcher={this_.markersDispatcher_} />),
|
||||
div,
|
||||
() => {
|
||||
// remove prerendered markers
|
||||
this_.setState({overlayCreated: true});
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
draw() {
|
||||
const div = overlay.div;
|
||||
const overlayProjection = overlay.getProjection();
|
||||
const bounds = map.getBounds();
|
||||
const ne = bounds.getNorthEast();
|
||||
const sw = bounds.getSouthWest();
|
||||
const ptx = overlayProjection.fromLatLngToDivPixel(new maps.LatLng(ne.lat(), sw.lng()));
|
||||
|
||||
// need round for safari still can't find what need for firefox
|
||||
const ptxRounded = detectBrowser().isSafari ? {x: Math.round(ptx.x), y: Math.round(ptx.y)} : {x: ptx.x, y: ptx.y};
|
||||
|
||||
this_.updateCounter_++;
|
||||
this_._onBoundsChanged(map, maps, !this_.props.debounced);
|
||||
|
||||
div.style.left = `${ptxRounded.x}px`;
|
||||
div.style.top = `${ptxRounded.y}px`;
|
||||
if (this_.markersDispatcher_) {
|
||||
this_.markersDispatcher_.emit('kON_CHANGE');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
overlay.setMap(map);
|
||||
|
||||
maps.event.addListener(map, 'idle', () => {
|
||||
if (this.resetSizeOnIdle_) {
|
||||
this._setViewSize();
|
||||
this.resetSizeOnIdle_ = false;
|
||||
}
|
||||
|
||||
const div = overlay.div;
|
||||
const overlayProjection = overlay.getProjection();
|
||||
const bounds = map.getBounds();
|
||||
const ne = bounds.getNorthEast();
|
||||
const sw = bounds.getSouthWest();
|
||||
const ptx = overlayProjection.fromLatLngToDivPixel(new maps.LatLng(ne.lat(), sw.lng()));
|
||||
// need round for safari still can't find what need for firefox
|
||||
const ptxRounded = detectBrowser().isSafari ? {x: Math.round(ptx.x), y: Math.round(ptx.y)} : {x: ptx.x, y: ptx.y};
|
||||
|
||||
this_.updateCounter_++;
|
||||
this_._onBoundsChanged(map, maps);
|
||||
|
||||
this_.dragTime_ = 0;
|
||||
div.style.left = `${ptxRounded.x}px`;
|
||||
div.style.top = `${ptxRounded.y}px`;
|
||||
if (this_.markersDispatcher_) {
|
||||
this_.markersDispatcher_.emit('kON_CHANGE');
|
||||
if (this_.fireMouseEventOnIdle_) {
|
||||
this_.markersDispatcher_.emit('kON_MOUSE_POSITION_CHANGE');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
maps.event.addListener(map, 'mouseover', () => { // has advantage over div MouseLeave
|
||||
this_.mouseInMap_ = true;
|
||||
});
|
||||
|
||||
maps.event.addListener(map, 'mouseout', () => { // has advantage over div MouseLeave
|
||||
this_.mouseInMap_ = false;
|
||||
this_.mouse_ = null;
|
||||
this_.markersDispatcher_.emit('kON_MOUSE_POSITION_CHANGE');
|
||||
});
|
||||
|
||||
maps.event.addListener(map, 'drag', () => {
|
||||
this_.dragTime_ = (new Date()).getTime();
|
||||
});
|
||||
})
|
||||
.catch( e => {
|
||||
console.error(e); // eslint-disable-line no-console
|
||||
throw e;
|
||||
});
|
||||
}
|
||||
|
||||
_onChildClick = (...args) => {
|
||||
if (this.props.onChildClick) {
|
||||
return this.props.onChildClick(...args);
|
||||
}
|
||||
}
|
||||
|
||||
_onChildMouseEnter = (...args) => {
|
||||
if (this.props.onChildMouseEnter) {
|
||||
return this.props.onChildMouseEnter(...args);
|
||||
}
|
||||
}
|
||||
|
||||
_onChildMouseLeave = (...args) => {
|
||||
if (this.props.onChildMouseLeave) {
|
||||
return this.props.onChildMouseLeave(...args);
|
||||
}
|
||||
}
|
||||
|
||||
_setViewSize = () => {
|
||||
const mapDom = React.findDOMNode(this.refs.google_map_dom);
|
||||
this.geoService_.setViewSize(mapDom.clientWidth, mapDom.clientHeight);
|
||||
this._onBoundsChanged();
|
||||
}
|
||||
|
||||
_onWindowResize = () => {
|
||||
this.resetSizeOnIdle_ = true;
|
||||
}
|
||||
|
||||
_onBoundsChanged = (map, maps, callExtBoundsChange) => {
|
||||
if (map) {
|
||||
const gmC = map.getCenter();
|
||||
this.geoService_.setView([gmC.lat(), gmC.lng()], map.getZoom(), 0);
|
||||
}
|
||||
|
||||
if (this.props.onBoundsChange && this.geoService_.canProject()) {
|
||||
const zoom = this.geoService_.getZoom();
|
||||
const bounds = this.geoService_.getBounds();
|
||||
const centerLatLng = this.geoService_.getCenter();
|
||||
|
||||
if (!isArraysEqualEps(bounds, this.prevBounds_, kEPS)) {
|
||||
if (callExtBoundsChange !== false) {
|
||||
const marginBounds = this.geoService_.getBounds(this.props.margin);
|
||||
this.props.onBoundsChange([centerLatLng.lat, centerLatLng.lng], zoom, bounds, marginBounds);
|
||||
this.prevBounds_ = bounds;
|
||||
}
|
||||
}
|
||||
// uncomment for strange bugs
|
||||
if (process.env.NODE_ENV !== 'production') { // compare with google calculations
|
||||
if (map) {
|
||||
const locBounds = map.getBounds();
|
||||
const ne = locBounds.getNorthEast();
|
||||
const sw = locBounds.getSouthWest();
|
||||
|
||||
const gmC = map.getCenter();
|
||||
// compare with google map
|
||||
|
||||
if (!isArraysEqualEps([centerLatLng.lat, centerLatLng.lng], [gmC.lat(), gmC.lng()], kEPS)) {
|
||||
console.info('GoogleMap center not eq:', [centerLatLng.lat, centerLatLng.lng], [gmC.lat(), gmC.lng()]); // eslint-disable-line no-console
|
||||
}
|
||||
|
||||
if (!isArraysEqualEps(bounds, [ne.lat(), sw.lng(), sw.lat(), ne.lng()], kEPS)) {
|
||||
// this is normal if this message occured on resize
|
||||
console.info('GoogleMap bounds not eq:', '\n', bounds, '\n', [ne.lat(), sw.lng(), sw.lat(), ne.lng()]); // eslint-disable-line no-console
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_onMouseMove = (e) => {
|
||||
if (!this.mouseInMap_) return;
|
||||
|
||||
const currTime = (new Date()).getTime();
|
||||
const K_RECALC_CLIENT_RECT_MS = 3000;
|
||||
|
||||
if (currTime - this.mouseMoveTime_ > K_RECALC_CLIENT_RECT_MS) {
|
||||
this.boundingRect_ = e.currentTarget.getBoundingClientRect();
|
||||
}
|
||||
this.mouseMoveTime_ = currTime;
|
||||
|
||||
const mousePosX = e.clientX - this.boundingRect_.left;
|
||||
const mousePosY = e.clientY - this.boundingRect_.top;
|
||||
|
||||
if (!this.mouse_) {
|
||||
this.mouse_ = {x: 0, y: 0, lat: 0, lng: 0};
|
||||
}
|
||||
const K_IDLE_TIMEOUT = 100;
|
||||
|
||||
this.mouse_.x = mousePosX;
|
||||
this.mouse_.y = mousePosY;
|
||||
|
||||
const latLng = this.geoService_.unproject(this.mouse_, true);
|
||||
this.mouse_.lat = latLng.lat;
|
||||
this.mouse_.lng = latLng.lng;
|
||||
|
||||
if (currTime - this.dragTime_ < K_IDLE_TIMEOUT) {
|
||||
this.fireMouseEventOnIdle_ = true;
|
||||
} else {
|
||||
this.markersDispatcher_.emit('kON_MOUSE_POSITION_CHANGE');
|
||||
this.fireMouseEventOnIdle_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
_onMapClick = () => {
|
||||
if (this.markersDispatcher_) {
|
||||
const K_IDLE_TIMEOUT = 100;
|
||||
const currTime = (new Date()).getTime();
|
||||
if (currTime - this.dragTime_ > K_IDLE_TIMEOUT) {
|
||||
this.markersDispatcher_.emit('kON_CLICK');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_isCenterDefined = (center) => {
|
||||
return center && center.length === 2 && isNumber(center[0]) && isNumber(center[1]);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.mounted_ = true;
|
||||
window.addEventListener('resize', this._onWindowResize);
|
||||
|
||||
setTimeout(() => { // to detect size
|
||||
this._setViewSize();
|
||||
if (this._isCenterDefined(this.props.center)) {
|
||||
this._initMap();
|
||||
} else {
|
||||
this.props.googleMapLoader(this.props.apiKey); // начать подгружать можно уже сейчас
|
||||
}
|
||||
}, 0, this);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.mounted_ = false;
|
||||
|
||||
window.removeEventListener('resize', this._onWindowResize);
|
||||
|
||||
if (this.maps_ && this.map_) {
|
||||
this.maps_.event.clearInstanceListeners(this.map_);
|
||||
}
|
||||
|
||||
this.map_ = null;
|
||||
this.maps_ = null;
|
||||
this.markersDispatcher_.dispose();
|
||||
|
||||
this.resetSizeOnIdle_ = false;
|
||||
|
||||
delete this.map_;
|
||||
delete this.markersDispatcher_;
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (!this._isCenterDefined(this.props.center) && this._isCenterDefined(nextProps.center)) {
|
||||
setTimeout(() =>
|
||||
this._initMap(), 0);
|
||||
}
|
||||
|
||||
if (this.map_) {
|
||||
const centerLatLng = this.geoService_.getCenter();
|
||||
if (nextProps.center) {
|
||||
if (Math.abs(nextProps.center[0] - centerLatLng.lat) + Math.abs(nextProps.center[1] - centerLatLng.lng) > kEPS) {
|
||||
this.map_.panTo({lat: nextProps.center[0], lng: nextProps.center[1]});
|
||||
}
|
||||
}
|
||||
|
||||
// if zoom chaged by user
|
||||
if (Math.abs(nextProps.zoom - this.props.zoom) > 0) {
|
||||
this.map_.setZoom(nextProps.zoom);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
this.markersDispatcher_.emit('kON_CHANGE');
|
||||
}
|
||||
|
||||
render() {
|
||||
const mapMarkerPrerender = !this.state.overlayCreated ? (
|
||||
<GoogleMapMarkersPrerender
|
||||
onChildClick={this._onChildClick}
|
||||
onChildMouseEnter={this._onChildMouseEnter}
|
||||
onChildMouseLeave={this._onChildMouseLeave}
|
||||
geoService={this.geoService_}
|
||||
projectFromLeftTop={false}
|
||||
distanceToMouse={this.props.distanceToMouse}
|
||||
hoverDistance={this.props.hoverDistance}
|
||||
dispatcher={this.markersDispatcher_} />
|
||||
) : null;
|
||||
|
||||
return (
|
||||
<div style={style} onMouseMove={this._onMouseMove} onClick={this._onMapClick}>
|
||||
<GoogleMapMap ref="google_map_dom" />
|
||||
|
||||
{/*render markers before map load done*/}
|
||||
{mapMarkerPrerender}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
27
src/google_map_map.js
Normal file
27
src/google_map_map.js
Normal file
@ -0,0 +1,27 @@
|
||||
import React, {Component} from 'react';
|
||||
|
||||
const style = {
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
left: 0,
|
||||
top: 0,
|
||||
margin: 0,
|
||||
padding: 0,
|
||||
position: 'absolute'
|
||||
};
|
||||
|
||||
export default class GoogleMapMap extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
shouldComponentUpdate() {
|
||||
return false; // disable react on this div
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div style={style} />
|
||||
);
|
||||
}
|
||||
}
|
||||
232
src/google_map_markers.js
Normal file
232
src/google_map_markers.js
Normal file
@ -0,0 +1,232 @@
|
||||
import React, {PropTypes, Component} from 'react';
|
||||
|
||||
import shouldPureComponentUpdate from 'react-pure-render/function';
|
||||
|
||||
const mainStyle = {
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
left: 0,
|
||||
top: 0,
|
||||
margin: 0,
|
||||
padding: 0,
|
||||
position: 'absolute'
|
||||
};
|
||||
|
||||
const style = {
|
||||
width: 0,
|
||||
height: 0,
|
||||
left: 0,
|
||||
top: 0,
|
||||
backgroundColor: 'transparent',
|
||||
position: 'absolute'
|
||||
};
|
||||
|
||||
export default class GoogleMapMarkers extends Component {
|
||||
static propTypes = {
|
||||
geoService: PropTypes.any,
|
||||
style: PropTypes.any,
|
||||
distanceToMouse: PropTypes.func,
|
||||
dispatcher: PropTypes.any,
|
||||
onChildClick: PropTypes.func,
|
||||
onChildMouseLeave: PropTypes.func,
|
||||
onChildMouseEnter: PropTypes.func,
|
||||
hoverDistance: PropTypes.number,
|
||||
projectFromLeftTop: PropTypes.bool
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
projectFromLeftTop: false
|
||||
};
|
||||
|
||||
shouldComponentUpdate = shouldPureComponentUpdate;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.props.dispatcher.on('kON_CHANGE', this._onChangeHandler);
|
||||
this.props.dispatcher.on('kON_MOUSE_POSITION_CHANGE', this._onMouseChangeHandler);
|
||||
this.props.dispatcher.on('kON_CLICK', this._onChildClick);
|
||||
|
||||
this.dimesionsCache_ = {};
|
||||
this.hoverKey_ = null;
|
||||
this.hoverChildProps_ = null;
|
||||
this.allowMouse_ = true;
|
||||
|
||||
this.state = {...this._getState(), hoverKey: null};
|
||||
}
|
||||
|
||||
_getState = () => {
|
||||
return {
|
||||
children: this.props.dispatcher.getChildren(),
|
||||
updateCounter: this.props.dispatcher.getUpdateCounter()
|
||||
};
|
||||
}
|
||||
|
||||
_onChangeHandler = () => {
|
||||
if (!this.dimesionsCache_) {
|
||||
return;
|
||||
}
|
||||
|
||||
const state = this._getState();
|
||||
this.setState(state);
|
||||
}
|
||||
|
||||
_onChildClick = () => {
|
||||
if (this.props.onChildClick) {
|
||||
if (this.hoverChildProps_) {
|
||||
const hoverKey = this.hoverKey_;
|
||||
const childProps = this.hoverChildProps_;
|
||||
// click works only on hovered item
|
||||
this.props.onChildClick(hoverKey, childProps);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_onChildMouseEnter = (hoverKey, childProps) => {
|
||||
if (!this.dimesionsCache_) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.props.onChildMouseEnter) {
|
||||
this.props.onChildMouseEnter(hoverKey, childProps);
|
||||
}
|
||||
|
||||
this.hoverChildProps_ = childProps;
|
||||
this.hoverKey_ = hoverKey;
|
||||
this.setState({hoverKey: hoverKey});
|
||||
}
|
||||
|
||||
_onChildMouseLeave = () => {
|
||||
if (!this.dimesionsCache_) {
|
||||
return;
|
||||
}
|
||||
|
||||
const hoverKey = this.hoverKey_;
|
||||
const childProps = this.hoverChildProps_;
|
||||
|
||||
if (hoverKey !== undefined && hoverKey !== null) {
|
||||
if (this.props.onChildMouseLeave) {
|
||||
this.props.onChildMouseLeave(hoverKey, childProps);
|
||||
}
|
||||
|
||||
this.hoverKey_ = null;
|
||||
this.hoverChildProps_ = null;
|
||||
this.setState({hoverKey: null});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
_onMouseAllow = (value) => {
|
||||
if (!value) {
|
||||
this._onChildMouseLeave();
|
||||
}
|
||||
|
||||
this.allowMouse_ = value;
|
||||
}
|
||||
|
||||
|
||||
_onMouseChangeHandler = () => {
|
||||
if (this.allowMouse_) {
|
||||
this._onMouseChangeHandler_raf();
|
||||
}
|
||||
}
|
||||
|
||||
_onMouseChangeHandler_raf = () => {
|
||||
if (!this.dimesionsCache_) {
|
||||
return;
|
||||
}
|
||||
|
||||
const mp = this.props.dispatcher.getMousePosition();
|
||||
|
||||
if (mp) {
|
||||
let distances = [];
|
||||
|
||||
React.Children.forEach(this.state.children, (child, childIndex) => {
|
||||
const childKey = child.key !== undefined && child.key !== null ? child.key : childIndex;
|
||||
const dist = this.props.distanceToMouse(this.dimesionsCache_[childKey], mp, child.props);
|
||||
if (dist < this.props.hoverDistance) {
|
||||
distances.push(
|
||||
{
|
||||
key: childKey,
|
||||
dist: dist,
|
||||
props: child.props
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (distances.length) {
|
||||
distances.sort((a, b) => a.dist - b.dist);
|
||||
const hoverKey = distances[0].key;
|
||||
const childProps = distances[0].props;
|
||||
|
||||
if (this.hoverKey_ !== hoverKey) {
|
||||
this._onChildMouseLeave();
|
||||
|
||||
this._onChildMouseEnter(hoverKey, childProps);
|
||||
}
|
||||
} else {
|
||||
this._onChildMouseLeave();
|
||||
}
|
||||
} else {
|
||||
this._onChildMouseLeave();
|
||||
}
|
||||
}
|
||||
|
||||
_getDimensions = (key) => {
|
||||
const childKey = key;
|
||||
return this.dimesionsCache_[childKey];
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.dispatcher.removeListener('kON_CHANGE', this._onChangeHandler);
|
||||
this.props.dispatcher.removeListener('kON_MOUSE_POSITION_CHANGE', this._onMouseChangeHandler);
|
||||
this.props.dispatcher.removeListener('kON_CLICK', this._onChildClick);
|
||||
|
||||
this.dimesionsCache_ = null;
|
||||
}
|
||||
|
||||
render() {
|
||||
const mainElementStyle = this.props.style || mainStyle;
|
||||
|
||||
this.dimesionsCache_ = {};
|
||||
|
||||
const markers = React.Children.map(this.state.children, (child, childIndex) => {
|
||||
const pt = this.props.geoService.project({lat: child.props.lat, lng: child.props.lng}, this.props.projectFromLeftTop);
|
||||
const stylePtPos = {
|
||||
left: pt.x,
|
||||
top: pt.y
|
||||
};
|
||||
|
||||
let dx = 0;
|
||||
let dy = 0;
|
||||
|
||||
if (!this.props.projectFromLeftTop) { // center projection
|
||||
if (this.props.geoService.hasSize()) {
|
||||
dx = this.props.geoService.getWidth() / 2;
|
||||
dy = this.props.geoService.getHeight() / 2;
|
||||
}
|
||||
}
|
||||
|
||||
// to prevent rerender on child element i need to pass const params $getDimensions and $dimensionKey instead of dimension object
|
||||
const childKey = child.key !== undefined && child.key !== null ? child.key : childIndex;
|
||||
this.dimesionsCache_[childKey] = {x: pt.x + dx, y: pt.y + dy, lat: child.props.lat, lng: child.props.lng};
|
||||
|
||||
return (
|
||||
<div key={childKey} style={{...style, ...stylePtPos}}>
|
||||
{React.cloneElement(child, {
|
||||
$hover: childKey === this.state.hoverKey,
|
||||
$getDimensions: this._getDimensions,
|
||||
$dimensionKey: childKey,
|
||||
$geoService: this.props.geoService,
|
||||
$onMouseAllow: this._onMouseAllow
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<div style={mainElementStyle}>
|
||||
{markers}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
29
src/google_map_markers_prerender.js
Normal file
29
src/google_map_markers_prerender.js
Normal file
@ -0,0 +1,29 @@
|
||||
import React, {PropTypes, Component} from 'react';
|
||||
import GoogleMapMarkers from './google_map_markers.js';
|
||||
|
||||
const style = {
|
||||
width: '50%',
|
||||
height: '50%',
|
||||
left: '50%',
|
||||
top: '50%',
|
||||
// backgroundColor: 'red',
|
||||
margin: 0,
|
||||
padding: 0,
|
||||
position: 'absolute'
|
||||
// opacity: 0.3
|
||||
};
|
||||
|
||||
export default class GoogleMapMarkersPrerender extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div style={style}>
|
||||
<GoogleMapMarkers {...this.props} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
25
src/marker_dispatcher.js
Normal file
25
src/marker_dispatcher.js
Normal file
@ -0,0 +1,25 @@
|
||||
import EventEmitter from 'eventemitter3';
|
||||
|
||||
export default class MarkerDispatcher extends EventEmitter {
|
||||
constructor(gmapInstance) {
|
||||
super();
|
||||
this.gmapInstance = gmapInstance;
|
||||
}
|
||||
|
||||
getChildren() {
|
||||
return this.gmapInstance.props.children;
|
||||
}
|
||||
|
||||
getMousePosition() {
|
||||
return this.gmapInstance.mouse_;
|
||||
}
|
||||
|
||||
getUpdateCounter() {
|
||||
return this.gmapInstance.updateCounter_;
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this.gmapInstance = null;
|
||||
this.removeAllListeners();
|
||||
}
|
||||
}
|
||||
12
src/utils/array_helper.js
Normal file
12
src/utils/array_helper.js
Normal file
@ -0,0 +1,12 @@
|
||||
|
||||
export default function isArraysEqualEps(arrayA, arrayB, eps) {
|
||||
if (arrayA && arrayB) {
|
||||
for (let i = 0; i !== arrayA.length; ++i) {
|
||||
if (Math.abs(arrayA[i] - arrayB[i]) > eps) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
31
src/utils/detect.js
Normal file
31
src/utils/detect.js
Normal file
@ -0,0 +1,31 @@
|
||||
// code here http://stackoverflow.com/questions/5899783/detect-safari-chrome-ie-firefox-opera-with-user-agent
|
||||
let detectBrowserResult_ = null;
|
||||
|
||||
export default function detectBrowser() {
|
||||
if (detectBrowserResult_) {
|
||||
return detectBrowserResult_;
|
||||
}
|
||||
|
||||
if (typeof navigator !== 'undefined') {
|
||||
const isExplorer = navigator.userAgent.indexOf('MSIE') > -1;
|
||||
const isFirefox = navigator.userAgent.indexOf('Firefox') > -1;
|
||||
const isOpera = navigator.userAgent.toLowerCase().indexOf('op') > -1;
|
||||
|
||||
let isChrome = navigator.userAgent.indexOf('Chrome') > -1;
|
||||
let isSafari = navigator.userAgent.indexOf('Safari') > -1;
|
||||
|
||||
if ((isChrome) && (isSafari)) {
|
||||
isSafari = false;
|
||||
}
|
||||
|
||||
if ((isChrome) && (isOpera)) {
|
||||
isChrome = false;
|
||||
}
|
||||
|
||||
detectBrowserResult_ = {isExplorer, isFirefox, isOpera, isChrome, isSafari};
|
||||
return detectBrowserResult_;
|
||||
}
|
||||
|
||||
detectBrowserResult_ = {isChrome: true, isExplorer: false, isFirefox: false, isOpera: false, isSafari: false};
|
||||
return detectBrowserResult_;
|
||||
}
|
||||
107
src/utils/geo.js
Normal file
107
src/utils/geo.js
Normal file
@ -0,0 +1,107 @@
|
||||
import LatLng from './lib_geo/lat_lng.js';
|
||||
import Point from 'point-geometry';
|
||||
import Transform from './lib_geo/transform.js';
|
||||
|
||||
|
||||
export default class Geo {
|
||||
|
||||
constructor(tileSize) { // left_top view пользует гугл
|
||||
// super();
|
||||
this.hasSize_ = false;
|
||||
this.hasView_ = false;
|
||||
this.transform_ = new Transform(tileSize || 512);
|
||||
}
|
||||
|
||||
setView(center, zoom, bearing) {
|
||||
this.transform_.center = LatLng.convert(center);
|
||||
this.transform_.zoom = +zoom;
|
||||
this.transform_.bearing = +bearing;
|
||||
this.hasView_ = true;
|
||||
}
|
||||
|
||||
setViewSize(width, height) {
|
||||
this.transform_.width = width;
|
||||
this.transform_.height = height;
|
||||
this.hasSize_ = true;
|
||||
}
|
||||
|
||||
canProject() {
|
||||
return this.hasSize_ && this.hasView_;
|
||||
}
|
||||
|
||||
hasSize() {
|
||||
return this.hasSize_;
|
||||
}
|
||||
|
||||
unproject(ptXY, viewFromLeftTop) {
|
||||
let ptRes;
|
||||
if (viewFromLeftTop) {
|
||||
const ptxy = Object.assign({}, ptXY);
|
||||
ptxy.x -= this.transform_.width / 2;
|
||||
ptxy.y -= this.transform_.height / 2;
|
||||
ptRes = this.transform_.pointLocation(Point.convert(ptxy));
|
||||
} else {
|
||||
ptRes = this.transform_.pointLocation(Point.convert(ptXY));
|
||||
}
|
||||
|
||||
ptRes.lng -= 360 * Math.round(ptRes.lng / 360); // convert 2 google format
|
||||
return ptRes;
|
||||
}
|
||||
|
||||
project(ptLatLng, viewFromLeftTop) {
|
||||
if (viewFromLeftTop) {
|
||||
const pt = this.transform_.locationPoint(LatLng.convert(ptLatLng));
|
||||
pt.x -= this.transform_.worldSize * Math.round(pt.x / this.transform_.worldSize);
|
||||
|
||||
pt.x += this.transform_.width / 2;
|
||||
pt.y += this.transform_.height / 2;
|
||||
|
||||
return pt;
|
||||
}
|
||||
|
||||
return this.transform_.locationPoint(LatLng.convert(ptLatLng));
|
||||
}
|
||||
|
||||
getWidth() {
|
||||
return this.transform_.width;
|
||||
}
|
||||
|
||||
getHeight() {
|
||||
return this.transform_.height;
|
||||
}
|
||||
|
||||
getZoom() {
|
||||
return this.transform_.zoom;
|
||||
}
|
||||
|
||||
getCenter() {
|
||||
let ptRes = this.transform_.pointLocation({x: 0, y: 0});
|
||||
|
||||
return ptRes;
|
||||
}
|
||||
|
||||
getBounds(margins, roundFactor) {
|
||||
const bndT = margins && margins[0] || 0;
|
||||
const bndR = margins && margins[1] || 0;
|
||||
const bndB = margins && margins[2] || 0;
|
||||
const bndL = margins && margins[3] || 0;
|
||||
|
||||
if (this.getWidth() - bndR - bndL > 0 && this.getHeight() - bndT - bndB > 0) {
|
||||
const topLeftCorner = this.unproject({x: bndL - this.getWidth() / 2, y: bndT - this.getHeight() / 2});
|
||||
const bottomRightCorner = this.unproject({x: this.getWidth() / 2 - bndR, y: this.getHeight() / 2 - bndB});
|
||||
|
||||
let res = [
|
||||
topLeftCorner.lat, topLeftCorner.lng,
|
||||
bottomRightCorner.lat, bottomRightCorner.lng
|
||||
];
|
||||
|
||||
if (roundFactor) {
|
||||
res = res.map(r => Math.round(r * roundFactor) / roundFactor);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
return [0, 0, 0, 0];
|
||||
}
|
||||
}
|
||||
|
||||
27
src/utils/lib_geo/LICENCE.txt
Normal file
27
src/utils/lib_geo/LICENCE.txt
Normal file
@ -0,0 +1,27 @@
|
||||
Copyright (c) 2014, Mapbox
|
||||
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
* Neither the name of Mapbox GL JS nor the names of its contributors
|
||||
may be used to endorse or promote products derived from this software
|
||||
without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
29
src/utils/lib_geo/lat_lng.js
Normal file
29
src/utils/lib_geo/lat_lng.js
Normal file
@ -0,0 +1,29 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = LatLng;
|
||||
|
||||
var wrap = require('./wrap.js').wrap;
|
||||
|
||||
function LatLng(lat, lng) {
|
||||
if (isNaN(lat) || isNaN(lng)) {
|
||||
throw new Error('Invalid LatLng object: (' + lat + ', ' + lng + ')');
|
||||
}
|
||||
this.lat = +lat;
|
||||
this.lng = +lng;
|
||||
}
|
||||
|
||||
LatLng.prototype.wrap = function () {
|
||||
return new LatLng(this.lat, wrap(this.lng, -180, 180));
|
||||
};
|
||||
|
||||
// constructs LatLng from an array if necessary
|
||||
|
||||
LatLng.convert = function (a) {
|
||||
if (a instanceof LatLng) {
|
||||
return a;
|
||||
}
|
||||
if (Array.isArray(a)) {
|
||||
return new LatLng(a[0], a[1]);
|
||||
}
|
||||
return a;
|
||||
};
|
||||
72
src/utils/lib_geo/lat_lng_bounds.js
Normal file
72
src/utils/lib_geo/lat_lng_bounds.js
Normal file
@ -0,0 +1,72 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = LatLngBounds;
|
||||
|
||||
var LatLng = require('./lat_lng');
|
||||
|
||||
function LatLngBounds(sw, ne) {
|
||||
if (!sw) return;
|
||||
|
||||
var latlngs = ne ? [sw, ne] : sw;
|
||||
|
||||
for (var i = 0, len = latlngs.length; i < len; i++) {
|
||||
this.extend(latlngs[i]);
|
||||
}
|
||||
}
|
||||
|
||||
LatLngBounds.prototype = {
|
||||
|
||||
// extend the bounds to contain the given point or bounds
|
||||
extend: function (obj) {
|
||||
var sw = this._sw,
|
||||
ne = this._ne,
|
||||
sw2, ne2;
|
||||
|
||||
if (obj instanceof LatLng) {
|
||||
sw2 = obj;
|
||||
ne2 = obj;
|
||||
|
||||
} else if (obj instanceof LatLngBounds) {
|
||||
sw2 = obj._sw;
|
||||
ne2 = obj._ne;
|
||||
|
||||
if (!sw2 || !ne2) return this;
|
||||
|
||||
} else {
|
||||
return obj ? this.extend(LatLng.convert(obj) || LatLngBounds.convert(obj)) : this;
|
||||
}
|
||||
|
||||
if (!sw && !ne) {
|
||||
this._sw = new LatLng(sw2.lat, sw2.lng);
|
||||
this._ne = new LatLng(ne2.lat, ne2.lng);
|
||||
|
||||
} else {
|
||||
sw.lat = Math.min(sw2.lat, sw.lat);
|
||||
sw.lng = Math.min(sw2.lng, sw.lng);
|
||||
ne.lat = Math.max(ne2.lat, ne.lat);
|
||||
ne.lng = Math.max(ne2.lng, ne.lng);
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
getCenter: function () {
|
||||
return new LatLng((this._sw.lat + this._ne.lat) / 2, (this._sw.lng + this._ne.lng) / 2);
|
||||
},
|
||||
|
||||
getSouthWest: function () { return this._sw; },
|
||||
getNorthEast: function () { return this._ne; },
|
||||
getNorthWest: function () { return new LatLng(this.getNorth(), this.getWest()); },
|
||||
getSouthEast: function () { return new LatLng(this.getSouth(), this.getEast()); },
|
||||
|
||||
getWest: function () { return this._sw.lng; },
|
||||
getSouth: function () { return this._sw.lat; },
|
||||
getEast: function () { return this._ne.lng; },
|
||||
getNorth: function () { return this._ne.lat; }
|
||||
};
|
||||
|
||||
// constructs LatLngBounds from an array if necessary
|
||||
LatLngBounds.convert = function (a) {
|
||||
if (!a || a instanceof LatLngBounds) return a;
|
||||
return new LatLngBounds(a);
|
||||
};
|
||||
2
src/utils/lib_geo/readme.md
Normal file
2
src/utils/lib_geo/readme.md
Normal file
@ -0,0 +1,2 @@
|
||||
###Код выдран из mapboxgl чтобы преобразования координат не зависели от библиотеки
|
||||
|
||||
121
src/utils/lib_geo/transform.js
Normal file
121
src/utils/lib_geo/transform.js
Normal file
@ -0,0 +1,121 @@
|
||||
|
||||
const LatLng = require('./lat_lng');
|
||||
const Point = require('point-geometry');
|
||||
const wrap = require('./wrap.js').wrap;
|
||||
|
||||
|
||||
// A single transform, generally used for a single tile to be scaled, rotated, and zoomed.
|
||||
|
||||
function Transform(tileSize, minZoom, maxZoom) {
|
||||
this.tileSize = tileSize || 512; // constant
|
||||
|
||||
this._minZoom = minZoom || 0;
|
||||
this._maxZoom = maxZoom || 52;
|
||||
|
||||
this.latRange = [-85.05113, 85.05113];
|
||||
|
||||
this.width = 0;
|
||||
this.height = 0;
|
||||
this.zoom = 0;
|
||||
this.center = new LatLng(0, 0);
|
||||
this.angle = 0;
|
||||
}
|
||||
|
||||
Transform.prototype = {
|
||||
get minZoom() { return this._minZoom; },
|
||||
set minZoom(zoom) {
|
||||
this._minZoom = zoom;
|
||||
this.zoom = Math.max(this.zoom, zoom);
|
||||
},
|
||||
|
||||
get maxZoom() { return this._maxZoom; },
|
||||
set maxZoom(zoom) {
|
||||
this._maxZoom = zoom;
|
||||
this.zoom = Math.min(this.zoom, zoom);
|
||||
},
|
||||
|
||||
get worldSize() {
|
||||
return this.tileSize * this.scale;
|
||||
},
|
||||
|
||||
get centerPoint() {
|
||||
return new Point(0, 0); // this.size._div(2);
|
||||
},
|
||||
|
||||
get size() {
|
||||
return new Point(this.width, this.height);
|
||||
},
|
||||
|
||||
get bearing() {
|
||||
return -this.angle / Math.PI * 180;
|
||||
},
|
||||
set bearing(bearing) {
|
||||
this.angle = -wrap(bearing, -180, 180) * Math.PI / 180;
|
||||
},
|
||||
|
||||
get zoom() { return this._zoom; },
|
||||
set zoom(zoom) {
|
||||
zoom = Math.min(Math.max(zoom, this.minZoom), this.maxZoom);
|
||||
this._zoom = zoom;
|
||||
this.scale = this.zoomScale(zoom);
|
||||
this.tileZoom = Math.floor(zoom);
|
||||
this.zoomFraction = zoom - this.tileZoom;
|
||||
},
|
||||
|
||||
zoomScale: function(zoom) { return Math.pow(2, zoom); },
|
||||
scaleZoom: function(scale) { return Math.log(scale) / Math.LN2; },
|
||||
|
||||
project: function(latlng, worldSize) {
|
||||
return new Point(
|
||||
this.lngX(latlng.lng, worldSize),
|
||||
this.latY(latlng.lat, worldSize));
|
||||
},
|
||||
|
||||
unproject: function(point, worldSize) {
|
||||
return new LatLng(
|
||||
this.yLat(point.y, worldSize),
|
||||
this.xLng(point.x, worldSize));
|
||||
},
|
||||
|
||||
get x() {
|
||||
return this.lngX(this.center.lng);
|
||||
},
|
||||
get y() {
|
||||
return this.latY(this.center.lat);
|
||||
},
|
||||
|
||||
get point() { return new Point(this.x, this.y); },
|
||||
|
||||
// lat/lon <-> absolute pixel coords convertion
|
||||
lngX: function(lon, worldSize) {
|
||||
return (180 + lon) * (worldSize || this.worldSize) / 360;
|
||||
},
|
||||
// latitude to absolute y coord
|
||||
latY: function(lat, worldSize) {
|
||||
var y = 180 / Math.PI * Math.log(Math.tan(Math.PI / 4 + lat * Math.PI / 360));
|
||||
return (180 - y) * (worldSize || this.worldSize) / 360;
|
||||
},
|
||||
|
||||
xLng: function(x, worldSize) {
|
||||
return x * 360 / (worldSize || this.worldSize) - 180;
|
||||
},
|
||||
yLat: function(y, worldSize) {
|
||||
var y2 = 180 - y * 360 / (worldSize || this.worldSize);
|
||||
return 360 / Math.PI * Math.atan(Math.exp(y2 * Math.PI / 180)) - 90;
|
||||
},
|
||||
|
||||
|
||||
locationPoint: function(latlng) {
|
||||
var p = this.project(latlng);
|
||||
return this.centerPoint._sub(this.point._sub(p)._rotate(this.angle));
|
||||
},
|
||||
|
||||
pointLocation: function(p) {
|
||||
var p2 = this.centerPoint._sub(p)._rotate(-this.angle);
|
||||
return this.unproject(this.point.sub(p2));
|
||||
},
|
||||
|
||||
|
||||
};
|
||||
|
||||
module.exports = Transform;
|
||||
6
src/utils/lib_geo/wrap.js
Normal file
6
src/utils/lib_geo/wrap.js
Normal file
@ -0,0 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
exports.wrap = function (n, min, max) {
|
||||
var d = max - min;
|
||||
return n === max ? n : ((n - min) % d + d) % d + min;
|
||||
};
|
||||
45
src/utils/loaders/google_map_loader.js
Normal file
45
src/utils/loaders/google_map_loader.js
Normal file
@ -0,0 +1,45 @@
|
||||
let $script_ = null;
|
||||
|
||||
let _loadPromise;
|
||||
|
||||
// TODO add libraries language and other map options
|
||||
module.exports = function googleMapLoader(apiKey) {
|
||||
if (!$script_) {
|
||||
$script_ = require('scriptjs');
|
||||
}
|
||||
|
||||
if (_loadPromise) {
|
||||
return _loadPromise;
|
||||
}
|
||||
|
||||
_loadPromise = new Promise((resolve, reject) => {
|
||||
if (typeof window === 'undefined') {
|
||||
reject(new Error('google map cannot be loaded outside browser env'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (window.google && window.google.maps) {
|
||||
resolve(window.google.maps);
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof window._$_google_map_initialize_$_ !== 'undefined') {
|
||||
reject(new Error('google map initialization error'));
|
||||
}
|
||||
|
||||
window._$_google_map_initialize_$_ = () => {
|
||||
delete window._$_google_map_initialize_$_;
|
||||
resolve(window.google.maps);
|
||||
};
|
||||
|
||||
const apiKeyString = apiKey ? `&key=${apiKey}` : '';
|
||||
|
||||
$script_(`https://maps.googleapis.com/maps/api/js?callback=_$_google_map_initialize_$_${apiKeyString}`, () => {
|
||||
if (typeof window.google === 'undefined') {
|
||||
reject(new Error('google map initialization error (not loaded)'));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return _loadPromise;
|
||||
};
|
||||
Loading…
x
Reference in New Issue
Block a user