diff --git a/demos/01_animatePoint.html b/demos/01_animatePoint.html
index 96f2acf9c4..e1ee4ae1f6 100644
--- a/demos/01_animatePoint.html
+++ b/demos/01_animatePoint.html
@@ -51,6 +51,7 @@ const scene = new L7.Scene({
   pitch: 0,
   zoom: 2,
   maxZoom:20,
+  hash:true,
   minZoom:0,
 });
 window.scene = scene;
diff --git a/demos/02_animateline.html b/demos/02_animateline.html
index 9d78576db4..55be2e09d0 100644
--- a/demos/02_animateline.html
+++ b/demos/02_animateline.html
@@ -88,7 +88,6 @@ scene.on('loaded', () => {
         // then update the map
          linelayer.setData(geojson);
         }
-        console.log( geojson.features[0].geometry.coordinates.length)
         // Request the next frame of the animation.
         animation = requestAnimationFrame(animateLine);
       }
diff --git a/demos/assets/screenshots/01_animatePoint.png b/demos/assets/screenshots/01_animatePoint.png
new file mode 100644
index 0000000000..2486fbebc5
Binary files /dev/null and b/demos/assets/screenshots/01_animatePoint.png differ
diff --git a/demos/assets/screenshots/01_point_circle.png b/demos/assets/screenshots/01_point_circle.png
index c049a40aad..829bcb7954 100644
Binary files a/demos/assets/screenshots/01_point_circle.png and b/demos/assets/screenshots/01_point_circle.png differ
diff --git a/demos/assets/screenshots/01_point_column.png b/demos/assets/screenshots/01_point_column.png
index 0d451be5b9..d643b3f43d 100644
Binary files a/demos/assets/screenshots/01_point_column.png and b/demos/assets/screenshots/01_point_column.png differ
diff --git a/demos/assets/screenshots/01_point_distribute.png b/demos/assets/screenshots/01_point_distribute.png
index f2345f8351..6ab860f52b 100644
Binary files a/demos/assets/screenshots/01_point_distribute.png and b/demos/assets/screenshots/01_point_distribute.png differ
diff --git a/demos/assets/screenshots/01_point_image.png b/demos/assets/screenshots/01_point_image.png
index d22999aecd..974b17a2c4 100644
Binary files a/demos/assets/screenshots/01_point_image.png and b/demos/assets/screenshots/01_point_image.png differ
diff --git a/demos/assets/screenshots/02_animateline.png b/demos/assets/screenshots/02_animateline.png
new file mode 100644
index 0000000000..dcce8a146f
Binary files /dev/null and b/demos/assets/screenshots/02_animateline.png differ
diff --git a/demos/assets/screenshots/02_contour.png b/demos/assets/screenshots/02_contour.png
index 8832d56b21..014d9d3b15 100644
Binary files a/demos/assets/screenshots/02_contour.png and b/demos/assets/screenshots/02_contour.png differ
diff --git a/demos/assets/screenshots/02_oneBletoneRoad.png b/demos/assets/screenshots/02_oneBletoneRoad.png
index 9f9d920822..1efcc4c127 100644
Binary files a/demos/assets/screenshots/02_oneBletoneRoad.png and b/demos/assets/screenshots/02_oneBletoneRoad.png differ
diff --git a/demos/assets/screenshots/03_1_extrude_polygon.png b/demos/assets/screenshots/03_1_extrude_polygon.png
index c049a40aad..3bfd17c60b 100644
Binary files a/demos/assets/screenshots/03_1_extrude_polygon.png and b/demos/assets/screenshots/03_1_extrude_polygon.png differ
diff --git a/demos/assets/screenshots/03_choropleths_polygon.png b/demos/assets/screenshots/03_choropleths_polygon.png
index 64e3e8d621..fb36580103 100644
Binary files a/demos/assets/screenshots/03_choropleths_polygon.png and b/demos/assets/screenshots/03_choropleths_polygon.png differ
diff --git a/demos/assets/screenshots/04_image.png b/demos/assets/screenshots/04_image.png
index c049a40aad..a9dabd3b55 100644
Binary files a/demos/assets/screenshots/04_image.png and b/demos/assets/screenshots/04_image.png differ
diff --git a/demos/assets/screenshots/05_raster_dem.png b/demos/assets/screenshots/05_raster_dem.png
index c049a40aad..829bcb7954 100644
Binary files a/demos/assets/screenshots/05_raster_dem.png and b/demos/assets/screenshots/05_raster_dem.png differ
diff --git a/demos/assets/screenshots/06_text.png b/demos/assets/screenshots/06_text.png
index c049a40aad..e4a6de2833 100644
Binary files a/demos/assets/screenshots/06_text.png and b/demos/assets/screenshots/06_text.png differ
diff --git a/demos/assets/screenshots/07_city.png b/demos/assets/screenshots/07_city.png
index c049a40aad..023f8e252b 100644
Binary files a/demos/assets/screenshots/07_city.png and b/demos/assets/screenshots/07_city.png differ
diff --git a/demos/assets/screenshots/08_point_shape.png b/demos/assets/screenshots/08_point_shape.png
index 68525450f4..24873ac275 100644
Binary files a/demos/assets/screenshots/08_point_shape.png and b/demos/assets/screenshots/08_point_shape.png differ
diff --git a/demos/assets/screenshots/grid.png b/demos/assets/screenshots/grid.png
new file mode 100644
index 0000000000..6ab860f52b
Binary files /dev/null and b/demos/assets/screenshots/grid.png differ
diff --git a/demos/assets/screenshots/heatmap.png b/demos/assets/screenshots/heatmap.png
new file mode 100644
index 0000000000..619506ae98
Binary files /dev/null and b/demos/assets/screenshots/heatmap.png differ
diff --git a/demos/assets/screenshots/hexgon.png b/demos/assets/screenshots/hexgon.png
new file mode 100644
index 0000000000..6ab860f52b
Binary files /dev/null and b/demos/assets/screenshots/hexgon.png differ
diff --git a/demos/assets/screenshots/index.png b/demos/assets/screenshots/index.png
new file mode 100644
index 0000000000..939021031a
Binary files /dev/null and b/demos/assets/screenshots/index.png differ
diff --git a/src/core/atlas/font-manager.js b/src/core/atlas/font-manager.js
index 0fdb8bb53f..b51ffe17e3 100644
--- a/src/core/atlas/font-manager.js
+++ b/src/core/atlas/font-manager.js
@@ -1,7 +1,7 @@
 import TinySDF from '@mapbox/tiny-sdf';
-import { buildMapping } from '../../../../util/font-util';
-import * as THREE from '../../../../core/three';
-import LRUCache from './lru-cache';
+import { buildMapping } from '../../util/font-util';
+import * as THREE from '../../core/three';
+import LRUCache from '../../util/lru-cache';
 export const DEFAULT_CHAR_SET = getDefaultCharacterSet();
 export const DEFAULT_FONT_FAMILY = 'sans-serif';
 export const DEFAULT_FONT_WEIGHT = 'normal';
diff --git a/src/core/atlas/icon-manager.js b/src/core/atlas/icon-manager.js
index e69de29bb2..74d7b9653f 100644
--- a/src/core/atlas/icon-manager.js
+++ b/src/core/atlas/icon-manager.js
@@ -0,0 +1,52 @@
+import { buildIconMaping } from '../../util/font-util';
+import * as THREE from '../../../../core/three';
+const BUFFER = 3;
+const MAX_CANVAS_WIDTH = 1024;
+export default class IconManager {
+  constructor() {
+    this._getIcon = null;
+    this._mapping = {};
+    this._autoPacking = false;
+    this.iconData = {};
+    this._canvas = document.createElement('canvas');
+    this._texture = new THREE.Texture(this._canvas);
+    this.ctx = this._canvas.getContext('2d');
+  }
+  getTexture() {
+    return this._texture;
+  }
+
+  _updateIconAtlas() {
+    this._canvas.width = MAX_CANVAS_WIDTH;
+    this._canvas.height = this._canvasHeigth;
+    for (const key in this.mapping) {
+      const icon = this.mapping[key];
+      const { x, y, image } = icon;
+      this.ctx.drawImage(image, x, y, this.imageWidth, this.imageWidth);
+    }
+    this.texture.magFilter = THREE.LinearFilter;
+    this.texture.minFilter = THREE.LinearFilter;
+    this.texture.needsUpdate = true;
+  }
+
+  addImage(id, opt) {
+    this._loadImage(opt).then(image => {
+      this.iconData.push({ id, image });
+      const { mapping, canvasHeight } = buildIconMaping(this.iconData, BUFFER, MAX_CANVAS_WIDTH);
+      this._mapping = mapping;
+      this._canvasHeigth = canvasHeight;
+    });
+  }
+  _loadImage(url) {
+    return new Promise((resolve, reject) => {
+      const image = new Image();
+      image.onload = () => {
+        resolve(image);
+      };
+      image.onerror = function() {
+        reject(new Error('Could not load image at ' + url));
+      };
+      image.src = url;
+    });
+  }
+}
diff --git a/src/core/atlas/lru-cache.js b/src/core/atlas/lru-cache.js
deleted file mode 100644
index 8b417e53e2..0000000000
--- a/src/core/atlas/lru-cache.js
+++ /dev/null
@@ -1,71 +0,0 @@
-/**
- * LRU Cache class with limit
- *
- * Update order for each get/set operation
- * Delete oldest when reach given limit
- */
-
-export default class LRUCache {
-  constructor(limit = 5) {
-    this.limit = limit;
-
-    this.clear();
-  }
-
-  clear() {
-    this._cache = {};
-    // access/update order, first item is oldest, last item is newest
-    this._order = [];
-  }
-
-  get(key) {
-    const value = this._cache[key];
-    if (value) {
-      // update order
-      this._deleteOrder(key);
-      this._appendOrder(key);
-    }
-    return value;
-  }
-
-  set(key, value) {
-    if (!this._cache[key]) {
-      // if reach limit, delete the oldest
-      if (Object.keys(this._cache).length === this.limit) {
-        this.delete(this._order[0]);
-      }
-
-      this._cache[key] = value;
-      this._appendOrder(key);
-    } else {
-      // if found in cache, delete the old one, insert new one to the first of list
-      this.delete(key);
-
-      this._cache[key] = value;
-      this._appendOrder(key);
-    }
-  }
-
-  delete(key) {
-    const value = this._cache[key];
-    if (value) {
-      this._deleteCache(key);
-      this._deleteOrder(key);
-    }
-  }
-
-  _deleteCache(key) {
-    delete this._cache[key];
-  }
-
-  _deleteOrder(key) {
-    const index = this._order.findIndex(o => o === key);
-    if (index >= 0) {
-      this._order.splice(index, 1);
-    }
-  }
-
-  _appendOrder(key) {
-    this._order.push(key);
-  }
-}
diff --git a/src/core/layer.js b/src/core/layer.js
index 290e64a6fc..507729b617 100644
--- a/src/core/layer.js
+++ b/src/core/layer.js
@@ -10,6 +10,7 @@ import source from './source';
 import pickingFragmentShader from '../core/engine/picking/picking_frag.glsl';
 import { getInteraction } from '../interaction/index';
 import Attr from '../attr/index';
+import diff from '../util/diff';
 import Util from '../util';
 import Global from '../global';
 let id = 1;
@@ -36,6 +37,7 @@ export default class Layer extends Base {
       attrOptions: {
       },
       scaleOptions: {},
+      preScaleOptions: null,
       scales: {},
       attrs: {},
       // 样式配置项
@@ -84,10 +86,11 @@ export default class Layer extends Base {
     * @param {*} type mesh类型是区别是填充还是边线
    */
   add(object, type = 'fill') {
-     // composer合图层绘制
+    // composer合图层绘制
     if (object.type === 'composer') {
       this._object3D = object;
       this.scene._engine.composerLayers.push(object);
+      setTimeout(() => this.scene._engine.update(), 500);
       return;
     }
     type === 'fill' ? this.layerMesh = object : this.layerLineMesh = object;
@@ -102,16 +105,11 @@ export default class Layer extends Base {
     object.onAfterRender = () => { // 每次渲染后改变状态
       this.afterRender();
     };
-    // 更新
-    if (this._needUpdateFilter) { // 动态更新数据过滤
-      this._updateFilter(object);
-    }
     this._object3D.add(object);
     if (type === 'fill') {
       this._addPickMesh(object);// 不对边界线进行拾取
     }
-    this.scene._engine.update();
-    setTimeout(() => this.scene._engine.update(), 200);
+    setTimeout(() => this.scene._engine.update(), 500);
   }
   remove(object) {
     if (object.type === 'composer') {
@@ -147,7 +145,6 @@ export default class Layer extends Base {
     return this;
   }
   color(field, values) {
-    this._needUpdateColor = true;// 标识颜色是否需要更新
     this._createAttrOption('color', field, values, Global.colors);
     return this;
   }
@@ -227,7 +224,6 @@ export default class Layer extends Base {
   }
 
   filter(field, values) {
-    this._needUpdateFilter = true;
     this._createAttrOption('filter', field, values, true);
     return this;
   }
@@ -323,6 +319,11 @@ export default class Layer extends Base {
     }
     return scale;
   }
+  render() {
+    this.init();
+    this.scene._engine.update();
+    return this;
+  }
   // 重绘 度量, 映射,顶点构建
   repaint() {
     this.set('scales', {});
@@ -335,11 +336,7 @@ export default class Layer extends Base {
   init() {
     this._initControllers();
     this._initAttrs();
-    this._scaleByZoom();
-    this._initInteraction();
-    this._initMapEvent();
-
-    this._mapping();
+    this._updateDraw();
   }
   _initInteraction() {
     if (this.get('allowActive')) {
@@ -388,6 +385,7 @@ export default class Layer extends Base {
 
 
   _initAttrs() {
+    // 对比 options变化判断如何更新
     const attrOptions = this.get('attrOptions');
     for (const type in attrOptions) {
       if (attrOptions.hasOwnProperty(type)) {
@@ -395,6 +393,55 @@ export default class Layer extends Base {
       }
     }
   }
+  _setPreOption() {
+    const nextAttrs = this.get('attrOptions');
+    const nextStyle = this.get('styleOptions');
+    this.set('preAttrOptions', Util.clone(nextAttrs));
+    this.set('preStyleOption', Util.clone(nextStyle));
+  }
+  _updateDraw() {
+    const preAttrs = this.get('preAttrOptions');
+    const nextAttrs = this.get('attrOptions');
+    const preStyle = this.get('preStyleOption');
+    const nextStyle = this.get('styleOptions');
+    if (preAttrs === undefined && preStyle === undefined) {
+      this._mapping();
+      this._setPreOption();
+      this._scaleByZoom();
+      this._initInteraction();
+      this._initMapEvent();
+      this.draw();
+      return;
+    }
+    if (!Util.isEqual(preAttrs.color, nextAttrs.color)) {
+      this._updateAttributes(this.layerMesh);
+    }
+    // 更新数据过滤 filter
+    if (!Util.isEqual(preAttrs.filter, nextAttrs.filter)) {
+      // 更新color;
+      this._updateAttributes(this.layerMesh);
+    }
+    // 更新Size
+    if (!Util.isEqual(preAttrs.size, nextAttrs.size)) {
+      // 更新color;
+      this._updateSize();
+    }
+    // 更新形状
+    if (!Util.isEqual(preAttrs.shape, nextAttrs.shape)) {
+      // 更新color;
+      this._updateShape();
+    }
+    if (!Util.isEqual(preStyle, nextStyle)) {
+      // 判断新增,修改,删除
+      const newStyle = {};
+      Util.each(diff(preStyle, nextStyle), ({ type, key, value }) => {
+        (type !== 'remove') && (newStyle[key] = value);
+        // newStyle[key] = type === 'remove' ? null : value;
+      });
+      this._updateStyle(newStyle);
+    }
+    this._setPreOption();
+  }
 
   _updateAttr(type) {
     const self = this;
@@ -435,7 +482,14 @@ export default class Layer extends Base {
     }
     this.emit('sizeUpdated', this.zoomSizeCache[zoom]);
   }
+  _updateStyle(option) {
+    const newOption = { };
+    for (const key in option) {
+      newOption['u_' + key] = option[key];
+    }
+    this.layerMesh.material.updateUninform(newOption);
 
+  }
   _mapping() {
     const self = this;
     const attrs = self.get('attrs');
@@ -445,13 +499,12 @@ export default class Layer extends Base {
     for (let i = 0; i < data.length; i++) {
       const record = data[i];
       const newRecord = {};
+
       newRecord.id = data[i]._id;
       for (const k in attrs) {
         if (attrs.hasOwnProperty(k)) {
           const attr = attrs[k];
-          attr.needUpdate = false;
           const names = attr.names;
-
           const values = self._getAttrValues(attr, record);
           if (names.length > 1) { // position 之类的生成多个字段的属性
             for (let j = 0; j < values.length; j++) {
@@ -468,6 +521,12 @@ export default class Layer extends Base {
       newRecord.coordinates = record.coordinates;
       mappedData.push(newRecord);
     }
+    // 通过透明度过滤数据
+    if (attrs.hasOwnProperty('filter')) {
+      mappedData.forEach(item => {
+        item.filter === false && (item.color[3] = 0);
+      });
+    }
     this.layerData = mappedData;
   }
 
@@ -580,7 +639,7 @@ export default class Layer extends Base {
    *  用于过滤数据
    * @param {*} object  更新颜色和数据过滤
    */
-  _updateFilter(object) {
+  _updateAttributes(object) {
     this._updateMaping();
     const filterData = this.layerData;
     this._activeIds = null; // 清空选中元素
@@ -621,7 +680,7 @@ export default class Layer extends Base {
     } else if (this.type === 'polyline') {
       offset = 2;
     }
-    this._object3D.position.z = offset * Math.pow(2, 20 - zoom);
+    this._object3D.position && (this._object3D.position.z = offset * Math.pow(2, 20 - zoom));
     if (zoom < minZoom || zoom > maxZoom) {
       this._object3D.visible = false;
     } else if (this.get('visible')) {
diff --git a/src/core/scene.js b/src/core/scene.js
index e662d45b9f..1309cfd525 100644
--- a/src/core/scene.js
+++ b/src/core/scene.js
@@ -7,6 +7,7 @@ import FontAtlasManager from '../geom/buffer/point/text/font-manager';
 // import { MapProvider } from '../map/AMap';
 import { getMap } from '../map/index';
 import Global from '../global';
+import { getInteraction } from '../interaction/index';
 import { compileBuiltinModules } from '../geom/shader';
 export default class Scene extends Base {
   getDefaultCfg() {
@@ -46,6 +47,12 @@ export default class Scene extends Base {
       Map.asyncCamera(this._engine);
       this.initLayer();
       this._registEvents();
+      const hash = this.get('hash');
+      if (hash) {
+        const Ctor = getInteraction('hash');
+        const interaction = new Ctor({ layer: this });
+        interaction._onHashChange();
+      }
       this.emit('loaded');
     });
 
diff --git a/src/geom/material/heatmapMateial.js b/src/geom/material/heatmapMateial.js
index 315d19c7b9..d1ff1c26e0 100644
--- a/src/geom/material/heatmapMateial.js
+++ b/src/geom/material/heatmapMateial.js
@@ -1,35 +1,58 @@
 import * as THREE from '../../core/three';
 import Material from './material';
 import { getModule } from '../../util/shaderModule';
+export class HeatmapColorizeMaterial extends Material {
+  getDefaultParameters() {
+    return {
+      uniforms: {
+        u_intensity: { value: 1.0 },
+        u_texture: { value: null },
+        u_rampColors: { value: 0 },
+        u_opacity: { value: 1 }
+      },
+      defines: {
 
-export function HeatmapIntensityMaterial(opt) {
-  const { vs, fs } = getModule('heatmap_intensity');
-  const material = new Material({
-    uniforms: {
-      u_intensity: { value: opt.intensity },
-      u_radius: { value: opt.radius },
-      u_zoom: { value: opt.zoom }
-    },
-    vertexShader: vs,
-    fragmentShader: fs,
-    transparent: true,
-    depthTest: false,
-    blending: THREE.AdditiveBlending
-  });
-  return material;
+      }
+    };
+  }
+  constructor(_uniforms, _defines = {}, parameters) {
+    super(parameters);
+    const { uniforms, defines } = this.getDefaultParameters();
+    const { vs, fs } = getModule('heatmap_color');
+    this.uniforms = Object.assign(uniforms, this.setUniform(_uniforms));
+    this.type = 'HeatmapColorizeMaterial';
+    this.defines = Object.assign(defines, _defines);
+    this.vertexShader = vs;
+    this.fragmentShader = fs;
+    this.transparent = true;
+  }
 }
 
-export function HeatmapColorizeMaterial(opt) {
-  const { vs, fs } = getModule('heatmap_color');
-  const material = new Material({
-    uniforms: {
-      u_texture: { value: opt.texture },
-      u_colorRamp: { value: opt.colorRamp },
-      u_opacity: { value: opt.opacity }
-    },
-    vertexShader: vs,
-    fragmentShader: fs,
-    transparent: true
-  });
-  return material;
+export class HeatmapIntensityMaterial extends Material {
+  getDefaultParameters() {
+    return {
+      uniforms: {
+        u_intensity: { value: 10.0 },
+        u_zoom: { value: 4 },
+        u_radius: { value: 10 }
+      },
+      defines: {
+
+      }
+    };
+  }
+  constructor(_uniforms, _defines = {}, parameters) {
+    super(parameters);
+    const { uniforms, defines } = this.getDefaultParameters();
+    const { vs, fs } = getModule('heatmap_intensity');
+    this.uniforms = Object.assign(uniforms, this.setUniform(_uniforms));
+    this.type = 'heatmap_intensity';
+    this.defines = Object.assign(defines, _defines);
+    this.vertexShader = vs;
+    this.blending = THREE.AdditiveBlending;
+    this.fragmentShader = fs;
+    this.depthTest = false;
+    this.transparent = true;
+  }
 }
+
diff --git a/src/geom/material/material.js b/src/geom/material/material.js
index 33e9d25fd0..d6b52304ce 100644
--- a/src/geom/material/material.js
+++ b/src/geom/material/material.js
@@ -19,7 +19,7 @@ export default class Material extends THREE.ShaderMaterial {
     }
     return uniforms;
   }
-  upDateUninform(option) {
+  updateUninform(option) {
     for (const key in option) {
       if (key.substr(0, 2) === 'u_') {
         this.setUniformsValue(key, option[key]);
diff --git a/src/geom/shader/heatmap_colorize_frag.glsl b/src/geom/shader/heatmap_colorize_frag.glsl
index 4cd75f67ed..4d5af730bc 100644
--- a/src/geom/shader/heatmap_colorize_frag.glsl
+++ b/src/geom/shader/heatmap_colorize_frag.glsl
@@ -1,11 +1,11 @@
 uniform sampler2D u_texture;
-uniform sampler2D u_colorRamp;
+uniform sampler2D u_rampColors;
   uniform float u_opacity;
 varying vec2 v_uv;
 
 void main(){
     float intensity = texture2D(u_texture,v_uv).r;
-    vec4 color = texture2D(u_colorRamp,vec2(0.5,1.0-intensity));
+    vec4 color = texture2D(u_rampColors,vec2(0.5,1.0-intensity));
     gl_FragColor = color;
     gl_FragColor.a = color.a * smoothstep(0.,0.05,intensity) * u_opacity;
     
diff --git a/src/global.js b/src/global.js
index 83d6ab5fc5..8bf3153379 100644
--- a/src/global.js
+++ b/src/global.js
@@ -3,6 +3,7 @@
  * @author dxq613
  */
 // const Global = {};
+const FONT_FAMILY = '"-apple-system", BlinkMacSystemFont, "Segoe UI", Roboto,"Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei",SimSun, "sans-serif"';
 const Global = {
   version: '1.0.0',
   scene: {
@@ -11,16 +12,13 @@ const Global = {
     center: [ 107.622, 39.266 ],
     minZoom: 0,
     maxZoom: 22,
-    pitch: 0
+    pitch: 0,
+    hash: false
   },
-  trackable: true,
   animate: true,
-  snapArray: [ 0, 1, 2, 4, 5, 10 ],
   height: 0,
   activeColor: '#2f54eb',
   colors: [ 'rgb(103,0,31)', 'rgb(178,24,43)', 'rgb(214,96,77)', 'rgb(244,165,130)', 'rgb(253,219,199)', 'rgb(247,247,247)', 'rgb(209,229,240)', 'rgb(146,197,222)', 'rgb(67,147,195)', 'rgb(33,102,172)', 'rgb(5,48,97)' ],
-  // 指定固定 tick 数的逼近值
-  snapCountArray: [ 0, 1, 1.2, 1.5, 1.6, 2, 2.2, 2.4, 2.5, 3, 4, 5, 6, 7.5, 8, 10 ],
   size: 10000,
   shape: 'circle',
   pointShape: {
@@ -28,7 +26,13 @@ const Global = {
     '3d': [ 'cylinder', 'triangleColumn', 'hexagonColumn', 'squareColumn' ]
   },
   sdfHomeUrl: 'https://sdf.amap.com',
-  scales: {
+  scales: { },
+  textStyle: {
+    fontSize: 12,
+    fill: '#ccc',
+    textBaseline: 'middle',
+    fontFamily: FONT_FAMILY,
+    textAlign: 'center'
   }
 };
 
diff --git a/src/interaction/factory.js b/src/interaction/factory.js
index 4442e4b7e9..46143b74d0 100644
--- a/src/interaction/factory.js
+++ b/src/interaction/factory.js
@@ -5,10 +5,10 @@ export const getInteraction = type => {
 };
 
 export const registerInteraction = (type, ctor) => {
-    // 注册的时候,需要校验 type 重名,不区分大小写
+  // 注册的时候,需要校验 type 重名,不区分大小写
   if (getInteraction(type)) {
     throw new Error(`Interaction type '${type}' existed.`);
   }
-    // 存储到 map 中
+  // 存储到 map 中
   INTERACTION_MAP[type] = ctor;
 };
diff --git a/src/interaction/hash.js b/src/interaction/hash.js
new file mode 100644
index 0000000000..b06188a6f7
--- /dev/null
+++ b/src/interaction/hash.js
@@ -0,0 +1,58 @@
+import Interaction from './base';
+import throttle from '@antv/util/src/throttle.js';
+export default class Hash extends Interaction {
+  constructor(cfg) {
+    super({
+      endEvent: 'camerachange',
+      ...cfg
+    });
+    window.addEventListener('hashchange', this._onHashChange.bind(this), false);
+    this._updateHash = throttle(this._updateHashUnthrottled.bind(this), 20 * 1000 / 100);
+  }
+  end() {
+    this._updateHash();
+  }
+  reset() {
+    this.layer._resetStyle();
+  }
+  _getHashString() {
+    const center = this.layer.getCenter(),
+      zoom = Math.round(this.layer.getZoom() * 100) / 100,
+      // derived from equation: 512px * 2^z / 360 / 10^d < 0.5px
+      precision = Math.ceil((zoom * Math.LN2 + Math.log(512 / 360 / 0.5)) / Math.LN10),
+      m = Math.pow(10, precision),
+      lng = Math.round(center.lng * m) / m,
+      lat = Math.round(center.lat * m) / m,
+      bearing = this.layer.getRotation(),
+      pitch = this.layer.getPitch();
+    let hash = '';
+    hash += `#${zoom}/${lat}/${lng}`;
+    if (bearing || pitch) hash += (`/${Math.round(bearing * 10) / 10}`);
+    if (pitch) hash += (`/${Math.round(pitch)}`);
+    return hash;
+  }
+  _onHashChange() {
+    const loc = window.location.hash.replace('#', '').split('/');
+    if (loc.length >= 3) {
+      this.layer.setStatus({
+        center: [ +loc[2], +loc[1] ],
+        zoom: +loc[0],
+        bearing: +(loc[3] || 0),
+        pitch: +(loc[4] || 0)
+      });
+      return true;
+    }
+    return false;
+  }
+  _updateHashUnthrottled() {
+    const hash = this._getHashString();
+    window.history.replaceState(window.history.state, '', hash);
+  }
+  destory() {
+    window.removeEventListener('hashchange', this._onHashChange, false);
+    this.layer.off('camerachange', this._updateHash);
+    clearTimeout(this._updateHash());
+
+    return this;
+  }
+}
diff --git a/src/interaction/index.js b/src/interaction/index.js
index 838f27ddac..54beb99308 100644
--- a/src/interaction/index.js
+++ b/src/interaction/index.js
@@ -1,9 +1,11 @@
 import Interaction from './base';
 import Active from './active';
 import Select from './select';
+import Hash from './hash';
 import { getInteraction, registerInteraction } from './factory';
 
 registerInteraction('active', Active);
 registerInteraction('select', Select);
+registerInteraction('hash', Hash);
 
 export { Interaction, registerInteraction, getInteraction };
diff --git a/src/layer/heatmapLayer.js b/src/layer/heatmapLayer.js
index 1c729001df..e47b5656fd 100644
--- a/src/layer/heatmapLayer.js
+++ b/src/layer/heatmapLayer.js
@@ -10,12 +10,7 @@ export default class HeatMapLayer extends Layer {
     this.shapeType = type;
     return this;
   }
-  render() {
-    this.draw();
-    return this;
-  }
   draw() {
-    this.init();
     this.type = 'heatmap';
     switch (this.shapeType) {
       case 'grid' :
diff --git a/src/layer/imageLayer.js b/src/layer/imageLayer.js
index 92e9a60b59..95f4d49767 100644
--- a/src/layer/imageLayer.js
+++ b/src/layer/imageLayer.js
@@ -4,8 +4,7 @@ import ImageBuffer from '../geom/buffer/image';
 // import ImageGeometry from '../geom/bufferGeometry/image';
 import ImageMaterial from '../geom/material/imageMaterial';
 export default class imageLayer extends Layer {
-  render() {
-    this.init();
+  draw() {
     this.type = 'image';
     const source = this.layerSource;
     const { opacity } = this.get('styleOptions');
diff --git a/src/layer/lineLayer.js b/src/layer/lineLayer.js
index dc26bd307b..4a2046686e 100644
--- a/src/layer/lineLayer.js
+++ b/src/layer/lineLayer.js
@@ -7,12 +7,6 @@ export default class LineLayer extends Layer {
     this.shapeType = type;
     return this;
   }
-  render() {
-    this.type = 'polyline';
-    this.init();
-    this.draw();
-    return this;
-  }
   preRender() {
     if (
       this.animateDuration > 0 &&
@@ -25,6 +19,7 @@ export default class LineLayer extends Layer {
     }
   }
   draw() {
+    this.type = 'polyline';
     const layerData = this.layerData;
     const style = this.get('styleOptions');
     const animateOptions = this.get('animateOptions');
diff --git a/src/layer/pointLayer.js b/src/layer/pointLayer.js
index 8168a67c56..1e6efc7ae3 100644
--- a/src/layer/pointLayer.js
+++ b/src/layer/pointLayer.js
@@ -14,21 +14,8 @@ const { pointShape } = Global;
  */
 
 export default class PointLayer extends Layer {
-  render() {
-    this.type = 'point';
-    this.init();
-    if (!this._hasRender) {
-      this.draw();
-      this._hasRender = true;
-    } else {
-      this._initAttrs();
-      this._needUpdateFilter || this._needUpdateColor
-        ? this._updateFilter()
-        : null;
-    }
-    return this;
-  }
   draw() {
+    this.type = 'point';
     const { stroke, fill } = this.get('styleOptions');
     const style = this.get('styleOptions');
     const activeOption = this.get('activedOptions');
diff --git a/src/layer/polygonLayer.js b/src/layer/polygonLayer.js
index 267e29cc9b..e25c052038 100644
--- a/src/layer/polygonLayer.js
+++ b/src/layer/polygonLayer.js
@@ -6,18 +6,6 @@ export default class PolygonLayer extends Layer {
     this.shape = type;
     return this;
   }
-  render() {
-    if (!this._hasRender) { // 首次渲染
-      this._hasRender = true;
-      this.draw();
-    } else {
-
-      this._initAttrs();
-      (this._needUpdateFilter || this._needUpdateColor) ? this._updateFilter(this.layerMesh) : null;
-      // TODO update Style;
-    }
-    return this;
-  }
   draw() {
     this.init();
     this.type = 'polygon';
diff --git a/src/layer/rasterLayer.js b/src/layer/rasterLayer.js
index f3816da695..40075e54c0 100644
--- a/src/layer/rasterLayer.js
+++ b/src/layer/rasterLayer.js
@@ -5,9 +5,8 @@ import { RasterBuffer } from '../geom/buffer/raster';
 
 export default class RasterLayer extends Layer {
 
-  render() {
+  draw() {
     this.type = 'raster';
-    this.init();
     const source = this.layerSource;
     // 加载 完成事件
     const styleOptions = this.get('styleOptions');
diff --git a/src/layer/render/heatmap/heatmap.js b/src/layer/render/heatmap/heatmap.js
index b035f38942..c03d7f9b46 100644
--- a/src/layer/render/heatmap/heatmap.js
+++ b/src/layer/render/heatmap/heatmap.js
@@ -10,7 +10,8 @@ import * as THREE from '../../../core/three';
 export function drawHeatmap(layer) {
 
   const colors = layer.get('styleOptions').rampColors;
-  layer.colorRamp = createColorRamp(colors);
+
+  layer.rampColors = createColorRamp(colors);
   const heatmap = new heatmapPass(layer);
   const copy = new copyPass(layer);
   copy.renderToScreen = true;
@@ -18,31 +19,42 @@ export function drawHeatmap(layer) {
   composer.addPass(heatmap);
   composer.addPass(copy);
   layer.add(composer);
+  layer.scene._engine.update();
+  layer._updateStyle = style => {
+    if (style.rampColors) {
+      style.rampColors = createColorRamp(style.rampColors);
+    }
+    const newOption = { };
+    for (const key in style) {
+      newOption['u_' + key] = style[key];
+    }
+    heatmap.scene.children[0].material.updateUninform(newOption);
+    copy.scene.children[0].material.updateUninform(newOption);
+  };
 
 }
 
-
 function heatmapPass(layer) {
   const scene = new THREE.Scene();
   const style = layer.get('styleOptions');
   const data = layer.layerData;
   const camera = layer.scene._engine._camera;
-    // get attributes data
+  // get attributes data
   const buffer = new HeatmapBuffer({
     data
   });
   const attributes = buffer.attributes;
-    // create geometery
+  // create geometery
   const geometry = new THREE.BufferGeometry();
-    // geometry.setIndex(attributes.indices);
+  // geometry.setIndex(attributes.indices);
   geometry.addAttribute('position', new THREE.Float32BufferAttribute(attributes.vertices, 3));
   geometry.addAttribute('a_dir', new THREE.Float32BufferAttribute(attributes.dirs, 2));
   geometry.addAttribute('a_weight', new THREE.Float32BufferAttribute(attributes.weights, 1));
   const material = new HeatmapIntensityMaterial({
-    intensity: style.intensity,
-    radius: style.radius,
-    zoom: layer.scene.getZoom()
-  });
+    u_intensity: style.intensity,
+    u_radius: style.radius,
+    u_zoom: layer.scene.getZoom()
+  }, {});
   const mesh = new THREE.Mesh(geometry, material);
   scene.add(mesh);
   scene.onBeforeRender = () => { // 每次渲染前改变状态
@@ -55,9 +67,9 @@ function heatmapPass(layer) {
 function copyPass(layer) {
   const style = layer.get('styleOptions');
   const material = new HeatmapColorizeMaterial({
-    colorRamp: layer.colorRamp,
-    opacity: style.opacity
-  });
+    u_rampColors: layer.rampColors,
+    u_opacity: style.opacity
+  }, {});
   const copyPass = new ShaderPass(material, 'u_texture');
   return copyPass;
 }
diff --git a/src/layer/render/line/drawMeshLine.js b/src/layer/render/line/drawMeshLine.js
index bb3fbe15c7..37eaf2ad25 100644
--- a/src/layer/render/line/drawMeshLine.js
+++ b/src/layer/render/line/drawMeshLine.js
@@ -33,7 +33,7 @@ export default function DrawLine(attributes, cfg, layer) {
     } = animateOptions;
     layer.animateDuration =
     layer.scene._engine.clock.getElapsedTime() + duration * repeat;
-    lineMaterial.upDateUninform({
+    lineMaterial.updateUninform({
       u_duration: duration,
       u_interval: interval,
       u_trailLength: trailLength
diff --git a/src/layer/render/polygon/drawAnimate.js b/src/layer/render/polygon/drawAnimate.js
index e8a4883bb5..1e7bb408fa 100644
--- a/src/layer/render/polygon/drawAnimate.js
+++ b/src/layer/render/polygon/drawAnimate.js
@@ -25,7 +25,7 @@ export default function DrawAnimate(attributes, style) {
 }
 
 DrawAnimate.prototype.updateStyle = function(style) {
-  this.fillPolygonMesh.material.upDateUninform({
+  this.fillPolygonMesh.material.updateUninform({
     u_opacity: style.opacity,
     u_baseColor: style.baseColor,
     u_brightColor: style.brightColor,
diff --git a/src/map/AMap.js b/src/map/AMap.js
index b5666bee4a..c4ba78e2a5 100644
--- a/src/map/AMap.js
+++ b/src/map/AMap.js
@@ -141,6 +141,9 @@ export default class GaodeMap extends Base {
     scene.setRotation = rotation => {
       return map.setRotation(rotation);
     };
+    scene.setStatus = status => {
+      return map.setStatus(status);
+    };
     scene.zoomIn = () => {
       return map.zoomIn();
     };
diff --git a/src/util/diff.js b/src/util/diff.js
new file mode 100644
index 0000000000..949b8fd08d
--- /dev/null
+++ b/src/util/diff.js
@@ -0,0 +1,38 @@
+import * as _ from '@antv/util';
+export default function diff(a, b) {
+  // Throw is a or b are not objects.
+  if (!_.isPlainObject(a)) {
+    throw new Error('First parameter to diff() is not an object');
+  }
+  if (!_.isPlainObject(b)) {
+    throw new Error('Second parameter to diff() is not an object');
+  }
+
+  const changes = [];
+  const keysA = _.keys(a);
+  const keysB = _.keys(b);
+
+  // Find the items in A that are not in B.
+  _.each(_.difference(keysA, keysB), key => {
+    changes.push({ type: 'remove', key, value: a[key] });
+  });
+
+  // Find the items in B that are not in A.
+  _.each(_.difference(keysB, keysA), key => {
+    changes.push({ type: 'add', key, value: b[key] });
+  });
+
+  // Find the items that are in both, but have changed.
+  _.each(intersection(keysA, keysB), key => {
+    if (!_.isEqual(a[key], b[key])) {
+      changes.push({ type: 'update', key, value: b[key] });
+    }
+  });
+
+  return changes;
+}
+function intersection(keysA, keysB) {
+  return keysA.filter(key => {
+    return keysB.indexOf(key) > -1;
+  });
+}
diff --git a/src/util/font-util.js b/src/util/font-util.js
index 1e0b96a98d..42c2387cb3 100644
--- a/src/util/font-util.js
+++ b/src/util/font-util.js
@@ -41,3 +41,73 @@ export function buildMapping({
     canvasHeight: nextPowOfTwo(yOffset + (row + 1) * rowHeight)
   };
 }
+
+function buildRowMapping(mapping, columns, yOffset) {
+  for (let i = 0; i < columns.length; i++) {
+    const { icon, xOffset } = columns[i];
+    mapping[icon.id] = Object.assign({}, icon, {
+      x: xOffset,
+      y: yOffset,
+      image: icon.image
+    });
+  }
+}
+export function resizeImage(ctx, imageData, width, height) {
+  const { naturalWidth, naturalHeight } = imageData;
+  if (width === naturalWidth && height === naturalHeight) {
+    return imageData;
+  }
+
+  ctx.canvas.height = height;
+  ctx.canvas.width = width;
+
+  ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
+
+  // image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight
+  ctx.drawImage(imageData, 0, 0, naturalWidth, naturalHeight, 0, 0, width, height);
+
+  return ctx.canvas;
+}
+
+export function buildIconMaping({ icons, buffer, maxCanvasWidth }) {
+  let xOffset = 0;
+  let yOffset = 0;
+  let rowHeight = 0;
+  let columns = [];
+  const mapping = {};
+  for (let i = 0; i < icons.length; i++) {
+    const icon = icons[i];
+    if (!mapping[icon.id]) {
+      const { height, width } = icon;
+
+      // fill one row
+      if (xOffset + width + buffer > maxCanvasWidth) {
+        buildRowMapping(mapping, columns, yOffset);
+
+        xOffset = 0;
+        yOffset = rowHeight + yOffset + buffer;
+        rowHeight = 0;
+        columns = [];
+      }
+
+      columns.push({
+        icon,
+        xOffset
+      });
+
+      xOffset = xOffset + width + buffer;
+      rowHeight = Math.max(rowHeight, height);
+    }
+  }
+
+  if (columns.length > 0) {
+    buildRowMapping(mapping, columns, yOffset);
+  }
+
+  const canvasHeight = nextPowOfTwo(rowHeight + yOffset + buffer);
+
+  return {
+    mapping,
+    canvasHeight
+  };
+}
diff --git a/src/util/index.js b/src/util/index.js
deleted file mode 100644
index 5d3404b1b8..0000000000
--- a/src/util/index.js
+++ /dev/null
@@ -1,25 +0,0 @@
-import CommonUtil from './common';
-import DomUtil from './dom';
-
-const Util = {};
-
-CommonUtil.merge(Util, CommonUtil, DomUtil, {
-  mixin(c, mixins) {
-    const Param = c.CFG ? 'CFG' : 'ATTRS';
-    if (c && mixins) {
-      c._mixins = mixins;
-      c[Param] = c[Param] || {};
-      const temp = {};
-      Util.each(mixins, function(mixin) {
-        Util.augment(c, mixin);
-        const attrs = mixin[Param];
-        if (attrs) {
-          Util.merge(temp, attrs);
-        }
-      });
-      c[Param] = Util.merge(temp, c[Param]);
-    }
-  }
-});
-
-export default Util;
diff --git a/test/unit/shader-module/base-spec.js b/test/unit/shader-module/base-spec.js
index 36410577d1..55ae9c0cba 100644
--- a/test/unit/shader-module/base-spec.js
+++ b/test/unit/shader-module/base-spec.js
@@ -22,8 +22,8 @@ describe('test shader module', function() {
   registerModule('common', commonModule);
   registerModule('module1', module1);
   it('should import a module correctly.', function() {
-   // expect(vs).eq('#define PI 3.14');
-   // expect(fs.replace(/(\s+)|(\n)+|(\r\n)+/g, '')).eqls('#ifdefGL_FRAGMENT_PRECISION_HIGHprecisionhighpfloat;#elseprecisionmediumpfloat;#endif');
+    // expect(vs).eq('#define PI 3.14');
+    // expect(fs.replace(/(\s+)|(\n)+|(\r\n)+/g, '')).eqls('#ifdefGL_FRAGMENT_PRECISION_HIGHprecisionhighpfloat;#elseprecisionmediumpfloat;#endif');
   });
 
 });