入门Leaflet之小Demo


写在前面 ---- WebGIS开发基础之Leaflet

  1. GIS基本概念:GIS、Map、Layer、Feature、Geometry、Symbol、Data(Point、Polyline、Polygon)、Renderer、Scale、Project、Coordinates;
  2. GIS开发概述:架构模式、常用平台和SDK、二维三维
  3. 使用Leaflet开发常用功能

    • 地图加载(底图类型、切换):
    • 地图操作(缩放、平移、定位/书签、动画):
    • 图层管理(加载、移除、调整顺序):
    • 要素标绘(点/聚簇、线、面,符号化/静态动态):
    • 属性标注(字段可选、样式定制):
    • 专题地图(点、线、面,渲染):
    • 查询定位(属性查询、空间查询/周边搜索/缓冲区/面查点线面/点线查面、图属互查、综合查询):
    • 信息窗口(入口、Popup、定制):
    • 坐标转换():
    • 空间运算(长度面积测量、点取坐标、缓冲区、相交包含关系):
    • 动态监控(固定点状态切换、车辆监控):

  1. Leaflet API

Demo用到的库


PART 1: 地图加载(底图类型、切换) Demo 1

  • 库引用
  1. <link rel="stylesheet" type="text/css" href="./lib/Flat-UI-master/dist/css/vendor/bootstrap/css/bootstrap.min.css"
  2. />
  3. <link rel="stylesheet" href="./lib/Flat-UI-master/dist/css/flat-ui.min.css">
  4. <link rel="stylesheet" href="./lib/leaflet/leaflet.css">
  1. <script src="./lib/Flat-UI-master/dist/js/vendor/jquery.min.js"></script>
  2. <script src="./lib/Flat-UI-master/dist/js/flat-ui.js"></script>
  3. <script src="./lib/leaflet/leaflet.js"></script>
  4. <script src="./js/urlTemplate.js"></script>
  • 地图加载与切换
  1. const map = L.map("mapDiv", {
  2. crs: L.CRS.EPSG3857, //要使用的坐标参考系统,默认的坐标参考系,互联网地图主流坐标系
  3. // crs: L.CRS.EPSG4326, //WGS 84坐标系,GPS默认坐标系
  4. zoomControl: true,
  5. // minZoom: 1,
  6. attributionControl: true,
  7. }).setView([30.6268660000, 104.1528940000], 18);//定位在成都北纬N30°37′45.58″ 东经E104°09′1.44″
  8. let Baselayer = L.tileLayer(urlTemplate.mapbox_Image, {
  9. maxZoom: 17, //最大视图
  10. minZoom: 2, //最小视图
  11. attribution: 'liuvigongzuoshi@foxmail.com &copy; <a href="https://github.com/liuvigongzuoshi/WebGIS-for-learnning/tree/master/Leaflet_Demo">WebGIS-for-learnning</a>'
  12. }).addTo(map);
  13. const setLayer = (ele) => {
  14. map.removeLayer(Baselayer)
  15. if (ele == "mapbox_Image") {
  16. Baselayer = L.tileLayer(urlTemplate.mapbox_Image, {
  17. maxZoom: 17,
  18. minZoom: 2
  19. }).addTo(map);
  20. } else if (ele == "mapbox_Vector") {
  21. Baselayer = L.tileLayer(urlTemplate.mapbox_Vector, {
  22. maxZoom: 17,
  23. // minZoom: 2
  24. }).addTo(map);
  25. console.log(Baselayer)
  26. }
  27. }

基于Demo 1 利用H5 Geolocation API 定位到当前位置 Demo 1.1

  • 库引用 如上 Demo 1
  1. //marker高亮显示库引用
  2. <link rel="stylesheet" href="./lib/leaflet.marker.highlight/leaflet.marker.highlight.css">
  3. <script src="./lib/leaflet.marker.highlight/leaflet.marker.highlight.js"></script>
  • 判断浏览器是否支持
  1. let map;
  2. let Baselayer;
  3. // 使用H5 API定位 定位在当前位置
  4. if (navigator.geolocation) {
  5. console.log('/* 地理位置服务可用 */')
  6. navigator.geolocation.getCurrentPosition(h5ApiSuccess, h5ApiError);
  7. } else {
  8. console.log('/* 地理位置服务不可用 */')
  9. mapInit([30.374558, 104.09144]);//指定一个数据 定位在成都北纬N30°37′45.58″ 东经E104°09′1.44″
  10. }
  • 定位成功或失败
  1. const h5ApiSuccess = (position) => {
  2. let latitude = position.coords.latitude; //纬度
  3. let longitude = position.coords.longitude; //经度
  4. console.log('你的经度纬度分别为' + longitude + ',' + latitude + '。')
  5. return mapInit([latitude, longitude]);
  6. };
  7. const h5ApiError = () => {
  8. console.log('/* 地理位置请求失败 */')
  9. mapInit([30.374558, 104.09144]);//指定一个数据 定位在成都北纬N30°37′45.58″ 东经E104°09′1.44″
  10. };
  • 成功后初始化底图
  1. const mapInit = (LatLng) => {
  2. map = L.map("mapDiv", {
  3. crs: L.CRS.EPSG3857, //要使用的坐标参考系统,默认的坐标参考系
  4. // crs: L.CRS.EPSG4326, //国内的坐标参考系
  5. zoomControl: true,
  6. // minZoom: 1,
  7. attributionControl: true,
  8. }).setView(LatLng, 18);//定位在当前位置
  9. Baselayer = L.tileLayer(urlTemplate.mapbox_Image, {
  10. maxZoom: 17, //最大视图
  11. minZoom: 2, //最小视图
  12. attribution: 'liuvigongzuoshi@foxmail.com &copy; <a href="https://github.com/liuvigongzuoshi/WebGIS-for-learnning/tree/master/Leaflet_Demo">WebGIS-for-learnning</a>'
  13. }).addTo(map);
  14. L.marker(LatLng, {
  15. highlight: "permanent" //永久高亮显示
  16. }).addTo(map);
  17. }
  • 更多了解geolocation对象,可参考MDN Web 文档
  • 更多了解使用marker高亮显示,可参考leaflet.marker.highlight插件
  • 基于Demo 1 利用eaflet封装好的H5定位API,定位到当前位置 Demo

PART 2: 地图操作(缩放、平移、定位/书签、动画) Demo 2

  • 库引用 如上 Demo 1
  • 设置地图缩放到指定图层
  1. map.setZoom(10, {
  2. // animate: false
  3. }) //设置地图缩放到
  • 图层往里进一个图层,放大
  1. map.zoomIn() //图层往里进一个图层,放大
  2. //map.zoomOut() //图层往里出一个图层,缩小
  • 地图平移至中心点
  1. map.panTo([37.91082, 128.73583], {
  2. animate: true
  3. }) //地图平移,默认就是true,将地图平移到给定的中心。如果新的中心点在屏幕内与现有的中心点不同则产生平移动作。
  • 地图飞到中心点
  1. map.flyTo([36.52, 120.31]); // 点到点的抛物线动画,平移加缩放动画

注意:尽量避免setZoom()等地图缩放方法与flyTo、flyToBounds一起合用,因为这两类地图操作方法都有各自的缩放值,造成动画不流畅、不能定位到目的点。

  • 地图飞到边界的合适的位置
  1. map.flyToBounds(polygon.getBounds()); //getBounds(获取边界):返回地图视图的经纬度边界。
  2. //飞到这个多变形区域上面,自动判断区域块的大小,合适缩放图层,将地图视图尽可能大地设定在给定的地理边界内。
  3. let polygon = L.polygon(
  4. [[37, -109.05],
  5. [41, -109.03],
  6. [41, -102.05],
  7. [37, -102.04]],
  8. [40.774, -74.125], {
  9. color: 'green',
  10. fillColor: '#f03',
  11. fillOpacity: 0.5
  12. }).addTo(map); //地图上绘制一个多形
  • 地图定位到边界的合适的位置
  1. map.fitBounds(polygon.getBounds()); //getBounds(获取边界):返回地图视图的经纬度边界。
  2. //平移到一个区域上面,自动判断区域块的大小,合适缩放图层
  3. let polygon = L.polygon(
  4. [[37, -109.05],
  5. [41, -109.03],
  6. [41, -102.05],
  7. [37, -102.04]],
  8. [40.774, -74.125], {
  9. color: 'green',
  10. fillColor: '#f03',
  11. fillOpacity: 0.5
  12. }).addTo(map); //地图上绘制一个多边形

PART 3: 图层管理(加载、移除、调整顺序): Demo 3

  • 库引用
  1. <link rel="stylesheet" type="text/css" href="./lib/Flat-UI-master/dist/css/vendor/bootstrap/css/bootstrap.min.css"
  2. />
  3. <link rel="stylesheet" href="./lib/Flat-UI-master/dist/css/flat-ui.min.css">
  4. <link rel="stylesheet" href="./lib/leaflet/leaflet.css">
  1. <script src="./lib/Flat-UI-master/dist/js/vendor/jquery.min.js"></script>
  2. <script src="./lib/Flat-UI-master/dist/js/flat-ui.js"></script>
  3. <script src="./lib/leaflet/leaflet.js"></script>
  4. <script src="./lib/esri-leaflet-v2.1.2/dist/esri-leaflet.js"></script> <!-- esri-leafleat插件 -->
  5. <script src="./js/urlTemplate.js"></script>
  • 使用esri-leaflet插件加载ArcGIS底图服务
  1. let oMap = null;
  2. let oLayer = [];
  3. oMap = L.map('mapDiv', {
  4. crs: L.CRS.EPSG4326,
  5. zoomControl: false,
  6. minZoom: 7,
  7. attributionControl: false
  8. }).setView([29.59, 106.59], 12); //定位在重庆
  9. oLayer.push(L.esri.tiledMapLayer({
  10. url: urlTemplate.SYS_CQMap_IMG_MAPSERVER_PATH,
  11. maxZoom: 17,
  12. minZoom: 0,
  13. useCors: false, //是否浏览器在跨域的情况下使用GET请求。
  14. }).addTo(oMap)); //加载第一个底图
  15. oLayer.push(L.esri.tiledMapLayer({
  16. url: urlTemplate.SYS_CQMap_IMG_LABEL_MAPSERVER_PATH,
  17. maxZoom: 17,
  18. minZoom: 0,
  19. useCors: false,
  20. }).addTo(oMap)); //加载第二个底图
  • 切换底图(移除及加载)
  1. const setLayer = (layerUrls, maxZoom) => {
  2. for (let i = 0; i < oLayer.length; i++) {
  3. oMap.removeLayer(oLayer[i]) //将图层在地图上移除
  4. }
  5. oLayer = [] //制空数组
  6. layerUrls.map((item) => {
  7. oLayer.push(L.esri.tiledMapLayer({
  8. url: item,
  9. useCors: false,
  10. maxZoom: maxZoom, // 设置最大放大图层值
  11. }).addTo(oMap));
  12. })
  13. }

不同的底图可能图层数不一样,就可能造成浏览器去请求不存在的图层,以及给用户展示出空白区域的不好体验,所以切换图层时候应注意设置最大及最小缩放值。

PART 4: 要素标绘(点、线、面,符号化/静态动态) Demo 4

  • 库引用 如上 Demo 1
  • 画一个圆
  1. // 画一个circle
  2. const circle = L.circle([36.52, 120.31], {
  3. color: 'green', //描边色
  4. fillColor: '#f03', //填充色
  5. fillOpacity: 0.5, //透明度
  6. radius: 10000 //半径,单位米
  7. }).addTo(map);
  8. // 绑定一个提示标签
  9. circle.bindTooltip('我是个圆');
  • Maker及自定义Maker
  1. // 做一个maker
  2. const marker = L.marker([36.52, 120.31]).addTo(map);
  3. // 绑定一个提示标签
  4. marker.bindTooltip('这是个Marker', { direction: 'left' }).openTooltip();
  5. //自定义一个maker
  6. const greenIcon = L.icon({
  7. iconUrl: './icon/logo.png',
  8. iconSize: [300, 79], // size of the icon
  9. popupAnchor: [0, -10] // point from which the popup should open relative to the iconAnchor
  10. });
  11. const oMarker = L.marker([36.52, 124.31], { icon: greenIcon }).addTo(map);
  12. // 绑定一个提示标签
  13. oMarker.bindTooltip('这是个自定义Marker', { direction: 'left', offset: [-150, 0] });
  • 画一根线
  1. //画一根线
  2. const polyline = L.polyline([[45.51, -122.68], [37.77, -122.43], [34.04, -118.2]], { color: 'red' }).addTo(map);
  3. // 飞到这个线的位置
  4. // map.fitBounds(polyline.getBounds());
  • 画一个多边形
  1. // 画一个polygon
  2. const polygon = L.polygon([
  3. [[37, -109.05], [41, -109.03], [41, -102.05], [37, -102.04]], // outer ring
  4. [[37.29, -108.58], [40.71, -108.58], [40.71, -102.50], [37.29, -102.50]] // hole
  5. ], {
  6. color: 'green',
  7. fillColor: '#f03',
  8. fillOpacity: 0.5
  9. }).addTo(map);
  10. // 绑定一个提示标签
  11. polygon.bindTooltip('this is 个多边形');
  12. // 飞到这个多边形的位置
  13. // map.fitBounds(polygon.getBounds());

### PART 5: 信息窗口(入口、Popup、定制) Demo 5

  • 库引用 如上 Demo 1

    • 画一个circle并绑定一个Popup
    1. // 画一个circle
    2. const circle = L.circle([36.92, 121.31], {
    3. color: 'green', //描边色
    4. fillColor: '#f03', //填充色
    5. fillOpacity: 0.5, //透明度
    6. radius: 10000 //半径,单位米
    7. }).addTo(map);
    8. // 绑定一个弹窗
    9. circle.bindPopup('我是个圆');
    • 定位一个marker,绑定一个自定义Popup
  1. // 定位一个maker
  2. const marker = L.marker([36.52, 120.31]).addTo(map);
  3. //maker上自定义一个popup
  4. const html = '<p>Hello world!<br />This is a nice popup.</p>';
  5. const popup = marker.bindPopup(html, { maxHeight: 250, maxWidth: 490, className: 'content', offset: [0, 0] }).on('popupopen', function (params) {
  6. console.log(params)
  7. });
  • 实现动态改变Popup的内容
  1. const mypop = L.popup();
  2. map.on('click', function (e) {
  3. mypop.setLatLng(e.latlng)
  4. .setContent('你临幸了这个点:<br>' + e.latlng.toString())
  5. .openOn(map);
  6. });

### PART 6: geojson 数据绘制边界(坐标转换、渲染) Demo 6

  • 库引用 如上 Demo 1
  • 获得geojson并处理数据
  1. // 请求geojson并处理数据
  2. const population = () => {
  3. $.get("./js/geojson.json", function (response) {
  4. const poplData = response.data
  5. const PolygonsCenter = response.geopoint
  6. drawPolygons(poplData, PolygonsCenter)
  7. });
  8. }

模拟后台返回的数据geojson

  • 绘制边界并添加图例
  1. let oPolygon_VilPop = [];
  2. const legend = L.control({
  3. position: 'bottomright'
  4. });
  5. const drawPolygons = (poplData, PolygonsCenter) => {
  6. for (const i in poplData) {
  7. poplData[i].geoJson = JSON.parse(poplData[i].geoJson)
  8. oPolygon_VilPop[i] = L.geoJSON(poplData[i].geoJson, {
  9. style: function () {
  10. return {
  11. color: 'white',
  12. fillColor: getBgColor(poplData[i].population), //获取边界的填充色
  13. fillOpacity: 0.6,
  14. weight: 3,
  15. dashArray: '10'
  16. };
  17. }
  18. }).bindTooltip(poplData[i].villageName + '<br><br>人口' + poplData[i].population + '人', {
  19. direction: 'top'
  20. }).on({
  21. mouseover: highlight, //鼠标移动上去高亮
  22. mouseout: resetHighlight, //鼠标移出恢复原样式
  23. click: zoomTo //点击最大化
  24. }).addTo(oMap);
  25. }
  26. // 添加图例
  27. legend.onAdd = legendHtml;
  28. legend.addTo(oMap);
  29. // 定位到该界限的中心位置
  30. oMap.flyToBounds(PolygonsCenter);
  31. }
  32. // 添加图例
  33. legend.onAdd = legendHtml;
  34. legend.addTo(oMap);
  35. // 定位到该界限的中心位置
  36. oMap.flyToBounds(PolygonsCenter);
  37. }
  • 返回边界的填充色及图列的样式
  1. const getBgColor = (d) => {
  2. return d > 400 ? '#800026' : d > 300 ? '#BD0026' : d > 200 ? '#FC4E2A' : d > 100 ? '#FD8D3C' : d > 50 ? '#FED976' : '#FFEDA0';
  3. }
  4. const legendHtml = (map) => {
  5. let div = L.DomUtil.create('div', 'legend locateVP_legend'),
  6. grades = [0, 50, 100, 200, 400],
  7. labels = [],
  8. from, to;
  9. for (let i = 0; i < grades.length; i++) {
  10. from = grades[i];
  11. to = grades[i + 1];
  12. labels.push(
  13. '<i style="background:' + getBgColor(from + 1) + '"></i> ' +
  14. from + (to ? ' &sim; ' + to + '人' : '以上'));
  15. }
  16. div.innerHTML = labels.join('<br>');
  17. return div;
  18. };
  • 鼠标移动上去的事件、鼠标移出的事件、发生点击的事件
  1. const highlight = (e) => {
  2. let layer = e.target;
  3. layer.setStyle({
  4. weight: 6,
  5. color: '#fff',
  6. fillOpacity: 0.9,
  7. dashArray: '0'
  8. })
  9. }
  10. const resetHighlight = (e) => {
  11. let layer = e.target;
  12. layer.setStyle({
  13. color: 'white',
  14. weight: 3,
  15. fillOpacity: 0.6,
  16. dashArray: '10'
  17. })
  18. }
  19. const zoomTo = (e) => {
  20. oMap.fitBounds(e.target.getBounds());
  21. }

写在后面

国内常用地图服务资源加载插件

Leaflet.ChineseTmsProviders Provider for Chinese Tms Service

  • Leaflet调用国内各种地图的功能十分复杂,幸好有leaflet.ChineseTmsProviders这个插件,这四种地图直接就可以加载进来,十分方便。
  • 使用方法很简单可点击上面链接去GitHub看使用说明,或拉这个demo下来来瞧一瞧代码。

优化marker相关的插件

模块化开发的加载包注意的问题

  • 引 leaflet 包的时候不要忘记引用包里的css import 'leaflet/dist/leaflet.css';

关于Leaflet 1.2.0 和esri-leaflet 2.1.1 一起使用L.esri.TiledMapLayer加载ArcGIS 服务切片底图时,控制台打印报错 Uncaught ReferenceError: proj4 is not defined

  • 查看了下源码 if (!proj4) { warn('L.esri.TiledMapLayer is using a non-mercator spatial reference. Support may be available through Proj4Leaflet http://esri.github.io/esri-leaflet/examples/non-mercator-projection.html');} 问题就出在这里,esri-leaflet里的一个插件proj4leaflet依赖proj4,所以需要手动引入proj4这个包。
  • 这个GitHub上面的提问及回答 Github esri-leaflet Issues
  • 如果你是模块化开发,需要再npm i proj4 然后再引入进来好了 `import * as proj4 from 'proj4';

window['proj4'] = proj4;`

  • 如果你是常规开发,直接添加一个script标签引用CDN资源上托管的Proj4js就是了 <script src="https://cdnjs.cloudflare.com/ajax/libs/proj4js/2.4.4/proj4-src.js"></script>

参考

持续更新中 原文地址: https://juejin.im/post/5a6586...

入门Leaflet之小Demo的更多相关文章

  1. vue入门 0 小demo (挂载点、模板、实例)

    vue入门 0 小demo  (挂载点.模板) 用直接的引用vue.js 首先 讲几个基本的概念 1.挂载点即el:vue 实例化时 元素挂靠的地方. 2.模板 即template:vue 实例化时挂 ...

  2. 11.Python使用Scrapy爬虫小Demo(新手入门)

    1.前提:已安装好scrapy,且已新建好项目,编写小Demo去获取美剧天堂的电影标题名 2.在项目中创建一个python文件 3.代码如下所示: import scrapy class movies ...

  3. 一个基于ES5的vue小demo

    由于现在很多vue项目都是基于ES6开发的,而我学vue的时候大多是看vue官网的API,是基于ES5的,所以对于刚接触项目的我来说要转变为项目的模块化写法确实有些挑战.因此,我打算先做一个基于ES5 ...

  4. 【Java】Jsoup爬虫,一个简单获取京东商品信息的小Demo

    简单记录 - Jsoup爬虫入门实战 数据问题?数据库获取,消息队列中获取中,都可以成为数据源,爬虫! 爬取数据:(获取请求返回的页面信息,筛选出我们想要的数据就可以了!) 我们经常需要分析HTML网 ...

  5. 新手 gulp+ seajs 小demo

    首先,不说废话,它的介绍和作者就不在多说了,网上一百度一大堆: 我在这里只是来写写我这2天抽空对seajs的了解并爬过的坑,和实现的一个小demo(纯属为了实现,高手请绕道); 一.环境工具及安装 1 ...

  6. Nancy之基于Nancy.Hosting.Self的小Demo

    继昨天的Nancy之基于Nancy.Hosting.Aspnet的小Demo后, 今天来做个基于Nancy.Hosting.Self的小Demo. 关于Self Hosting Nancy,官方文档的 ...

  7. Nancy之基于Nancy.Owin的小Demo

    前面做了基于Nancy.Hosting.Aspnet和Nancy.Hosting.Self的小Demo 今天我们来做个基于Nancy.Owin的小Demo 开始之前我们来说说什么是Owin和Katan ...

  8. Nancy之基于Self Hosting的补充小Demo

    前面把Hosting Nancy with ASP.NET.Self Hosting Nancy和Hosting Nancy with OWIN 以demo的形式简单描述了一下. 这篇是为Self H ...

  9. [Unity3D]做个小Demo学习Input.touches

    [Unity3D]做个小Demo学习Input.touches 学不如做,下面用一个简单的Demo展示的Input.touches各项字段,有图有真相. 本项目已发布到Github,地址在(https ...

随机推荐

  1. Vue组件中的data属性

    Vue中的data属性专门用来以对象方式存放数据,它有两种用法. var vm=new Vue({ data:{a:1,b:2,}, }) var vm=new Vue({ data(){return ...

  2. Xshell 6 安装注册说明

    1.下载 官方下载到的版本在进行安装的时候,是有序列号输入框的如下图,但是好像我们从那个所谓的官网上下载的却没有,只能下载到一点击注册就跳转到一个网站的那种版本. 像上面这种版本如何下载呢? 我们去官 ...

  3. Tensorflow 从文件中载入训练数据

    本节包含: 用纯文本文件准备训练数据 加载文件中的训练数据 一.用纯文本文件准备训练数据 1.数据的数字化 比如,“是” —— “1”,“否” —— “0” “优”,“中”,“差” —— 1 2 3  ...

  4. 【ABAP系列】【第六篇】SAP ABAP7.50 之隐式增强

    公众号:SAP Technical 本文作者:matinal 原文出处:http://www.cnblogs.com/SAPmatinal/ 原文链接:[ABAP系列][第六篇]SAP ABAP7.5 ...

  5. @Validated和@Valid校验参数、级联属性、List

    @Validated和@Valid的区别 在Controller中校验方法参数时,使用@Valid和@Validated并无特殊差异(若不需要分组校验的话): @Valid:标准JSR-303规范的标 ...

  6. POJ3734 Block母函数入门

    一段长度为n的序列,你有红黄蓝绿四种颜色的砖块,一块砖长度为1,问你铺砖的方案数,其中红黄颜色之和必须为偶数. #include <queue> #include <stack> ...

  7. Andrew算法求二维凸包-学习笔记

    凸包的概念 首先,引入凸包的概念: (有点窄的时候...图片右边可能会被吞,拉开图片看就可以了) 大概长这个样子: 那么,给定一些散点,如何快速地求出凸包呢(用在凸包上的点来表示凸包) Andrew算 ...

  8. yum tenxun ntpdate 时间同步

    centos7 wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.cloud.tencent.com/repo/centos7_base ...

  9. FPGA —— Quartus II 15.0 使用 ModelSim SE-64 2019.2 软件进行仿真

    Quartus II 15.0 使用 ModelSim SE-64 2019.2 软件进行仿真 ModelSim 仿真 Verilog HDL 时需要编写一个 TestBench 仿真文件,通过仿真文 ...

  10. PTA(Basic Level)1026.程序运行时间

    要获得一个 C 语言程序的运行时间,常用的方法是调用头文件 time.h,其中提供了 clock() 函数,可以捕捉从程序开始运行到 clock() 被调用时所耗费的时间.这个时间单位是 clock ...