├── .editorconfig ├── .electron-builder.config.js ├── .electron-vendors.cache.json ├── .env.development ├── .eslintrc.json ├── .gitattributes ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── config.yml │ └── feature_request.md ├── actions │ └── release-notes │ │ ├── action.yml │ │ └── main.js ├── renovate.json └── workflows │ ├── ci.del-yml │ ├── release.yml │ └── update-electron-vendors.yml ├── .gitignore ├── .idea ├── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml ├── deployment.xml ├── inspectionProfiles │ └── Project_Default.xml ├── jsLibraryMappings.xml ├── jsLinters │ └── eslint.xml ├── modules.xml ├── vcs.xml ├── vite-electron-builder.iml └── webResources.xml ├── LICENSE ├── README.md ├── buildResources ├── .gitkeep ├── icon.icns └── icon.png ├── package-lock.json ├── package.json ├── packages ├── main │ ├── src │ │ ├── index.js │ │ ├── ipHandle.js │ │ └── ipcMain.js │ └── vite.config.js ├── preload │ ├── src │ │ └── index.js │ └── vite.config.js └── renderer │ ├── .eslintrc.json │ ├── index.html │ ├── src │ ├── App.vue │ ├── assets │ │ ├── grid.svg │ │ └── logo.svg │ ├── cesium-helper │ │ └── BaiduImageryProvider │ │ │ ├── BaiduImageryProvider.js │ │ │ ├── BaiduMercatorProjection.js │ │ │ ├── BaiduMercatorTilingScheme.js │ │ │ ├── CoordTransform.js │ │ │ └── ImageryType.js │ ├── components │ │ ├── About.vue │ │ ├── AreaChoose.vue │ │ ├── GridIcon.vue │ │ ├── Help.vue │ │ ├── Home.vue │ │ ├── LayerControl.vue │ │ ├── MapKey.vue │ │ ├── ProgressControl.vue │ │ ├── Save.vue │ │ └── Tips.vue │ ├── geojson │ │ ├── city │ │ │ ├── 130100-石家庄市.json │ │ │ ├── 130200-唐山市.json │ │ │ ├── 130300-秦皇岛市.json │ │ │ ├── 130400-邯郸市.json │ │ │ ├── 130500-邢台市.json │ │ │ ├── 130600-保定市.json │ │ │ ├── 130700-张家口市.json │ │ │ ├── 130800-承德市.json │ │ │ ├── 130900-沧州市.json │ │ │ ├── 131000-廊坊市.json │ │ │ ├── 131100-衡水市.json │ │ │ ├── 140100-太原市.json │ │ │ ├── 140200-大同市.json │ │ │ ├── 140300-阳泉市.json │ │ │ ├── 140400-长治市.json │ │ │ ├── 140500-晋城市.json │ │ │ ├── 140600-朔州市.json │ │ │ ├── 140700-晋中市.json │ │ │ ├── 140800-运城市.json │ │ │ ├── 140900-忻州市.json │ │ │ ├── 141000-临汾市.json │ │ │ ├── 141100-吕梁市.json │ │ │ ├── 150100-呼和浩特市.json │ │ │ ├── 150200-包头市.json │ │ │ ├── 150300-乌海市.json │ │ │ ├── 150400-赤峰市.json │ │ │ ├── 150500-通辽市.json │ │ │ ├── 150600-鄂尔多斯市.json │ │ │ ├── 150700-呼伦贝尔市.json │ │ │ ├── 150800-巴彦淖尔市.json │ │ │ ├── 150900-乌兰察布市.json │ │ │ ├── 152200-兴安盟.json │ │ │ ├── 152500-锡林郭勒盟.json │ │ │ ├── 152900-阿拉善盟.json │ │ │ ├── 210100-沈阳市.json │ │ │ ├── 210200-大连市.json │ │ │ ├── 210300-鞍山市.json │ │ │ ├── 210400-抚顺市.json │ │ │ ├── 210500-本溪市.json │ │ │ ├── 210600-丹东市.json │ │ │ ├── 210700-锦州市.json │ │ │ ├── 210800-营口市.json │ │ │ ├── 210900-阜新市.json │ │ │ ├── 211000-辽阳市.json │ │ │ ├── 211100-盘锦市.json │ │ │ ├── 211200-铁岭市.json │ │ │ ├── 211300-朝阳市.json │ │ │ ├── 211400-葫芦岛市.json │ │ │ ├── 220100-长春市.json │ │ │ ├── 220200-吉林市.json │ │ │ ├── 220300-四平市.json │ │ │ ├── 220400-辽源市.json │ │ │ ├── 220500-通化市.json │ │ │ ├── 220600-白山市.json │ │ │ ├── 220700-松原市.json │ │ │ ├── 220800-白城市.json │ │ │ ├── 222400-延边朝鲜族自治州.json │ │ │ ├── 230100-哈尔滨市.json │ │ │ ├── 230200-齐齐哈尔市.json │ │ │ ├── 230300-鸡西市.json │ │ │ ├── 230400-鹤岗市.json │ │ │ ├── 230500-双鸭山市.json │ │ │ ├── 230600-大庆市.json │ │ │ ├── 230700-伊春市.json │ │ │ ├── 230800-佳木斯市.json │ │ │ ├── 230900-七台河市.json │ │ │ ├── 231000-牡丹江市.json │ │ │ ├── 231100-黑河市.json │ │ │ ├── 231200-绥化市.json │ │ │ ├── 232700-大兴安岭地区.json │ │ │ ├── 320100-南京市.json │ │ │ ├── 320200-无锡市.json │ │ │ ├── 320300-徐州市.json │ │ │ ├── 320400-常州市.json │ │ │ ├── 320500-苏州市.json │ │ │ ├── 320600-南通市.json │ │ │ ├── 320700-连云港市.json │ │ │ ├── 320800-淮安市.json │ │ │ ├── 320900-盐城市.json │ │ │ ├── 321000-扬州市.json │ │ │ ├── 321100-镇江市.json │ │ │ ├── 321200-泰州市.json │ │ │ ├── 321300-宿迁市.json │ │ │ ├── 330100-杭州市.json │ │ │ ├── 330200-宁波市.json │ │ │ ├── 330300-温州市.json │ │ │ ├── 330400-嘉兴市.json │ │ │ ├── 330500-湖州市.json │ │ │ ├── 330600-绍兴市.json │ │ │ ├── 330700-金华市.json │ │ │ ├── 330800-衢州市.json │ │ │ ├── 330900-舟山市.json │ │ │ ├── 331000-台州市.json │ │ │ ├── 331100-丽水市.json │ │ │ ├── 340100-合肥市.json │ │ │ ├── 340200-芜湖市.json │ │ │ ├── 340300-蚌埠市.json │ │ │ ├── 340400-淮南市.json │ │ │ ├── 340500-马鞍山市.json │ │ │ ├── 340600-淮北市.json │ │ │ ├── 340700-铜陵市.json │ │ │ ├── 340800-安庆市.json │ │ │ ├── 341000-黄山市.json │ │ │ ├── 341100-滁州市.json │ │ │ ├── 341200-阜阳市.json │ │ │ ├── 341300-宿州市.json │ │ │ ├── 341500-六安市.json │ │ │ ├── 341600-亳州市.json │ │ │ ├── 341700-池州市.json │ │ │ ├── 341800-宣城市.json │ │ │ ├── 350100-福州市.json │ │ │ ├── 350200-厦门市.json │ │ │ ├── 350300-莆田市.json │ │ │ ├── 350400-三明市.json │ │ │ ├── 350500-泉州市.json │ │ │ ├── 350600-漳州市.json │ │ │ ├── 350700-南平市.json │ │ │ ├── 350800-龙岩市.json │ │ │ ├── 350900-宁德市.json │ │ │ ├── 360100-南昌市.json │ │ │ ├── 360200-景德镇市.json │ │ │ ├── 360300-萍乡市.json │ │ │ ├── 360400-九江市.json │ │ │ ├── 360500-新余市.json │ │ │ ├── 360600-鹰潭市.json │ │ │ ├── 360700-赣州市.json │ │ │ ├── 360800-吉安市.json │ │ │ ├── 360900-宜春市.json │ │ │ ├── 361000-抚州市.json │ │ │ ├── 361100-上饶市.json │ │ │ ├── 370100-济南市.json │ │ │ ├── 370200-青岛市.json │ │ │ ├── 370300-淄博市.json │ │ │ ├── 370400-枣庄市.json │ │ │ ├── 370500-东营市.json │ │ │ ├── 370600-烟台市.json │ │ │ ├── 370700-潍坊市.json │ │ │ ├── 370800-济宁市.json │ │ │ ├── 370900-泰安市.json │ │ │ ├── 371000-威海市.json │ │ │ ├── 371100-日照市.json │ │ │ ├── 371300-临沂市.json │ │ │ ├── 371400-德州市.json │ │ │ ├── 371500-聊城市.json │ │ │ ├── 371600-滨州市.json │ │ │ ├── 371700-菏泽市.json │ │ │ ├── 410100-郑州市.json │ │ │ ├── 410200-开封市.json │ │ │ ├── 410300-洛阳市.json │ │ │ ├── 410400-平顶山市.json │ │ │ ├── 410500-安阳市.json │ │ │ ├── 410600-鹤壁市.json │ │ │ ├── 410700-新乡市.json │ │ │ ├── 410800-焦作市.json │ │ │ ├── 410900-濮阳市.json │ │ │ ├── 411000-许昌市.json │ │ │ ├── 411100-漯河市.json │ │ │ ├── 411200-三门峡市.json │ │ │ ├── 411300-南阳市.json │ │ │ ├── 411400-商丘市.json │ │ │ ├── 411500-信阳市.json │ │ │ ├── 411600-周口市.json │ │ │ ├── 411700-驻马店市.json │ │ │ ├── 420100-武汉市.json │ │ │ ├── 420200-黄石市.json │ │ │ ├── 420300-十堰市.json │ │ │ ├── 420500-宜昌市.json │ │ │ ├── 420600-襄阳市.json │ │ │ ├── 420700-鄂州市.json │ │ │ ├── 420800-荆门市.json │ │ │ ├── 420900-孝感市.json │ │ │ ├── 421000-荆州市.json │ │ │ ├── 421100-黄冈市.json │ │ │ ├── 421200-咸宁市.json │ │ │ ├── 421300-随州市.json │ │ │ ├── 422800-恩施土家族苗族自治州.json │ │ │ ├── 430100-长沙市.json │ │ │ ├── 430200-株洲市.json │ │ │ ├── 430300-湘潭市.json │ │ │ ├── 430400-衡阳市.json │ │ │ ├── 430500-邵阳市.json │ │ │ ├── 430600-岳阳市.json │ │ │ ├── 430700-常德市.json │ │ │ ├── 430800-张家界市.json │ │ │ ├── 430900-益阳市.json │ │ │ ├── 431000-郴州市.json │ │ │ ├── 431100-永州市.json │ │ │ ├── 431200-怀化市.json │ │ │ ├── 431300-娄底市.json │ │ │ ├── 433100-湘西土家族苗族自治州.json │ │ │ ├── 440100-广州市.json │ │ │ ├── 440200-韶关市.json │ │ │ ├── 440300-深圳市.json │ │ │ ├── 440400-珠海市.json │ │ │ ├── 440500-汕头市.json │ │ │ ├── 440600-佛山市.json │ │ │ ├── 440700-江门市.json │ │ │ ├── 440800-湛江市.json │ │ │ ├── 440900-茂名市.json │ │ │ ├── 441200-肇庆市.json │ │ │ ├── 441300-惠州市.json │ │ │ ├── 441400-梅州市.json │ │ │ ├── 441500-汕尾市.json │ │ │ ├── 441600-河源市.json │ │ │ ├── 441700-阳江市.json │ │ │ ├── 441800-清远市.json │ │ │ ├── 441900-东莞市.json │ │ │ ├── 442000-中山市.json │ │ │ ├── 445100-潮州市.json │ │ │ ├── 445200-揭阳市.json │ │ │ ├── 445300-云浮市.json │ │ │ ├── 450100-南宁市.json │ │ │ ├── 450200-柳州市.json │ │ │ ├── 450300-桂林市.json │ │ │ ├── 450400-梧州市.json │ │ │ ├── 450500-北海市.json │ │ │ ├── 450600-防城港市.json │ │ │ ├── 450700-钦州市.json │ │ │ ├── 450800-贵港市.json │ │ │ ├── 450900-玉林市.json │ │ │ ├── 451000-百色市.json │ │ │ ├── 451100-贺州市.json │ │ │ ├── 451200-河池市.json │ │ │ ├── 451300-来宾市.json │ │ │ ├── 451400-崇左市.json │ │ │ ├── 460100-海口市.json │ │ │ ├── 460200-三亚市.json │ │ │ ├── 460300-三沙市.json │ │ │ ├── 460400-儋州市.json │ │ │ ├── 510100-成都市.json │ │ │ ├── 510300-自贡市.json │ │ │ ├── 510400-攀枝花市.json │ │ │ ├── 510500-泸州市.json │ │ │ ├── 510600-德阳市.json │ │ │ ├── 510700-绵阳市.json │ │ │ ├── 510800-广元市.json │ │ │ ├── 510900-遂宁市.json │ │ │ ├── 511000-内江市.json │ │ │ ├── 511100-乐山市.json │ │ │ ├── 511300-南充市.json │ │ │ ├── 511400-眉山市.json │ │ │ ├── 511500-宜宾市.json │ │ │ ├── 511600-广安市.json │ │ │ ├── 511700-达州市.json │ │ │ ├── 511800-雅安市.json │ │ │ ├── 511900-巴中市.json │ │ │ ├── 512000-资阳市.json │ │ │ ├── 513200-阿坝藏族羌族自治州.json │ │ │ ├── 513300-甘孜藏族自治州.json │ │ │ ├── 513400-凉山彝族自治州.json │ │ │ ├── 520100-贵阳市.json │ │ │ ├── 520200-六盘水市.json │ │ │ ├── 520300-遵义市.json │ │ │ ├── 520400-安顺市.json │ │ │ ├── 520500-毕节市.json │ │ │ ├── 520600-铜仁市.json │ │ │ ├── 522300-黔西南布依族苗族自治州.json │ │ │ ├── 522600-黔东南苗族侗族自治州.json │ │ │ ├── 522700-黔南布依族苗族自治州.json │ │ │ ├── 530100-昆明市.json │ │ │ ├── 530300-曲靖市.json │ │ │ ├── 530400-玉溪市.json │ │ │ ├── 530500-保山市.json │ │ │ ├── 530600-昭通市.json │ │ │ ├── 530700-丽江市.json │ │ │ ├── 530800-普洱市.json │ │ │ ├── 530900-临沧市.json │ │ │ ├── 532300-楚雄彝族自治州.json │ │ │ ├── 532500-红河哈尼族彝族自治州.json │ │ │ ├── 532600-文山壮族苗族自治州.json │ │ │ ├── 532800-西双版纳傣族自治州.json │ │ │ ├── 532900-大理白族自治州.json │ │ │ ├── 533100-德宏傣族景颇族自治州.json │ │ │ ├── 533300-怒江傈僳族自治州.json │ │ │ ├── 533400-迪庆藏族自治州.json │ │ │ ├── 540100-拉萨市.json │ │ │ ├── 540200-日喀则市.json │ │ │ ├── 540300-昌都市.json │ │ │ ├── 540400-林芝市.json │ │ │ ├── 540500-山南市.json │ │ │ ├── 540600-那曲市.json │ │ │ ├── 542500-阿里地区.json │ │ │ ├── 610100-西安市.json │ │ │ ├── 610200-铜川市.json │ │ │ ├── 610300-宝鸡市.json │ │ │ ├── 610400-咸阳市.json │ │ │ ├── 610500-渭南市.json │ │ │ ├── 610600-延安市.json │ │ │ ├── 610700-汉中市.json │ │ │ ├── 610800-榆林市.json │ │ │ ├── 610900-安康市.json │ │ │ ├── 611000-商洛市.json │ │ │ ├── 620100-兰州市.json │ │ │ ├── 620200-嘉峪关市.json │ │ │ ├── 620300-金昌市.json │ │ │ ├── 620400-白银市.json │ │ │ ├── 620500-天水市.json │ │ │ ├── 620600-武威市.json │ │ │ ├── 620700-张掖市.json │ │ │ ├── 620800-平凉市.json │ │ │ ├── 620900-酒泉市.json │ │ │ ├── 621000-庆阳市.json │ │ │ ├── 621100-定西市.json │ │ │ ├── 621200-陇南市.json │ │ │ ├── 622900-临夏回族自治州.json │ │ │ ├── 623000-甘南藏族自治州.json │ │ │ ├── 630100-西宁市.json │ │ │ ├── 630200-海东市.json │ │ │ ├── 632200-海北藏族自治州.json │ │ │ ├── 632300-黄南藏族自治州.json │ │ │ ├── 632500-海南藏族自治州.json │ │ │ ├── 632600-果洛藏族自治州.json │ │ │ ├── 632700-玉树藏族自治州.json │ │ │ ├── 632800-海西蒙古族藏族自治州.json │ │ │ ├── 640100-银川市.json │ │ │ ├── 640200-石嘴山市.json │ │ │ ├── 640300-吴忠市.json │ │ │ ├── 640400-固原市.json │ │ │ ├── 640500-中卫市.json │ │ │ ├── 650100-乌鲁木齐市.json │ │ │ ├── 650200-克拉玛依市.json │ │ │ ├── 650400-吐鲁番市.json │ │ │ ├── 650500-哈密市.json │ │ │ ├── 652300-昌吉回族自治州.json │ │ │ ├── 652700-博尔塔拉蒙古自治州.json │ │ │ ├── 652800-巴音郭楞蒙古自治州.json │ │ │ ├── 652900-阿克苏地区.json │ │ │ ├── 653000-克孜勒苏柯尔克孜自治州.json │ │ │ ├── 653100-喀什地区.json │ │ │ ├── 653200-和田地区.json │ │ │ ├── 654000-伊犁哈萨克自治州.json │ │ │ ├── 654200-塔城地区.json │ │ │ └── 654300-阿勒泰地区.json │ │ ├── province │ │ │ ├── 110000-北京市.json │ │ │ ├── 120000-天津市.json │ │ │ ├── 130000-河北省.json │ │ │ ├── 140000-山西省.json │ │ │ ├── 150000-内蒙古自治区.json │ │ │ ├── 210000-辽宁省.json │ │ │ ├── 220000-吉林省.json │ │ │ ├── 230000-黑龙江省.json │ │ │ ├── 310000-上海市.json │ │ │ ├── 320000-江苏省.json │ │ │ ├── 330000-浙江省.json │ │ │ ├── 340000-安徽省.json │ │ │ ├── 350000-福建省.json │ │ │ ├── 360000-江西省.json │ │ │ ├── 370000-山东省.json │ │ │ ├── 410000-河南省.json │ │ │ ├── 420000-湖北省.json │ │ │ ├── 430000-湖南省.json │ │ │ ├── 440000-广东省.json │ │ │ ├── 450000-广西壮族自治区.json │ │ │ ├── 460000-海南省.json │ │ │ ├── 500000-重庆市.json │ │ │ ├── 510000-四川省.json │ │ │ ├── 520000-贵州省.json │ │ │ ├── 530000-云南省.json │ │ │ ├── 540000-西藏自治区.json │ │ │ ├── 610000-陕西省.json │ │ │ ├── 620000-甘肃省.json │ │ │ ├── 630000-青海省.json │ │ │ ├── 640000-宁夏回族自治区.json │ │ │ ├── 650000-新疆维吾尔自治区.json │ │ │ ├── 710000-台湾省.json │ │ │ ├── 810000-香港特别行政区.json │ │ │ └── 820000-澳门特别行政区.json │ │ └── 中国.json │ ├── index.js │ ├── naive-ui-load.js │ ├── router.js │ ├── style │ │ └── index.scss │ ├── use │ │ └── electron.js │ └── utils │ │ ├── TileLayerCollection │ │ ├── TileLayerCollection.js │ │ ├── param.js │ │ ├── tilelayers │ │ │ ├── AmapTileLayer.js │ │ │ ├── BaiduTileLayer.js │ │ │ ├── BaseTileLayer.js │ │ │ ├── CartoDbTileLayer.js │ │ │ ├── GeoqTileLayer.js │ │ │ ├── GoogleTileLayer.js │ │ │ ├── MapboxTileLayer.js │ │ │ ├── OsmTileLayer.js │ │ │ ├── TdtTileLayer.js │ │ │ └── TencentTileLayer.js │ │ └── utils.js │ │ ├── areaList.js │ │ ├── baseMap.js │ │ ├── clipImage.js │ │ ├── download.js │ │ ├── downloadCascadeTiles.js │ │ ├── fileSave.js │ │ ├── fileSaveBaidu.js │ │ ├── fileSaveTms.js │ │ ├── layerList.js │ │ ├── mapKey.js │ │ ├── progress.js │ │ ├── random.js │ │ ├── tileBaidu.js │ │ └── tileTms.js │ └── vite.config.js ├── scripts ├── build.js ├── update-electron-vendors.js └── watch.js ├── tests └── app.spec.js └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | # https://github.com/jokeyrhyme/standard-editorconfig 4 | 5 | # top-most EditorConfig file 6 | root = true 7 | 8 | # defaults 9 | [*] 10 | charset = utf-8 11 | end_of_line = lf 12 | insert_final_newline = true 13 | trim_trailing_whitespace = true 14 | indent_size = 2 15 | indent_style = space 16 | 17 | [*.md] 18 | trim_trailing_whitespace = false 19 | -------------------------------------------------------------------------------- /.electron-builder.config.js: -------------------------------------------------------------------------------- 1 | if (process.env.VITE_APP_VERSION === undefined) { 2 | const now = new Date; 3 | process.env.VITE_APP_VERSION = `${now.getUTCFullYear() - 2000}.${now.getUTCMonth() + 1}.${now.getUTCDate()}-${now.getUTCHours() * 60 + now.getUTCMinutes()}`; 4 | } 5 | 6 | /** 7 | * @type {import('electron-builder').Configuration} 8 | * @see https://www.electron.build/configuration/configuration 9 | */ 10 | const config = { 11 | directories: { 12 | output: 'dist', 13 | buildResources: 'buildResources', 14 | }, 15 | files: [ 16 | 'packages/**/dist/**', 17 | ], 18 | extraMetadata: { 19 | version: process.env.VITE_APP_VERSION, 20 | }, 21 | nsis: { 22 | oneClick: false, 23 | allowToChangeInstallationDirectory: true 24 | } 25 | }; 26 | 27 | module.exports = config; 28 | -------------------------------------------------------------------------------- /.electron-vendors.cache.json: -------------------------------------------------------------------------------- 1 | { 2 | "chrome": "96", 3 | "node": "16" 4 | } 5 | -------------------------------------------------------------------------------- /.env.development: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hxy1992/MapDownload/99f54e18d151379e01d6aae3b368a668f898663a/.env.development -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "env": { 4 | "es2021": true, 5 | "node": true, 6 | "browser": false 7 | }, 8 | "extends": [ 9 | "eslint:recommended" 10 | ], 11 | "parserOptions": { 12 | "sourceType": "module" 13 | }, 14 | "ignorePatterns": [ 15 | "node_modules/**", 16 | "**/dist/**" 17 | ], 18 | "rules": { 19 | 20 | /** 21 | * Having a semicolon helps the optimizer interpret your code correctly. 22 | * This avoids rare errors in optimized code. 23 | * @see https://twitter.com/alex_kozack/status/1364210394328408066 24 | */ 25 | "semi": [ 26 | "error", 27 | "always" 28 | ], 29 | /** 30 | * This will make the history of changes in the hit a little cleaner 31 | */ 32 | "comma-dangle": [ 33 | "warn", 34 | "always-multiline" 35 | ], 36 | /** 37 | * Just for beauty 38 | */ 39 | "quotes": [ 40 | "warn", "single" 41 | ] 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | .github/actions/**/*.js linguist-detectable=false 2 | scripts/*.js linguist-detectable=false 3 | *.config.js linguist-detectable=false 4 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | patreon: Kozack 4 | open_collective: vite-electron-builder 5 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: cawa-93 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Additional context** 27 | Add any other context about the problem here. 28 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Questions & Discussions 4 | url: https://github.com/cawa-93/vite-electron-builder/discussions/categories/q-a 5 | about: Use GitHub discussions for message-board style questions and discussions. 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: cawa-93 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/actions/release-notes/action.yml: -------------------------------------------------------------------------------- 1 | name: 'Release Notes' 2 | description: 'Return release notes based on Git Commits' 3 | inputs: 4 | from: 5 | description: 'Commit from which start log' 6 | required: true 7 | to: 8 | description: 'Commit to which end log' 9 | required: true 10 | include-commit-body: 11 | description: 'Should the commit body be in notes' 12 | required: false 13 | default: 'false' 14 | include-abbreviated-commit: 15 | description: 'Should the commit sha be in notes' 16 | required: false 17 | default: 'true' 18 | outputs: 19 | release-note: # id of output 20 | description: 'Release notes' 21 | runs: 22 | using: 'node12' 23 | main: 'main.js' 24 | -------------------------------------------------------------------------------- /.github/actions/release-notes/main.js: -------------------------------------------------------------------------------- 1 | // TODO: Refactor this action 2 | 3 | const {execSync} = require('child_process'); 4 | 5 | /** 6 | * Gets the value of an input. The value is also trimmed. 7 | * 8 | * @param name name of the input to get 9 | * @param options optional. See InputOptions. 10 | * @returns string 11 | */ 12 | function getInput(name, options) { 13 | const val = process.env[`INPUT_${name.replace(/ /g, '_').toUpperCase()}`] || ''; 14 | if (options && options.required && !val) { 15 | throw new Error(`Input required and not supplied: ${name}`); 16 | } 17 | 18 | return val.trim(); 19 | } 20 | 21 | const START_FROM = getInput('from'); 22 | const END_TO = getInput('to'); 23 | const INCLUDE_COMMIT_BODY = getInput('include-commit-body') === 'true'; 24 | const INCLUDE_ABBREVIATED_COMMIT = getInput('include-abbreviated-commit') === 'true'; 25 | 26 | /** 27 | * @typedef {Object} ICommit 28 | * @property {string | undefined} abbreviated_commit 29 | * @property {string | undefined} subject 30 | * @property {string | undefined} body 31 | */ 32 | 33 | /** 34 | * @typedef {ICommit & {type: string | undefined, scope: string | undefined}} ICommitExtended 35 | */ 36 | 37 | 38 | /** 39 | * Any unique string that is guaranteed not to be used in committee text. 40 | * Used to split data in the commit line 41 | * @type {string} 42 | */ 43 | const commitInnerSeparator = '~~~~'; 44 | 45 | 46 | /** 47 | * Any unique string that is guaranteed not to be used in committee text. 48 | * Used to split each commit line 49 | * @type {string} 50 | */ 51 | const commitOuterSeparator = '₴₴₴₴'; 52 | 53 | 54 | /** 55 | * Commit data to be obtained. 56 | * @type {Map} 57 | * 58 | * @see https://git-scm.com/docs/git-log#Documentation/git-log.txt-emnem 59 | */ 60 | const commitDataMap = new Map([ 61 | ['subject', '%s'], // Required 62 | ]); 63 | 64 | if (INCLUDE_COMMIT_BODY) { 65 | commitDataMap.set('body', '%b'); 66 | } 67 | 68 | if (INCLUDE_ABBREVIATED_COMMIT) { 69 | commitDataMap.set('abbreviated_commit', '%h'); 70 | } 71 | 72 | /** 73 | * The type used to group commits that do not comply with the convention 74 | * @type {string} 75 | */ 76 | const fallbackType = 'other'; 77 | 78 | 79 | /** 80 | * List of all desired commit groups and in what order to display them. 81 | * @type {string[]} 82 | */ 83 | const supportedTypes = [ 84 | 'feat', 85 | 'fix', 86 | 'perf', 87 | 'refactor', 88 | 'style', 89 | 'docs', 90 | 'test', 91 | 'build', 92 | 'ci', 93 | 'chore', 94 | 'revert', 95 | 'deps', 96 | fallbackType, 97 | ]; 98 | 99 | /** 100 | * @param {string} commitString 101 | * @returns {ICommit} 102 | */ 103 | function parseCommit(commitString) { 104 | /** @type {ICommit} */ 105 | const commitDataObj = {}; 106 | const commitDataArray = 107 | commitString 108 | .split(commitInnerSeparator) 109 | .map(s => s.trim()); 110 | 111 | for (const [key] of commitDataMap) { 112 | commitDataObj[key] = commitDataArray.shift(); 113 | } 114 | 115 | return commitDataObj; 116 | } 117 | 118 | /** 119 | * Returns an array of commits since the last git tag 120 | * @return {ICommit[]} 121 | */ 122 | function getCommits() { 123 | 124 | const format = Array.from(commitDataMap.values()).join(commitInnerSeparator) + commitOuterSeparator; 125 | 126 | const logs = String(execSync(`git --no-pager log ${START_FROM}..${END_TO} --pretty=format:"${format}" --reverse`)); 127 | 128 | return logs 129 | .trim() 130 | .split(commitOuterSeparator) 131 | .filter(r => !!r.trim()) // Skip empty lines 132 | .map(parseCommit); 133 | } 134 | 135 | 136 | /** 137 | * 138 | * @param {ICommit} commit 139 | * @return {ICommitExtended} 140 | */ 141 | function setCommitTypeAndScope(commit) { 142 | 143 | const matchRE = new RegExp(`^(?:(${supportedTypes.join('|')})(?:\\((\\S+)\\))?:)?(.*)`, 'i'); 144 | 145 | let [, type, scope, clearSubject] = commit.subject.match(matchRE); 146 | 147 | /** 148 | * Additional rules for checking committees that do not comply with the convention, but for which it is possible to determine the type. 149 | */ 150 | // Commits like `revert something` 151 | if (type === undefined && commit.subject.startsWith('revert')) { 152 | type = 'revert'; 153 | } 154 | 155 | return { 156 | ...commit, 157 | type: (type || fallbackType).toLowerCase().trim(), 158 | scope: (scope || '').toLowerCase().trim(), 159 | subject: (clearSubject || commit.subject).trim(), 160 | }; 161 | } 162 | 163 | class CommitGroup { 164 | constructor() { 165 | this.scopes = new Map; 166 | this.commits = []; 167 | } 168 | 169 | /** 170 | * 171 | * @param {ICommitExtended[]} array 172 | * @param {ICommitExtended} commit 173 | */ 174 | static _pushOrMerge(array, commit) { 175 | const similarCommit = array.find(c => c.subject === commit.subject); 176 | if (similarCommit) { 177 | if (commit.abbreviated_commit !== undefined) { 178 | similarCommit.abbreviated_commit += `, ${commit.abbreviated_commit}`; 179 | } 180 | } else { 181 | array.push(commit); 182 | } 183 | } 184 | 185 | /** 186 | * @param {ICommitExtended} commit 187 | */ 188 | push(commit) { 189 | if (!commit.scope) { 190 | CommitGroup._pushOrMerge(this.commits, commit); 191 | return; 192 | } 193 | 194 | const scope = this.scopes.get(commit.scope) || {commits: []}; 195 | CommitGroup._pushOrMerge(scope.commits, commit); 196 | this.scopes.set(commit.scope, scope); 197 | } 198 | 199 | get isEmpty() { 200 | return this.commits.length === 0 && this.scopes.size === 0; 201 | } 202 | } 203 | 204 | 205 | /** 206 | * Groups all commits by type and scopes 207 | * @param {ICommit[]} commits 208 | * @returns {Map} 209 | */ 210 | function getGroupedCommits(commits) { 211 | const parsedCommits = commits.map(setCommitTypeAndScope); 212 | 213 | const types = new Map( 214 | supportedTypes.map(id => ([id, new CommitGroup()])), 215 | ); 216 | 217 | for (const parsedCommit of parsedCommits) { 218 | const typeId = parsedCommit.type; 219 | const type = types.get(typeId); 220 | type.push(parsedCommit); 221 | } 222 | 223 | return types; 224 | } 225 | 226 | /** 227 | * Return markdown list with commits 228 | * @param {ICommitExtended[]} commits 229 | * @param {string} pad 230 | * @returns {string} 231 | */ 232 | function getCommitsList(commits, pad = '') { 233 | let changelog = ''; 234 | for (const commit of commits) { 235 | changelog += `${pad}- ${commit.subject}.`; 236 | 237 | if (commit.abbreviated_commit !== undefined) { 238 | changelog += ` (${commit.abbreviated_commit})`; 239 | } 240 | 241 | changelog += '\r\n'; 242 | 243 | if (commit.body === undefined) { 244 | continue; 245 | } 246 | 247 | const body = commit.body.replace('[skip ci]', '').trim(); 248 | if (body !== '') { 249 | changelog += `${ 250 | body 251 | .split(/\r*\n+/) 252 | .filter(s => !!s.trim()) 253 | .map(s => `${pad} ${s}`) 254 | .join('\r\n') 255 | }${'\r\n'}`; 256 | } 257 | } 258 | 259 | return changelog; 260 | } 261 | 262 | 263 | function replaceHeader(str) { 264 | switch (str) { 265 | case 'feat': 266 | return 'New Features'; 267 | case 'fix': 268 | return 'Bug Fixes'; 269 | case 'docs': 270 | return 'Documentation Changes'; 271 | case 'build': 272 | return 'Build System'; 273 | case 'chore': 274 | return 'Chores'; 275 | case 'ci': 276 | return 'Continuous Integration'; 277 | case 'refactor': 278 | return 'Refactors'; 279 | case 'style': 280 | return 'Code Style Changes'; 281 | case 'test': 282 | return 'Tests'; 283 | case 'perf': 284 | return 'Performance improvements'; 285 | case 'revert': 286 | return 'Reverts'; 287 | case 'deps': 288 | return 'Dependency updates'; 289 | case 'other': 290 | return 'Other Changes'; 291 | default: 292 | return str; 293 | } 294 | } 295 | 296 | 297 | /** 298 | * Return markdown string with changelog 299 | * @param {Map} groups 300 | */ 301 | function getChangeLog(groups) { 302 | 303 | let changelog = ''; 304 | 305 | for (const [typeId, group] of groups) { 306 | if (group.isEmpty) { 307 | continue; 308 | } 309 | 310 | changelog += `### ${replaceHeader(typeId)}${'\r\n'}`; 311 | 312 | for (const [scopeId, scope] of group.scopes) { 313 | if (scope.commits.length) { 314 | changelog += `- #### ${replaceHeader(scopeId)}${'\r\n'}`; 315 | changelog += getCommitsList(scope.commits, ' '); 316 | } 317 | } 318 | 319 | if (group.commits.length) { 320 | changelog += getCommitsList(group.commits); 321 | } 322 | 323 | changelog += ('\r\n' + '\r\n'); 324 | } 325 | 326 | return changelog.trim(); 327 | } 328 | 329 | 330 | function escapeData(s) { 331 | return String(s) 332 | .replace(/%/g, '%25') 333 | .replace(/\r/g, '%0D') 334 | .replace(/\n/g, '%0A'); 335 | } 336 | 337 | try { 338 | const commits = getCommits(); 339 | const grouped = getGroupedCommits(commits); 340 | const changelog = getChangeLog(grouped); 341 | process.stdout.write('::set-output name=release-note::' + escapeData(changelog) + '\r\n'); 342 | // require('fs').writeFileSync('../CHANGELOG.md', changelog, {encoding: 'utf-8'}) 343 | } catch (e) { 344 | console.error(e); 345 | process.exit(1); 346 | } 347 | -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base", 4 | ":semanticCommits", 5 | ":automergeTypes", 6 | ":disableDependencyDashboard" 7 | ], 8 | "labels": [ 9 | "dependencies" 10 | ], 11 | "baseBranches": [ 12 | "main" 13 | ], 14 | "bumpVersion": "patch", 15 | "patch": { 16 | "automerge": true 17 | }, 18 | "minor": { 19 | "automerge": true 20 | }, 21 | "packageRules": [ 22 | { 23 | "packageNames": [ 24 | "node", 25 | "npm" 26 | ], 27 | "enabled": false 28 | }, 29 | { 30 | "depTypeList": [ 31 | "devDependencies" 32 | ], 33 | "semanticCommitType": "build" 34 | }, 35 | { 36 | "matchSourceUrlPrefixes": [ 37 | "https://github.com/vitejs/vite/" 38 | ], 39 | "groupName": "Vite monorepo packages", 40 | "automerge": false 41 | }, 42 | { 43 | "matchPackagePatterns": [ 44 | "^@typescript-eslint", 45 | "^eslint" 46 | ], 47 | "automerge": true, 48 | "groupName": "eslint" 49 | }, 50 | { 51 | "matchPackageNames": [ 52 | "electron" 53 | ], 54 | "separateMajorMinor": false 55 | } 56 | ], 57 | "rangeStrategy": "pin" 58 | } 59 | -------------------------------------------------------------------------------- /.github/workflows/ci.del-yml: -------------------------------------------------------------------------------- 1 | # 参考:https://www.ruanyifeng.com/blog/2019/09/getting-started-with-github-actions.html 2 | # 参考:https://docs.github.com/en/github/authenticating-to-github/keeping-your-account-and-data-secure/creating-a-personal-access-token 3 | # 参考:https://docs.github.com/cn/actions/guides/building-and-testing-nodejs 4 | # 参考:https://github.com/ruanyf/github-actions-demo 5 | 6 | # workflow的名称 7 | # name: map-download master ci 8 | # # 整个流程在master分支发生push事件时触发 9 | # on: 10 | # push: 11 | # # 分支 12 | # branches: [ master ] 13 | # # 执行的job 14 | # jobs: 15 | # # job名称 16 | # build-and-deploy: 17 | # # 指定job运行环境 18 | # runs-on: ubuntu-latest 19 | # strategy: 20 | # matrix: 21 | # node-version: [16.13.1] 22 | # # job的步骤 23 | # steps: 24 | # # 步骤名称 25 | # - name: Checkout 26 | # # 使用action库进行代码拉取 27 | # uses: actions/checkout@v2 28 | # with: 29 | # persist-credentials: false 30 | # - name: Install and Build 31 | # run: | 32 | # yarn install --frozen-lockfile 33 | # yarn run compile 34 | # zip -r mapdownload.zip dist 35 | 36 | # # - name: Deploy 37 | # # uses: JamesIves/github-pages-deploy-action@releases/v3 38 | # # with: 39 | # # ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }} 40 | # # BRANCH: gh-pages 41 | # # FOLDER: dist 42 | # # 发布到 Release 43 | # - name: Release exe 44 | # uses: ncipollo/release-action@v1.5.0 45 | # with: 46 | # # ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }} 47 | # artifacts: "mapdownload.zip" 48 | 49 | 50 | name: Release 51 | on: 52 | push: 53 | branches: 54 | - main 55 | paths-ignore: 56 | - '**.md' 57 | - '**.spec.js' 58 | - '.idea' 59 | - '.gitignore' 60 | - '.github/**' 61 | - '!.github/workflows/release.yml' 62 | 63 | concurrency: 64 | group: release-${{ github.ref }} 65 | cancel-in-progress: true 66 | 67 | 68 | defaults: 69 | run: 70 | shell: 'bash' 71 | 72 | 73 | jobs: 74 | 75 | draft: 76 | runs-on: ubuntu-latest 77 | outputs: 78 | release-note: ${{ steps.release-note.outputs.release-note }} 79 | version: ${{ steps.version.outputs.build-version }} 80 | 81 | steps: 82 | - uses: actions/checkout@v2 83 | with: 84 | fetch-depth: 0 85 | 86 | - uses: actions/setup-node@v2 87 | with: 88 | node-version: 14 89 | 90 | - name: Get last git tag 91 | id: tag 92 | run: echo "::set-output name=last-tag::$(git describe --tags --abbrev=0 || git rev-list --max-parents=0 ${{github.ref}})" 93 | 94 | - name: Generate release notes 95 | uses: ./.github/actions/release-notes 96 | id: release-note 97 | with: 98 | from: ${{ steps.tag.outputs.last-tag }} 99 | to: ${{ github.ref }} 100 | include-commit-body: true 101 | include-abbreviated-commit: true 102 | 103 | - name: Get version from current date 104 | id: version 105 | run: echo "::set-output name=build-version::$(node -e "try{console.log(require('./.electron-builder.config.js').extraMetadata.version)}catch(e){console.error(e);process.exit(1)}")" 106 | 107 | 108 | - name: Waiting on All checks 109 | uses: lewagon/wait-on-check-action@v0.2 110 | with: 111 | ref: ${{ github.ref }} 112 | repo-token: ${{ secrets.GITHUB_TOKEN }} 113 | running-workflow-name: 'draft' 114 | 115 | - name: Delete outdated drafts 116 | uses: hugo19941994/delete-draft-releases@v1.0.0 117 | env: 118 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 119 | 120 | - name: Create Release Draft 121 | uses: softprops/action-gh-release@v1 122 | env: 123 | GITHUB_TOKEN: ${{ secrets.github_token }} 124 | with: 125 | prerelease: true 126 | draft: true 127 | tag_name: v${{ steps.version.outputs.build-version }} 128 | name: v${{ steps.version.outputs.build-version }} 129 | body: ${{ steps.release-note.outputs.release-note }} 130 | 131 | upload_artifacts: 132 | needs: [ draft ] 133 | 134 | strategy: 135 | matrix: 136 | os: [ windows-latest ] 137 | # To compile the application for different platforms, use: 138 | # os: [ macos-latest, ubuntu-latest, windows-latest ] 139 | 140 | runs-on: ${{ matrix.os }} 141 | 142 | steps: 143 | - uses: actions/checkout@v2 144 | 145 | - uses: actions/setup-node@v2 146 | with: 147 | node-version: 16 # Need for npm >=7.7 148 | cache: 'npm' 149 | 150 | - name: Install dependencies 151 | run: npm ci 152 | 153 | # The easiest way to transfer release notes to a compiled application is create `release-notes.md` in the build resources. 154 | # See https://github.com/electron-userland/electron-builder/issues/1511#issuecomment-310160119 155 | - name: Prepare release notes 156 | env: 157 | RELEASE_NOTE: ${{ needs.draft.outputs.release-note }} 158 | run: echo "$RELEASE_NOTE" >> ./buildResources/release-notes.md 159 | 160 | # Compile app and upload artifacts 161 | - name: Compile & release Electron app 162 | uses: samuelmeuli/action-electron-builder@v1 163 | env: 164 | VITE_APP_VERSION: ${{ needs.draft.outputs.version }} 165 | with: 166 | build_script_name: build 167 | args: --config .electron-builder.config.js 168 | 169 | # GitHub token, automatically provided to the action 170 | # (No need to define this secret in the repo settings) 171 | github_token: ${{ secrets.github_token }} 172 | 173 | # If the commit is tagged with a version (e.g. "v1.0.0"), 174 | # release the app after building 175 | release: true 176 | 177 | # Sometimes the build may fail due to a connection problem with Apple, GitHub, etc. servers. 178 | # This option will restart the build as many attempts as possible 179 | max_attempts: 3 180 | 181 | 182 | # Code Signing params 183 | 184 | # Base64-encoded code signing certificate for Windows 185 | # windows_certs: '' 186 | 187 | # Password for decrypting `windows_certs` 188 | # windows_certs_password: '' 189 | 190 | # Base64-encoded code signing certificate for macOS 191 | # mac_certs: '' 192 | 193 | # Password for decrypting `mac_certs` 194 | # mac_certs_password: '' 195 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | push: 4 | branches: 5 | - main 6 | paths-ignore: 7 | - '**.md' 8 | - '**.spec.js' 9 | - '.idea' 10 | - '.gitignore' 11 | - '.github/**' 12 | - '!.github/workflows/release.yml' 13 | 14 | concurrency: 15 | group: release-${{ github.ref }} 16 | cancel-in-progress: true 17 | 18 | 19 | defaults: 20 | run: 21 | shell: 'bash' 22 | 23 | 24 | jobs: 25 | 26 | draft: 27 | runs-on: ubuntu-latest 28 | outputs: 29 | release-note: ${{ steps.release-note.outputs.release-note }} 30 | version: ${{ steps.version.outputs.build-version }} 31 | 32 | steps: 33 | - uses: actions/checkout@v2 34 | with: 35 | fetch-depth: 0 36 | 37 | - uses: actions/setup-node@v2 38 | with: 39 | node-version: 14 40 | 41 | - name: Get last git tag 42 | id: tag 43 | run: echo "::set-output name=last-tag::$(git describe --tags --abbrev=0 || git rev-list --max-parents=0 ${{github.ref}})" 44 | 45 | - name: Generate release notes 46 | uses: ./.github/actions/release-notes 47 | id: release-note 48 | with: 49 | from: ${{ steps.tag.outputs.last-tag }} 50 | to: ${{ github.ref }} 51 | include-commit-body: true 52 | include-abbreviated-commit: true 53 | 54 | - name: Get version from current date 55 | id: version 56 | run: echo "::set-output name=build-version::$(node -e "try{console.log(require('./.electron-builder.config.js').extraMetadata.version)}catch(e){console.error(e);process.exit(1)}")" 57 | 58 | 59 | - name: Waiting on All checks 60 | uses: lewagon/wait-on-check-action@v0.2 61 | with: 62 | ref: ${{ github.ref }} 63 | repo-token: ${{ secrets.GITHUB_TOKEN }} 64 | running-workflow-name: 'draft' 65 | 66 | - name: Delete outdated drafts 67 | uses: hugo19941994/delete-draft-releases@v1.0.0 68 | env: 69 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 70 | 71 | - name: Create Release Draft 72 | uses: softprops/action-gh-release@v1 73 | env: 74 | GITHUB_TOKEN: ${{ secrets.github_token }} 75 | with: 76 | prerelease: true 77 | draft: true 78 | tag_name: v${{ steps.version.outputs.build-version }} 79 | name: v${{ steps.version.outputs.build-version }} 80 | body: ${{ steps.release-note.outputs.release-note }} 81 | 82 | upload_artifacts: 83 | needs: [ draft ] 84 | 85 | strategy: 86 | matrix: 87 | os: [ windows-latest ] 88 | # To compile the application for different platforms, use: 89 | # os: [ macos-latest, ubuntu-latest, windows-latest ] 90 | 91 | runs-on: ${{ matrix.os }} 92 | 93 | steps: 94 | - uses: actions/checkout@v2 95 | 96 | - uses: actions/setup-node@v2 97 | with: 98 | node-version: 16 # Need for npm >=7.7 99 | cache: 'npm' 100 | 101 | - name: Install dependencies 102 | run: npm install 103 | 104 | # The easiest way to transfer release notes to a compiled application is create `release-notes.md` in the build resources. 105 | # See https://github.com/electron-userland/electron-builder/issues/1511#issuecomment-310160119 106 | - name: Prepare release notes 107 | env: 108 | RELEASE_NOTE: ${{ needs.draft.outputs.release-note }} 109 | run: echo "$RELEASE_NOTE" >> ./buildResources/release-notes.md 110 | 111 | # Compile app and upload artifacts 112 | - name: Compile & release Electron app 113 | uses: samuelmeuli/action-electron-builder@v1 114 | env: 115 | VITE_APP_VERSION: ${{ needs.draft.outputs.version }} 116 | with: 117 | build_script_name: build 118 | args: --config .electron-builder.config.js 119 | 120 | # GitHub token, automatically provided to the action 121 | # (No need to define this secret in the repo settings) 122 | github_token: ${{ secrets.github_token }} 123 | 124 | # If the commit is tagged with a version (e.g. "v1.0.0"), 125 | # release the app after building 126 | release: true 127 | 128 | # Sometimes the build may fail due to a connection problem with Apple, GitHub, etc. servers. 129 | # This option will restart the build as many attempts as possible 130 | max_attempts: 3 131 | 132 | 133 | # Code Signing params 134 | 135 | # Base64-encoded code signing certificate for Windows 136 | # windows_certs: '' 137 | 138 | # Password for decrypting `windows_certs` 139 | # windows_certs_password: '' 140 | 141 | # Base64-encoded code signing certificate for macOS 142 | # mac_certs: '' 143 | 144 | # Password for decrypting `mac_certs` 145 | # mac_certs_password: '' 146 | -------------------------------------------------------------------------------- /.github/workflows/update-electron-vendors.yml: -------------------------------------------------------------------------------- 1 | name: Update Electon vendors versions 2 | on: 3 | push: 4 | branches: 5 | - main 6 | paths: 7 | - 'package-lock.json' 8 | 9 | 10 | concurrency: 11 | group: update-electron-vendors-${{ github.ref }} 12 | cancel-in-progress: true 13 | 14 | 15 | defaults: 16 | run: 17 | shell: 'bash' 18 | 19 | 20 | jobs: 21 | node-chrome: 22 | runs-on: ubuntu-latest 23 | 24 | steps: 25 | - uses: actions/checkout@v2 26 | - uses: actions/setup-node@v2 27 | with: 28 | node-version: 16 # Need for npm >=7.7 29 | cache: 'npm' 30 | 31 | # TODO: Install not all dependencies, but only those required for this workflow 32 | - name: Install dependencies 33 | run: npm ci 34 | 35 | - run: node ./scripts/update-electron-vendors.js 36 | 37 | - name: Create Pull Request 38 | uses: peter-evans/create-pull-request@v3 39 | with: 40 | delete-branch: true 41 | commit-message: Update electron vendors 42 | branch: autoupdates/electron-vendors 43 | title: Update electron vendors 44 | body: Updated versions of electron vendors in `.electron-vendors.cache.json` and `.browserslistrc` files 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist 4 | *.local 5 | thumbs.db 6 | 7 | .eslintcache 8 | 9 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 10 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 11 | 12 | # User-specific stuff 13 | .idea/**/workspace.xml 14 | .idea/**/tasks.xml 15 | .idea/**/usage.statistics.xml 16 | .idea/**/dictionaries 17 | .idea/**/shelf 18 | 19 | # Generated files 20 | .idea/**/contentModel.xml 21 | 22 | # Sensitive or high-churn files 23 | .idea/**/dataSources/ 24 | .idea/**/dataSources.ids 25 | .idea/**/dataSources.local.xml 26 | .idea/**/sqlDataSources.xml 27 | .idea/**/dynamic.xml 28 | .idea/**/uiDesigner.xml 29 | .idea/**/dbnavigator.xml 30 | 31 | # Gradle 32 | .idea/**/gradle.xml 33 | .idea/**/libraries 34 | 35 | # Gradle and Maven with auto-import 36 | # When using Gradle or Maven with auto-import, you should exclude module files, 37 | # since they will be recreated, and may cause churn. Uncomment if using 38 | # auto-import. 39 | .idea/artifacts 40 | .idea/compiler.xml 41 | .idea/jarRepositories.xml 42 | .idea/modules.xml 43 | .idea/*.iml 44 | .idea/modules 45 | *.iml 46 | *.ipr 47 | 48 | # Mongo Explorer plugin 49 | .idea/**/mongoSettings.xml 50 | 51 | # File-based project format 52 | *.iws 53 | 54 | # Editor-based Rest Client 55 | .idea/httpRequests 56 | /.idea/csv-plugin.xml 57 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 15 | 16 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/deployment.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /.idea/jsLibraryMappings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/jsLinters/eslint.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/vite-electron-builder.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /.idea/webResources.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Alex Kozack 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Required Node.JS >= v16.13](https://img.shields.io/static/v1?label=node&message=%3E=16.13&logo=node.js&color)](https://nodejs.org/about/releases/) 2 | [![Required npm >= v8.1](https://img.shields.io/static/v1?label=npm&message=%3E=8.1&logo=npm&color)](https://github.com/npm/cli/releases) 3 | # map-download 4 | 5 | > 基于electron和maptalks实现高德地图、百度地图(包括百度自定义地图 !!!百度个性化地图午夜蓝、清新蓝、黑夜等等链接已经失效!!!)、腾讯地图、OpenStreetMap、CartoDb、ArcGIS在线地图、天地图、MapBox的下载 6 | 7 | > 支持卫星遥感影像和标注合并 8 | 9 | > 支持行政区划瓦片下载,裁切边界 10 | 11 | > 支持下载瓦片格式jpeg、png、webp 12 | 13 | > 软件下载地址:https://github.com/Hxy1992/MapDownload/releases 14 | 15 | > V0.42版本win-unpacked压缩包 百度网盘链接:https://pan.baidu.com/s/1M12KnC8bIvyHo3ik3hxy9A 提取码:9986 16 | 17 | ![image](https://user-images.githubusercontent.com/14800641/154039927-e8994f36-523b-40cb-b184-46a7d8e1a9f2.png) 18 | 19 | 20 | ## Build Setup 21 | 22 | ``` bash 23 | # 安装依赖(依赖较大,使用国内镜像) 24 | npm install 25 | 26 | # 热更新服务 27 | npm run dev / npm run watch 28 | 29 | # 构建web 30 | npm run build 31 | 32 | # 构建应用 33 | npm run compile 34 | 35 | ``` 36 | --- 37 | 38 | ## 下载瓦片加载方式 39 | 40 | ### Cesium 41 | 42 | ```javascript 43 | // 非百度地图 44 | viewer.imageryLayers.addImageryProvider(new Cesium.UrlTemplateImageryProvider({ 45 | url: 'http://localhost:7099/{z}/{x}/{y}.png' 46 | })) 47 | // 百度地图(需自定义BaiduImageryProvider),可参考cesium-helper目录下代码 48 | import BaiduImageryProvider from './cesium-helper/BaiduImageryProvider/BaiduImageryProvider.js' 49 | viewer.imageryLayers.addImageryProvider(new BaiduImageryProvider({ 50 | url: 'http://localhost:7099/{z}/{x}/{y}.png' 51 | })) 52 | 53 | ``` 54 | 55 | ### openlayers 56 | 57 | ```javascript 58 | // 非百度地图 59 | const baseMap = new ol.layer.Tile({ 60 | source: new ol.source.XYZ({ 61 | url: 'http://localhost:7099/{z}/{x}/{y}.png', 62 | projection: 'EPSG:3857', 63 | }), 64 | }); 65 | const map = new ol.Map({ 66 | layers: [baseMap], 67 | target: 'map', 68 | view: new ol.View({ 69 | center: ol.proj.transform([105.08052356963802, 36.04231948670001], 'EPSG:4326', 'EPSG:3857'), 70 | zoom: 5, 71 | }), 72 | }); 73 | 74 | ``` 75 | 76 | ### maptalks 77 | 78 | ```javascript 79 | // 非百度地图 80 | var map = new maptalks.Map('map', { 81 | center: [105.08052356963802, 36.04231948670001], 82 | zoom: 5, 83 | minZoom:1, 84 | maxZoom:19, 85 | baseLayer: new maptalks.TileLayer('base', { 86 | 'urlTemplate' : 'http://localhost:7099/{z}/{x}/{y}.png' 87 | }) 88 | }); 89 | //百度地图 90 | var map = new maptalks.Map('map', { 91 | center: [105.08052356963802, 36.04231948670001], 92 | zoom: 5, 93 | minZoom:1, 94 | maxZoom:19, 95 | spatialReference:{ 96 | projection : 'baidu', 97 | }, 98 | baseLayer: new maptalks.TileLayer('base', { 99 | 'urlTemplate' : 'http://localhost:7099/{z}/{x}/{y}.png' 100 | }) 101 | }); 102 | ``` 103 | 104 | # TODO 105 | + 自定义图层加载、下载,支持上传geojson作为下载范围 106 | + 瓦片拼接大图 107 | + [断点续传](https://github.com/Hxy1992/MapDownload/issues/27) 108 | 109 | 如果该项目对你有帮助,麻烦给个star!欢迎提PR! 110 | 111 | 声明:本软件仅供个人学习与科研使用,所下载的数据版权归各个地图服务商所有,任何组织或个人因数据使用不当造成的问题,软件作者不负责任。 112 | -------------------------------------------------------------------------------- /buildResources/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hxy1992/MapDownload/99f54e18d151379e01d6aae3b368a668f898663a/buildResources/.gitkeep -------------------------------------------------------------------------------- /buildResources/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hxy1992/MapDownload/99f54e18d151379e01d6aae3b368a668f898663a/buildResources/icon.icns -------------------------------------------------------------------------------- /buildResources/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hxy1992/MapDownload/99f54e18d151379e01d6aae3b368a668f898663a/buildResources/icon.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "map-download", 3 | "description": "高德地图、百度地图(包括百度自定义地图)、腾讯地图、OpenStreetMap、CartoDb、ArcGIS在线地图、天地图、MapBox的下载", 4 | "author": "Hxy1992", 5 | "repository": "https://github.com/Hxy1992/MapDownload", 6 | "version": "v1.0.0", 7 | "private": true, 8 | "engines": { 9 | "node": ">=v16.13", 10 | "npm": ">=8.1" 11 | }, 12 | "main": "packages/main/dist/index.cjs", 13 | "scripts": { 14 | "build": "node --max_old_space_size=8192 scripts/build.js", 15 | "precompile": "cross-env MODE=production npm run build", 16 | "compile": "electron-builder build --config .electron-builder.config.js --dir --config.asar=false", 17 | "pretest": "npm run build", 18 | "test": "node tests/app.spec.js", 19 | "watch": "node scripts/watch.js", 20 | "lint": "eslint . --ext js,ts,vue", 21 | "dev": "npm run watch" 22 | }, 23 | "browserslist": [ 24 | "Chrome 96" 25 | ], 26 | "lint-staged": { 27 | "*.{js,ts,vue}": "eslint --cache --fix" 28 | }, 29 | "devDependencies": { 30 | "@vicons/ionicons5": "^0.12.0", 31 | "@vitejs/plugin-vue": "1.10.0", 32 | "cross-env": "7.0.3", 33 | "electron": "16.0.1", 34 | "electron-builder": "22.14.5", 35 | "electron-devtools-installer": "3.2.0", 36 | "eslint": "8.3.0", 37 | "eslint-plugin-vue": "8.1.1", 38 | "naive-ui": "^2.28.0", 39 | "playwright": "1.16.3", 40 | "sass": "^1.45.0", 41 | "vfonts": "^0.0.3", 42 | "vite": "2.6.14" 43 | }, 44 | "dependencies": { 45 | "@turf/boolean-contains": "^6.5.0", 46 | "@turf/boolean-crosses": "^6.5.0", 47 | "@turf/boolean-disjoint": "^6.5.0", 48 | "@turf/helpers": "^6.5.0", 49 | "@turf/intersect": "^6.5.0", 50 | "electron-updater": "4.6.2", 51 | "fs-extra": "^10.0.0", 52 | "maptalks": "^1.0.0-rc.33", 53 | "marked": "^4.0.8", 54 | "sharp": "^0.29.3", 55 | "superagent": "^6.1.0", 56 | "vue": "3.2.22", 57 | "vue-router": "4.0.12" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /packages/main/src/index.js: -------------------------------------------------------------------------------- 1 | import {app, BrowserWindow, shell} from 'electron'; 2 | import {join} from 'path'; 3 | import {URL} from 'url'; 4 | import { ipcHandle } from './ipcMain'; 5 | 6 | 7 | const isSingleInstance = app.requestSingleInstanceLock(); 8 | const isDevelopment = import.meta.env.MODE === 'development'; 9 | 10 | if (!isSingleInstance) { 11 | app.quit(); 12 | process.exit(0); 13 | } 14 | 15 | app.disableHardwareAcceleration(); 16 | 17 | // Install "Vue.js devtools" 18 | if (isDevelopment) { 19 | app.whenReady() 20 | .then(() => import('electron-devtools-installer')) 21 | .then(({default: installExtension, VUEJS3_DEVTOOLS}) => installExtension(VUEJS3_DEVTOOLS, { 22 | loadExtensionOptions: { 23 | allowFileAccess: true, 24 | }, 25 | })) 26 | .catch(e => console.error('Failed install extension:', e)); 27 | } 28 | 29 | let mainWindow = null; 30 | 31 | const createWindow = async () => { 32 | mainWindow = new BrowserWindow({ 33 | show: false, // Use 'ready-to-show' event to show window 34 | webPreferences: { 35 | nativeWindowOpen: true, 36 | // nodeIntegration: true, 37 | width: 800, 38 | height: 600, 39 | preload: join(__dirname, '../../preload/dist/index.cjs'), 40 | }, 41 | }); 42 | 43 | /** 44 | * If you install `show: true` then it can cause issues when trying to close the window. 45 | * Use `show: false` and listener events `ready-to-show` to fix these issues. 46 | * 47 | * @see https://github.com/electron/electron/issues/25012 48 | */ 49 | mainWindow.on('ready-to-show', () => { 50 | mainWindow?.show(); 51 | 52 | if (isDevelopment) { 53 | mainWindow?.webContents.openDevTools(); 54 | } 55 | }); 56 | 57 | /** 58 | * URL for main window. 59 | * Vite dev server for development. 60 | * `file://../renderer/index.html` for production and test 61 | */ 62 | const pageUrl = isDevelopment && import.meta.env.VITE_DEV_SERVER_URL !== undefined 63 | ? import.meta.env.VITE_DEV_SERVER_URL 64 | : new URL('../renderer/dist/index.html', 'file://' + __dirname).toString(); 65 | 66 | 67 | await mainWindow.loadURL(pageUrl, { 68 | userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36', 69 | }); 70 | 71 | ipcHandle(mainWindow); 72 | }; 73 | 74 | app.on('web-contents-created', (_event, contents) => { 75 | 76 | /** 77 | * Block navigation to origins not on the allowlist. 78 | * 79 | * Navigation is a common attack vector. If an attacker can convince the app to navigate away 80 | * from its current page, they can possibly force the app to open web sites on the Internet. 81 | * 82 | * @see https://www.electronjs.org/docs/latest/tutorial/security#13-disable-or-limit-navigation 83 | */ 84 | contents.on('will-navigate', (event, url) => { 85 | const allowedOrigins = 86 | new Set(); // Do not use insecure protocols like HTTP. https://www.electronjs.org/docs/latest/tutorial/security#1-only-load-secure-content 87 | const { origin, hostname } = new URL(url); 88 | const isDevLocalhost = isDevelopment && hostname === 'localhost'; // permit live reload of index.html 89 | if (!allowedOrigins.has(origin) && !isDevLocalhost){ 90 | console.warn('Blocked navigating to an unallowed origin:', origin); 91 | event.preventDefault(); 92 | } 93 | }); 94 | 95 | /** 96 | * Hyperlinks to allowed sites open in the default browser. 97 | * 98 | * The creation of new `webContents` is a common attack vector. Attackers attempt to convince the app to create new windows, 99 | * frames, or other renderer processes with more privileges than they had before; or with pages opened that they couldn't open before. 100 | * You should deny any unexpected window creation. 101 | * 102 | * @see https://www.electronjs.org/docs/latest/tutorial/security#14-disable-or-limit-creation-of-new-windows 103 | * @see https://www.electronjs.org/docs/latest/tutorial/security#15-do-not-use-openexternal-with-untrusted-content 104 | */ 105 | contents.setWindowOpenHandler(({ url }) => { 106 | const allowedOrigins = 107 | new Set([ // Do not use insecure protocols like HTTP. https://www.electronjs.org/docs/latest/tutorial/security#1-only-load-secure-content 108 | 'https://vitejs.dev', 109 | 'https://github.com', 110 | 'https://v3.vuejs.org']); 111 | const { origin } = new URL(url); 112 | if (allowedOrigins.has(origin)){ 113 | shell.openExternal(url); 114 | } else { 115 | console.warn('Blocked the opening of an unallowed origin:', origin); 116 | } 117 | return { action: 'deny' }; 118 | }); 119 | }); 120 | 121 | 122 | app.on('second-instance', () => { 123 | // Someone tried to run a second instance, we should focus our window. 124 | if (mainWindow) { 125 | if (mainWindow.isMinimized()) mainWindow.restore(); 126 | mainWindow.focus(); 127 | } 128 | }); 129 | 130 | 131 | app.on('window-all-closed', () => { 132 | if (process.platform !== 'darwin') { 133 | app.quit(); 134 | } 135 | }); 136 | 137 | 138 | app.whenReady() 139 | .then(createWindow) 140 | .catch((e) => console.error('Failed create window:', e)); 141 | 142 | 143 | // Auto-updates 144 | if (import.meta.env.PROD) { 145 | app.whenReady() 146 | .then(() => import('electron-updater')) 147 | .then(({autoUpdater}) => autoUpdater.checkForUpdatesAndNotify()) 148 | .catch((e) => console.error('Failed check updates:', e)); 149 | } 150 | 151 | -------------------------------------------------------------------------------- /packages/main/src/ipHandle.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * 设置请求头 4 | */ 5 | export function getHeader() { 6 | return { 7 | 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36', 8 | // 'X-Forwarded-For': returnIp(), 9 | }; 10 | } 11 | -------------------------------------------------------------------------------- /packages/main/src/ipcMain.js: -------------------------------------------------------------------------------- 1 | // 在主进程中. 2 | const { ipcMain } = require('electron'); 3 | const { dialog } = require('electron'); 4 | const fse = require('fs-extra'); 5 | const fs = require('fs'); 6 | const sharp = require('sharp'); 7 | const request = require('superagent'); 8 | const path = require('path'); 9 | import { getHeader } from './ipHandle'; 10 | 11 | ipcMain.handle('show-dialog', async () => { 12 | const result = await dialog.showOpenDialog({ properties: ['openFile', 'openDirectory'] }); 13 | return result; 14 | }); 15 | // 确保目录存在,不存在则创建 16 | ipcMain.on('ensure-dir', (event, args) => { 17 | fse.ensureDirSync(args); 18 | }); 19 | 20 | 21 | // 下载事件 22 | export function ipcHandle(win) { 23 | 24 | // superagent & sharp 下载图片 25 | ipcMain.on('save-image', (event, args) => { 26 | // sharp(base64Data).composite 反过来试试 27 | const savePath = path.normalize(args.savePath); 28 | const sharpStream = sharp({ 29 | failOnError: false, 30 | }); 31 | const promises = []; 32 | if (args.imageBuffer) { 33 | const base64Data = args.imageBuffer.replace(/^data:image\/\w+;base64,/, ''); 34 | const dataBuffer = Buffer.from(base64Data, 'base64'); 35 | promises.push( 36 | sharpStream 37 | .composite([{ input: dataBuffer, gravity: 'centre', blend: 'dest-in' }]) 38 | .toFile(savePath), 39 | ); 40 | } else { 41 | promises.push( 42 | sharpStream 43 | // .ensureAlpha() 44 | .toFile(savePath), 45 | ); 46 | } 47 | 48 | request.get(args.url).set(getHeader()).pipe(sharpStream); 49 | Promise.all(promises) 50 | .then(() => { 51 | win.webContents.send('imageDownloadDone', { 52 | state: 'completed', 53 | }); 54 | }) 55 | .catch((err) => { 56 | console.error('错误', err); 57 | try { 58 | fs.unlinkSync(savePath); 59 | } catch { 60 | // do nothing 61 | } 62 | win.webContents.send('imageDownloadDone', { 63 | state: 'error', 64 | }); 65 | }); 66 | }); 67 | 68 | // superagent & sharp 下载、合并图片 69 | ipcMain.on('save-image-merge', (event, args) => { 70 | try { 71 | const savePath = path.normalize(args.savePath); 72 | let imgBack; 73 | const imgBuffer = []; 74 | args.layers.forEach(async (item, index) => { 75 | const sharpStream = sharp({ 76 | failOnError: false, 77 | }); 78 | request.get(args.url).set(getHeader()).pipe(sharpStream); 79 | const bff = await sharpStream.toBuffer(); 80 | if (item.isLabel) { 81 | imgBack = bff; 82 | } else { 83 | imgBuffer.push(bff); 84 | } 85 | // 结束保存 86 | if (index === args.layers.length - 1) { 87 | let opration; 88 | if (args.imageBuffer) { 89 | const base64Data = args.imageBuffer.replace(/^data:image\/\w+;base64,/, ''); 90 | const dataBuffer = Buffer.from(base64Data, 'base64'); 91 | sharp(imgBack) 92 | .composite(imgBuffer.map(input => { 93 | return { input, gravity: 'centre', blend: 'saturate' }; 94 | })) 95 | .composite([{ input: dataBuffer, gravity: 'centre', blend: 'dest-in' }]); 96 | } else { 97 | opration = sharp(imgBack) 98 | .composite(imgBuffer.map(input => { 99 | return { input, gravity: 'centre', blend: 'saturate' }; 100 | })); 101 | } 102 | opration 103 | .toFile(savePath) 104 | .then(() => { 105 | win.webContents.send('imageDownloadDone', { 106 | state: 'completed', 107 | }); 108 | }) 109 | .catch((err) => { 110 | console.error('错误', err); 111 | try { 112 | fs.unlinkSync(savePath); 113 | } catch (e) { 114 | // do nothing 115 | } 116 | }); 117 | } 118 | }); 119 | } catch { 120 | win.webContents.send('imageDownloadDone', { 121 | state: 'error', 122 | }); 123 | } 124 | 125 | }); 126 | 127 | } 128 | -------------------------------------------------------------------------------- /packages/main/vite.config.js: -------------------------------------------------------------------------------- 1 | import {node} from '../../.electron-vendors.cache.json'; 2 | import {join} from 'path'; 3 | import {builtinModules} from 'module'; 4 | 5 | const PACKAGE_ROOT = __dirname; 6 | 7 | 8 | const config = { 9 | mode: process.env.MODE, 10 | root: PACKAGE_ROOT, 11 | envDir: process.cwd(), 12 | resolve: { 13 | alias: { 14 | '/@/': join(PACKAGE_ROOT, 'src') + '/', 15 | }, 16 | }, 17 | build: { 18 | sourcemap: 'inline', 19 | target: `node${node}`, 20 | outDir: 'dist', 21 | assetsDir: '.', 22 | minify: process.env.MODE !== 'development', 23 | lib: { 24 | entry: 'src/index.js', 25 | formats: ['cjs'], 26 | }, 27 | rollupOptions: { 28 | external: [ 29 | 'electron', 30 | 'electron-devtools-installer', 31 | ...builtinModules, 32 | ], 33 | output: { 34 | entryFileNames: '[name].cjs', 35 | }, 36 | }, 37 | emptyOutDir: true, 38 | brotliSize: false, 39 | }, 40 | }; 41 | 42 | export default config; 43 | -------------------------------------------------------------------------------- /packages/preload/src/index.js: -------------------------------------------------------------------------------- 1 | import {contextBridge} from 'electron'; 2 | const { ipcRenderer } = require('electron'); 3 | 4 | const apiKey = 'electron'; 5 | let imageDownloadhandle; 6 | ipcRenderer.on('imageDownloadDone', (event, state) => { 7 | imageDownloadhandle && imageDownloadhandle(state); 8 | }); 9 | /** 10 | * @see https://github.com/electron/electron/issues/21437#issuecomment-573522360 11 | */ 12 | const api = { 13 | versions: process.versions, 14 | ipcRenderer: { ...ipcRenderer }, 15 | imageDownloadDone: (callback) => { 16 | imageDownloadhandle = callback; 17 | }, 18 | }; 19 | 20 | /** 21 | * The "Main World" is the JavaScript context that your main renderer code runs in. 22 | * By default, the page you load in your renderer executes code in this world. 23 | * 24 | * @see https://www.electronjs.org/docs/api/context-bridge 25 | */ 26 | contextBridge.exposeInMainWorld(apiKey, api); 27 | -------------------------------------------------------------------------------- /packages/preload/vite.config.js: -------------------------------------------------------------------------------- 1 | import {chrome} from '../../.electron-vendors.cache.json'; 2 | import {join} from 'path'; 3 | import {builtinModules} from 'module'; 4 | 5 | const PACKAGE_ROOT = __dirname; 6 | 7 | const config = { 8 | mode: process.env.MODE, 9 | root: PACKAGE_ROOT, 10 | envDir: process.cwd(), 11 | resolve: { 12 | alias: { 13 | '/@/': join(PACKAGE_ROOT, 'src') + '/', 14 | }, 15 | }, 16 | build: { 17 | sourcemap: 'inline', 18 | target: `chrome${chrome}`, 19 | outDir: 'dist', 20 | assetsDir: '.', 21 | minify: process.env.MODE !== 'development', 22 | lib: { 23 | entry: 'src/index.js', 24 | formats: ['cjs'], 25 | }, 26 | rollupOptions: { 27 | external: [ 28 | 'electron', 29 | ...builtinModules, 30 | ], 31 | output: { 32 | entryFileNames: '[name].cjs', 33 | }, 34 | }, 35 | emptyOutDir: true, 36 | brotliSize: false, 37 | }, 38 | }; 39 | 40 | export default config; 41 | -------------------------------------------------------------------------------- /packages/renderer/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "node": false 5 | }, 6 | "extends": [ 7 | /** @see https://eslint.vuejs.org/rules/ */ 8 | "plugin:vue/vue3-recommended" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /packages/renderer/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | MapDownload 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /packages/renderer/src/App.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 17 | 18 | 20 | -------------------------------------------------------------------------------- /packages/renderer/src/assets/grid.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/renderer/src/assets/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /packages/renderer/src/cesium-helper/BaiduImageryProvider/BaiduImageryProvider.js: -------------------------------------------------------------------------------- 1 | import * as Cesium from 'cesium'; 2 | import ImageryType from './ImageryType'; 3 | import BaiduMercatorTilingScheme from './BaiduMercatorTilingScheme'; 4 | 5 | const IMG_URL = 6 | 'https://shangetu{s}.map.bdimg.com/it/u=x={x};y={y};z={z};v=009;type=sate&fm=46'; 7 | 8 | const VEC_URL = 9 | 'https://online{s}.map.bdimg.com/tile/?qt=tile&x={x}&y={y}&z={z}&styles=sl&v=020'; 10 | 11 | const CUSTOM_URL = 12 | 'https://api{s}.map.bdimg.com/customimage/tile?&x={x}&y={y}&z={z}&scale=1&customid=midnight'; 13 | 14 | const TRAFFIC_URL = 15 | 'https://its.map.baidu.com:8002/traffic/TrafficTileService?time={time}&label={labelStyle}&v=016&level={z}&x={x}&y={y}&scaler=2'; 16 | 17 | class BaiduImageryProvider { 18 | constructor(options = {}) { 19 | this._url = 20 | options.style === 'img' 21 | ? IMG_URL 22 | : options.style === 'vec' 23 | ? VEC_URL 24 | : options.style === 'traffic' 25 | ? TRAFFIC_URL 26 | : CUSTOM_URL; 27 | const baiduLocal = options.url; 28 | if (baiduLocal) { 29 | this._url = `${baiduLocal}/{z}/{x}/{y}.png`; 30 | } 31 | this._labelStyle = options.labelStyle || 'web2D'; 32 | this._tileWidth = 256; 33 | this._tileHeight = 256; 34 | this._maximumLevel = 18; 35 | this._crs = options.crs || 'BD09'; 36 | if (options.crs === 'WGS84') { 37 | const resolutions = []; 38 | for (let i = 0; i < 19; i++) { 39 | resolutions[i] = 256 * Math.pow(2, 18 - i); 40 | } 41 | this._tilingScheme = new BaiduMercatorTilingScheme({ 42 | resolutions, 43 | rectangleSouthwestInMeters: new Cesium.Cartesian2( 44 | -20037726.37, 45 | -12474104.17, 46 | ), 47 | rectangleNortheastInMeters: new Cesium.Cartesian2( 48 | 20037726.37, 49 | 12474104.17, 50 | ), 51 | }); 52 | } else { 53 | this._tilingScheme = new Cesium.WebMercatorTilingScheme({ 54 | rectangleSouthwestInMeters: new Cesium.Cartesian2(-33554054, -33746824), 55 | rectangleNortheastInMeters: new Cesium.Cartesian2(33554054, 33746824), 56 | }); 57 | } 58 | this._rectangle = this._tilingScheme.rectangle; 59 | this._credit = undefined; 60 | this._token = undefined; 61 | this._style = options.style || 'normal'; 62 | } 63 | 64 | get url() { 65 | return this._url; 66 | } 67 | 68 | get token() { 69 | return this._token; 70 | } 71 | 72 | get tileWidth() { 73 | if (!this.ready) { 74 | throw new Cesium.DeveloperError( 75 | 'tileWidth must not be called before the imagery provider is ready.', 76 | ); 77 | } 78 | return this._tileWidth; 79 | } 80 | 81 | get tileHeight() { 82 | if (!this.ready) { 83 | throw new Cesium.DeveloperError( 84 | 'tileHeight must not be called before the imagery provider is ready.', 85 | ); 86 | } 87 | return this._tileHeight; 88 | } 89 | 90 | get maximumLevel() { 91 | if (!this.ready) { 92 | throw new Cesium.DeveloperError( 93 | 'maximumLevel must not be called before the imagery provider is ready.', 94 | ); 95 | } 96 | return this._maximumLevel; 97 | } 98 | 99 | get minimumLevel() { 100 | if (!this.ready) { 101 | throw new Cesium.DeveloperError( 102 | 'minimumLevel must not be called before the imagery provider is ready.', 103 | ); 104 | } 105 | return 0; 106 | } 107 | 108 | get tilingScheme() { 109 | if (!this.ready) { 110 | throw new Cesium.DeveloperError( 111 | 'tilingScheme must not be called before the imagery provider is ready.', 112 | ); 113 | } 114 | return this._tilingScheme; 115 | } 116 | 117 | get rectangle() { 118 | if (!this.ready) { 119 | throw new Cesium.DeveloperError( 120 | 'rectangle must not be called before the imagery provider is ready.', 121 | ); 122 | } 123 | return this._rectangle; 124 | } 125 | 126 | get ready() { 127 | return !!this._url; 128 | } 129 | 130 | get credit() { 131 | return this._credit; 132 | } 133 | 134 | get hasAlphaChannel() { 135 | return true; 136 | } 137 | // eslint-disable-next-line 138 | getTileCredits(x, y, level) {} 139 | 140 | /** 141 | * Request Image 142 | * @param x 143 | * @param y 144 | * @param level 145 | * @returns {Promise} 146 | */ 147 | requestImage(x, y, level) { 148 | if (!this.ready) { 149 | throw new Cesium.DeveloperError( 150 | 'requestImage must not be called before the imagery provider is ready.', 151 | ); 152 | } 153 | const xTiles = this._tilingScheme.getNumberOfXTilesAtLevel(level); 154 | const yTiles = this._tilingScheme.getNumberOfYTilesAtLevel(level); 155 | let url = this._url 156 | .replace('{z}', level) 157 | .replace('{s}', String(1)) 158 | .replace('{style}', this._style) 159 | .replace('{labelStyle}', this._labelStyle) 160 | .replace('{time}', String(new Date().getTime())); 161 | 162 | if (this._crs === 'WGS84') { 163 | url = url.replace('{x}', String(x)).replace('{y}', String(-y)); 164 | } else { 165 | url = url 166 | .replace('{x}', String(x - xTiles / 2)) 167 | .replace('{y}', String(yTiles / 2 - y - 1)); 168 | } 169 | return Cesium.ImageryProvider.loadImage(this, url); 170 | } 171 | } 172 | 173 | ImageryType.BAIDU = 'baidu'; 174 | 175 | export default BaiduImageryProvider; 176 | -------------------------------------------------------------------------------- /packages/renderer/src/cesium-helper/BaiduImageryProvider/BaiduMercatorTilingScheme.js: -------------------------------------------------------------------------------- 1 | 2 | import * as Cesium from 'cesium'; 3 | import CoordTransform from './CoordTransform'; 4 | import BaiduMercatorProjection from './BaiduMercatorProjection'; 5 | 6 | class BaiduMercatorTilingScheme extends Cesium.WebMercatorTilingScheme { 7 | constructor(options) { 8 | super(options); 9 | const projection = new BaiduMercatorProjection(); 10 | this._projection.project = function(cartographic, result) { 11 | result = result || {}; 12 | result = CoordTransform.WGS84ToGCJ02( 13 | Cesium.Math.toDegrees(cartographic.longitude), 14 | Cesium.Math.toDegrees(cartographic.latitude), 15 | ); 16 | result = CoordTransform.GCJ02ToBD09(result[0], result[1]); 17 | result[0] = Math.min(result[0], 180); 18 | result[0] = Math.max(result[0], -180); 19 | result[1] = Math.min(result[1], 74.000022); 20 | result[1] = Math.max(result[1], -71.988531); 21 | result = projection.lngLatToPoint({ 22 | lng: result[0], 23 | lat: result[1], 24 | }); 25 | return new Cesium.Cartesian2(result.x, result.y); 26 | }; 27 | this._projection.unproject = function(cartesian, result) { 28 | result = result || {}; 29 | result = projection.mercatorToLngLat({ 30 | lng: cartesian.x, 31 | lat: cartesian.y, 32 | }); 33 | result = CoordTransform.BD09ToGCJ02(result.lng, result.lat); 34 | result = CoordTransform.GCJ02ToWGS84(result[0], result[1]); 35 | return new Cesium.Cartographic( 36 | Cesium.Math.toRadians(result[0]), 37 | Cesium.Math.toRadians(result[1]), 38 | ); 39 | }; 40 | this.resolutions = options.resolutions || []; 41 | } 42 | 43 | /** 44 | * 45 | * @param x 46 | * @param y 47 | * @param level 48 | * @param result 49 | * @returns {module:cesium.Rectangle|*} 50 | */ 51 | tileXYToNativeRectangle(x, y, level, result) { 52 | const tileWidth = this.resolutions[level]; 53 | const west = x * tileWidth; 54 | const east = (x + 1) * tileWidth; 55 | const north = ((y = -y) + 1) * tileWidth; 56 | const south = y * tileWidth; 57 | 58 | if (!Cesium.defined(result)) { 59 | return new Cesium.Rectangle(west, south, east, north); 60 | } 61 | 62 | result.west = west; 63 | result.south = south; 64 | result.east = east; 65 | result.north = north; 66 | return result; 67 | } 68 | 69 | /** 70 | * 71 | * @param position 72 | * @param level 73 | * @param result 74 | * @returns {undefined|*} 75 | */ 76 | positionToTileXY(position, level, result) { 77 | const rectangle = this._rectangle; 78 | if (!Cesium.Rectangle.contains(rectangle, position)) { 79 | return undefined; 80 | } 81 | const projection = this._projection; 82 | const webMercatorPosition = projection.project(position); 83 | if (!Cesium.defined(webMercatorPosition)) { 84 | return undefined; 85 | } 86 | const tileWidth = this.resolutions[level]; 87 | const xTileCoordinate = Math.floor(webMercatorPosition.x / tileWidth); 88 | const yTileCoordinate = -Math.floor(webMercatorPosition.y / tileWidth); 89 | if (!Cesium.defined(result)) { 90 | return new Cesium.Cartesian2(xTileCoordinate, yTileCoordinate); 91 | } 92 | result.x = xTileCoordinate; 93 | result.y = yTileCoordinate; 94 | return result; 95 | } 96 | } 97 | 98 | export default BaiduMercatorTilingScheme; 99 | -------------------------------------------------------------------------------- /packages/renderer/src/cesium-helper/BaiduImageryProvider/CoordTransform.js: -------------------------------------------------------------------------------- 1 | 2 | const BD_FACTOR = (3.14159265358979324 * 3000.0) / 180.0 3 | const PI = 3.1415926535897932384626 4 | const RADIUS = 6378245.0 5 | const EE = 0.00669342162296594323 6 | 7 | class CoordTransform { 8 | /** 9 | * BD-09 To GCJ-02 10 | * @param lng 11 | * @param lat 12 | * @returns {number[]} 13 | */ 14 | static BD09ToGCJ02(lng, lat) { 15 | const x = +lng - 0.0065 16 | const y = +lat - 0.006 17 | const z = Math.sqrt(x * x + y * y) - 0.00002 * Math.sin(y * BD_FACTOR) 18 | const theta = Math.atan2(y, x) - 0.000003 * Math.cos(x * BD_FACTOR) 19 | const gg_lng = z * Math.cos(theta) 20 | const gg_lat = z * Math.sin(theta) 21 | return [gg_lng, gg_lat] 22 | } 23 | 24 | /** 25 | * GCJ-02 To BD-09 26 | * @param lng 27 | * @param lat 28 | * @returns {number[]} 29 | * @constructor 30 | */ 31 | static GCJ02ToBD09(lng, lat) { 32 | lat = +lat 33 | lng = +lng 34 | const z = 35 | Math.sqrt(lng * lng + lat * lat) + 0.00002 * Math.sin(lat * BD_FACTOR) 36 | const theta = Math.atan2(lat, lng) + 0.000003 * Math.cos(lng * BD_FACTOR) 37 | const bd_lng = z * Math.cos(theta) + 0.0065 38 | const bd_lat = z * Math.sin(theta) + 0.006 39 | return [bd_lng, bd_lat] 40 | } 41 | 42 | /** 43 | * WGS-84 To GCJ-02 44 | * @param lng 45 | * @param lat 46 | * @returns {number[]} 47 | */ 48 | static WGS84ToGCJ02(lng, lat) { 49 | lat = +lat 50 | lng = +lng 51 | if (this.out_of_china(lng, lat)) { 52 | return [lng, lat] 53 | } else { 54 | const d = this.delta(lng, lat) 55 | return [lng + d[0], lat + d[1]] 56 | } 57 | } 58 | 59 | /** 60 | * GCJ-02 To WGS-84 61 | * @param lng 62 | * @param lat 63 | * @returns {number[]} 64 | * @constructor 65 | */ 66 | static GCJ02ToWGS84(lng, lat) { 67 | lat = +lat 68 | lng = +lng 69 | if (this.out_of_china(lng, lat)) { 70 | return [lng, lat] 71 | } else { 72 | const d = this.delta(lng, lat) 73 | const mgLng = lng + d[0] 74 | const mgLat = lat + d[1] 75 | return [lng * 2 - mgLng, lat * 2 - mgLat] 76 | } 77 | } 78 | 79 | /** 80 | * 81 | * @param lng 82 | * @param lat 83 | * @returns {number[]} 84 | */ 85 | static delta(lng, lat) { 86 | let dLng = this.transformLng(lng - 105, lat - 35) 87 | let dLat = this.transformLat(lng - 105, lat - 35) 88 | const radLat = (lat / 180) * PI 89 | let magic = Math.sin(radLat) 90 | magic = 1 - EE * magic * magic 91 | const sqrtMagic = Math.sqrt(magic) 92 | dLng = (dLng * 180) / ((RADIUS / sqrtMagic) * Math.cos(radLat) * PI) 93 | dLat = (dLat * 180) / (((RADIUS * (1 - EE)) / (magic * sqrtMagic)) * PI) 94 | return [dLng, dLat] 95 | } 96 | 97 | /** 98 | * 99 | * @param lng 100 | * @param lat 101 | * @returns {number} 102 | */ 103 | static transformLng(lng, lat) { 104 | lat = +lat 105 | lng = +lng 106 | let ret = 107 | 300.0 + 108 | lng + 109 | 2.0 * lat + 110 | 0.1 * lng * lng + 111 | 0.1 * lng * lat + 112 | 0.1 * Math.sqrt(Math.abs(lng)) 113 | ret += 114 | ((20.0 * Math.sin(6.0 * lng * PI) + 20.0 * Math.sin(2.0 * lng * PI)) * 115 | 2.0) / 116 | 3.0 117 | ret += 118 | ((20.0 * Math.sin(lng * PI) + 40.0 * Math.sin((lng / 3.0) * PI)) * 2.0) / 119 | 3.0 120 | ret += 121 | ((150.0 * Math.sin((lng / 12.0) * PI) + 122 | 300.0 * Math.sin((lng / 30.0) * PI)) * 123 | 2.0) / 124 | 3.0 125 | return ret 126 | } 127 | 128 | /** 129 | * 130 | * @param lng 131 | * @param lat 132 | * @returns {number} 133 | */ 134 | static transformLat(lng, lat) { 135 | lat = +lat 136 | lng = +lng 137 | let ret = 138 | -100.0 + 139 | 2.0 * lng + 140 | 3.0 * lat + 141 | 0.2 * lat * lat + 142 | 0.1 * lng * lat + 143 | 0.2 * Math.sqrt(Math.abs(lng)) 144 | ret += 145 | ((20.0 * Math.sin(6.0 * lng * PI) + 20.0 * Math.sin(2.0 * lng * PI)) * 146 | 2.0) / 147 | 3.0 148 | ret += 149 | ((20.0 * Math.sin(lat * PI) + 40.0 * Math.sin((lat / 3.0) * PI)) * 2.0) / 150 | 3.0 151 | ret += 152 | ((160.0 * Math.sin((lat / 12.0) * PI) + 153 | 320 * Math.sin((lat * PI) / 30.0)) * 154 | 2.0) / 155 | 3.0 156 | return ret 157 | } 158 | 159 | /** 160 | * 161 | * @param lng 162 | * @param lat 163 | * @returns {boolean} 164 | */ 165 | static out_of_china(lng, lat) { 166 | lat = +lat 167 | lng = +lng 168 | return !(lng > 73.66 && lng < 135.05 && lat > 3.86 && lat < 53.55) 169 | } 170 | } 171 | 172 | export default CoordTransform 173 | -------------------------------------------------------------------------------- /packages/renderer/src/cesium-helper/BaiduImageryProvider/ImageryType.js: -------------------------------------------------------------------------------- 1 | 2 | const ImageryType = { 3 | ARCGIS: 'arcgis', 4 | SINGLE_TILE: 'single_tile', 5 | WMS: 'wms', 6 | WMTS: 'wmts', 7 | XYZ: 'xyz', 8 | COORD: 'coord' 9 | } 10 | 11 | export default ImageryType 12 | -------------------------------------------------------------------------------- /packages/renderer/src/components/About.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 29 | 30 | 37 | -------------------------------------------------------------------------------- /packages/renderer/src/components/AreaChoose.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 74 | 75 | -------------------------------------------------------------------------------- /packages/renderer/src/components/GridIcon.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 45 | -------------------------------------------------------------------------------- /packages/renderer/src/components/Help.vue: -------------------------------------------------------------------------------- 1 | 39 | 40 | 150 | 151 | 206 | -------------------------------------------------------------------------------- /packages/renderer/src/components/Home.vue: -------------------------------------------------------------------------------- 1 | 78 | 79 | 236 | 237 | 238 | 281 | -------------------------------------------------------------------------------- /packages/renderer/src/components/LayerControl.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 81 | 82 | -------------------------------------------------------------------------------- /packages/renderer/src/components/MapKey.vue: -------------------------------------------------------------------------------- 1 | 44 | 45 | 97 | 98 | 117 | -------------------------------------------------------------------------------- /packages/renderer/src/components/ProgressControl.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 55 | 56 | 75 | -------------------------------------------------------------------------------- /packages/renderer/src/components/Save.vue: -------------------------------------------------------------------------------- 1 | 114 | 115 | 240 | 241 | 275 | -------------------------------------------------------------------------------- /packages/renderer/src/components/Tips.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 50 | 51 | 81 | -------------------------------------------------------------------------------- /packages/renderer/src/geojson/city/150300-乌海市.json: -------------------------------------------------------------------------------- 1 | {"type":"FeatureCollection","features":[{"type":"Feature","properties":{"adcode":150300,"name":"乌海市","center":[106.825563,39.673734],"centroid":[106.874373,39.426964],"childrenNum":3,"level":"city","acroutes":[100000,150000],"parent":{"adcode":150000}},"geometry":{"type":"MultiPolygon","coordinates":[[[[106.952416,39.507484],[106.949332,39.540695],[106.942564,39.552431],[106.940632,39.564965],[106.945114,39.577619],[106.940896,39.584384],[106.923775,39.633131],[106.912994,39.638786],[106.90508,39.65055],[106.902878,39.663416],[106.897324,39.668113],[106.889506,39.670278],[106.887795,39.677708],[106.89025,39.679268],[106.893861,39.67904],[106.900291,39.675141],[106.904256,39.674808],[106.911009,39.680231],[106.91292,39.682343],[106.909245,39.685751],[106.902488,39.688598],[106.896791,39.690017],[106.89818,39.694047],[106.900751,39.697078],[106.912096,39.701387],[106.915581,39.704076],[106.916272,39.708455],[106.90726,39.721924],[106.900281,39.755111],[106.883577,39.757421],[106.883471,39.766584],[106.881486,39.790259],[106.875795,39.795026],[106.875737,39.796802],[106.87807,39.80567],[106.891237,39.819373],[106.907862,39.833913],[106.923838,39.839796],[106.921858,39.85025],[106.930738,39.856804],[106.952743,39.857897],[106.966248,39.859531],[106.967906,39.862048],[106.965055,39.869012],[106.964379,39.895237],[106.968423,39.906477],[106.967811,39.908302],[106.963466,39.911323],[106.951846,39.912406],[106.947046,39.911708],[106.941931,39.91321],[106.93323,39.915113],[106.921985,39.905962],[106.898634,39.881121],[106.867965,39.862773],[106.86352,39.843109],[106.84009,39.836369],[106.834478,39.833624],[106.825455,39.830521],[106.822847,39.829288],[106.801528,39.822285],[106.796708,39.819303],[106.787105,39.814074],[106.778278,39.812247],[106.769102,39.866277],[106.749811,39.858421],[106.753945,39.851001],[106.758078,39.83247],[106.758311,39.821603],[106.75915,39.80553],[106.758997,39.795498],[106.758606,39.792874],[106.752799,39.78906],[106.752308,39.786663],[106.754626,39.782849],[106.760924,39.77606],[106.763822,39.771921],[106.766467,39.764387],[106.767365,39.753439],[106.767407,39.747986],[106.766383,39.736869],[106.76481,39.730382],[106.760079,39.719446],[106.759631,39.717257],[106.759984,39.711477],[106.762487,39.707308],[106.767587,39.703603],[106.77663,39.699732],[106.787316,39.692742],[106.794354,39.68986],[106.796017,39.68717],[106.796296,39.684016],[106.794259,39.680039],[106.789998,39.675684],[106.786017,39.672538],[106.776847,39.666615],[106.768975,39.663381],[106.765448,39.658561],[106.764646,39.655134],[106.765359,39.650874],[106.768416,39.640863],[106.769751,39.633823],[106.738819,39.619812],[106.639612,39.574188],[106.637279,39.571582],[106.638446,39.566852],[106.626841,39.559594],[106.625073,39.55718],[106.619962,39.552976],[106.617465,39.548718],[106.611372,39.541195],[106.611473,39.530115],[106.62012,39.499604],[106.626746,39.492049],[106.631144,39.490222],[106.63948,39.481243],[106.644586,39.477983],[106.647938,39.47706],[106.658275,39.47532],[106.662504,39.471427],[106.665862,39.466558],[106.666759,39.462568],[106.668824,39.459677],[106.678047,39.457312],[106.69029,39.453172],[106.708351,39.445972],[106.711619,39.445559],[106.719316,39.441436],[106.727019,39.441128],[106.733851,39.43922],[106.743058,39.437436],[106.752883,39.431448],[106.752625,39.428423],[106.750175,39.424668],[106.744119,39.422056],[106.740308,39.419866],[106.739378,39.412769],[106.741928,39.408556],[106.743454,39.403155],[106.74631,39.395466],[106.749072,39.391393],[106.751674,39.386131],[106.752936,39.381855],[106.751305,39.381574],[106.753095,39.376479],[106.755856,39.373469],[106.761457,39.372466],[106.772328,39.372967],[106.781514,39.371867],[106.787195,39.36693],[106.790954,39.361658],[106.793208,39.356641],[106.798049,39.348102],[106.797965,39.345848],[106.794127,39.340838],[106.793292,39.335899],[106.799554,39.326776],[106.806813,39.318656],[106.807146,39.315398],[106.80222,39.30311],[106.802389,39.298591],[106.801887,39.293913],[106.802806,39.289393],[106.804976,39.284538],[106.806475,39.277347],[106.803888,39.270156],[106.793451,39.253276],[106.79155,39.246779],[106.791054,39.24148],[106.79637,39.237556],[106.799295,39.232231],[106.798535,39.226913],[106.796149,39.220731],[106.795932,39.214319],[106.802114,39.20855],[106.814363,39.20234],[106.825524,39.193747],[106.829747,39.181358],[106.833913,39.164466],[106.843078,39.147799],[106.84774,39.137875],[106.850268,39.128179],[106.853668,39.116539],[106.859006,39.107776],[106.870172,39.096901],[106.878957,39.091352],[106.881449,39.090239],[106.897604,39.085786],[106.903021,39.083859],[106.905919,39.080837],[106.910338,39.079688],[106.913738,39.080386],[106.917545,39.079326],[106.920369,39.07952],[106.930728,39.0758],[106.932602,39.076445],[106.936287,39.07435],[106.938172,39.071999],[106.941112,39.071168],[106.943219,39.067881],[106.943604,39.064805],[106.945273,39.061914],[106.947495,39.061127],[106.949232,39.061852],[106.954469,39.061251],[106.959084,39.055664],[106.961697,39.054506],[106.966633,39.054966],[106.971052,39.054091],[106.975001,39.051527],[106.977409,39.049184],[106.980328,39.047743],[106.98339,39.044684],[106.987271,39.042438],[106.986991,39.040086],[106.989467,39.037248],[106.993068,39.036399],[106.997333,39.040183],[107.000174,39.040104],[107.002312,39.041138],[107.006736,39.040997],[107.011804,39.039343],[107.020072,39.041978],[107.02116,39.041784],[107.022643,39.038362],[107.027199,39.03677],[107.035293,39.037142],[107.038561,39.039839],[107.039886,39.044171],[107.043951,39.048008],[107.046818,39.047274],[107.049806,39.047628],[107.050598,39.048795],[107.046285,39.051394],[107.045926,39.052782],[107.043217,39.054427],[107.04356,39.058272],[107.04497,39.059439],[107.052726,39.058016],[107.054964,39.058113],[107.05706,39.06035],[107.060085,39.061755],[107.062635,39.060783],[107.064472,39.058042],[107.067809,39.05692],[107.069947,39.052712],[107.070243,39.047053],[107.073791,39.045108],[107.077692,39.04578],[107.091799,39.082763],[107.088959,39.113271],[107.103488,39.13716],[107.119463,39.209414],[107.139314,39.227363],[107.135107,39.232707],[107.12696,39.23835],[107.12733,39.250526],[107.127895,39.255127],[107.138728,39.268305],[107.136543,39.279982],[107.059705,39.222953],[107.052235,39.238438],[107.020215,39.25756],[106.993194,39.269468],[106.943029,39.299278],[106.942707,39.313213],[106.958767,39.340521],[106.960704,39.379453],[106.964965,39.449946],[106.963745,39.451959],[106.956349,39.482183],[106.953477,39.495976],[106.952416,39.507484]]]]}}]} 2 | -------------------------------------------------------------------------------- /packages/renderer/src/geojson/city/442000-中山市.json: -------------------------------------------------------------------------------- 1 | {"type":"FeatureCollection","features":[{"type":"Feature","properties":{"adcode":442000,"name":"中山市","center":[113.382391,22.521113],"centroid":[113.398784,22.517323],"childrenNum":0,"level":"city","acroutes":[100000,440000],"parent":{"adcode":440000}},"geometry":{"type":"MultiPolygon","coordinates":[[[[113.568749,22.411936],[113.573725,22.411718],[113.610107,22.445707],[113.631482,22.475966],[113.668426,22.480425],[113.692042,22.51515],[113.682342,22.514362],[113.662005,22.51479],[113.65161,22.515716],[113.63932,22.548274],[113.620781,22.579533],[113.615013,22.58529],[113.609797,22.588232],[113.59962,22.594394],[113.594666,22.594394],[113.589979,22.5952],[113.587422,22.596827],[113.580974,22.602099],[113.578556,22.604617],[113.577861,22.603915],[113.574715,22.605714],[113.571916,22.602807],[113.567395,22.605931],[113.565143,22.603475],[113.561633,22.607513],[113.558022,22.613731],[113.555641,22.617123],[113.551788,22.623764],[113.544742,22.63519],[113.541307,22.640334],[113.536877,22.647527],[113.532971,22.654983],[113.533089,22.656371],[113.53441,22.656536],[113.539509,22.65794],[113.540724,22.666206],[113.523373,22.6793],[113.507509,22.68958],[113.491907,22.69979],[113.482159,22.707146],[113.471314,22.714998],[113.464813,22.720961],[113.467975,22.728521],[113.449538,22.735094],[113.447783,22.735853],[113.441122,22.737222],[113.435531,22.737615],[113.426141,22.738003],[113.417886,22.740405],[113.412193,22.742824],[113.404274,22.747246],[113.39107,22.755706],[113.381455,22.761564],[113.372969,22.76748],[113.365153,22.772602],[113.361033,22.768569],[113.358738,22.764873],[113.355629,22.763629],[113.349738,22.761724],[113.345993,22.760138],[113.34396,22.758706],[113.341804,22.757685],[113.339792,22.755478],[113.336566,22.753515],[113.332505,22.751832],[113.330295,22.750509],[113.329348,22.748997],[113.328262,22.746304],[113.328824,22.745677],[113.329118,22.741261],[113.321023,22.739133],[113.313083,22.737638],[113.306176,22.736531],[113.301435,22.736269],[113.295941,22.737222],[113.291238,22.738956],[113.284876,22.738876],[113.28127,22.737547],[113.278279,22.735139],[113.274422,22.730672],[113.270564,22.726478],[113.266541,22.725468],[113.263919,22.726672],[113.260891,22.73033],[113.257884,22.73579],[113.254074,22.740839],[113.248066,22.744855],[113.243031,22.745352],[113.23912,22.743714],[113.236316,22.740776],[113.234385,22.734004],[113.231763,22.728196],[113.22812,22.721874],[113.222657,22.713434],[113.222234,22.712259],[113.217451,22.706381],[113.209549,22.698745],[113.204236,22.697324],[113.204974,22.694054],[113.204974,22.69176],[113.204375,22.689477],[113.201652,22.686555],[113.19926,22.682542],[113.199833,22.677879],[113.201042,22.675956],[113.197082,22.675773],[113.194509,22.675408],[113.189383,22.673884],[113.18659,22.675288],[113.184825,22.676926],[113.183075,22.677588],[113.180801,22.679084],[113.179667,22.678821],[113.177094,22.681287],[113.174151,22.684894],[113.173177,22.684506],[113.175831,22.681264],[113.17651,22.679106],[113.175793,22.679163],[113.173819,22.680687],[113.17315,22.679768],[113.172,22.68067],[113.167902,22.675996],[113.166323,22.674934],[113.165467,22.675716],[113.164082,22.675585],[113.161401,22.673701],[113.164649,22.670213],[113.166099,22.667959],[113.163568,22.667314],[113.162669,22.665778],[113.160839,22.665407],[113.161851,22.663495],[113.163648,22.663786],[113.164488,22.662233],[113.166168,22.658083],[113.169988,22.658694],[113.17063,22.651672],[113.169667,22.650764],[113.163161,22.651398],[113.16276,22.650907],[113.16008,22.644319],[113.157222,22.632055],[113.157046,22.62357],[113.158373,22.6181],[113.157014,22.614245],[113.16223,22.609654],[113.165558,22.601916],[113.176398,22.590008],[113.185103,22.574289],[113.18712,22.565001],[113.188725,22.552781],[113.187109,22.539932],[113.19224,22.53551],[113.201812,22.524785],[113.209399,22.519482],[113.215381,22.513099],[113.220051,22.504978],[113.225562,22.496451],[113.231282,22.489353],[113.237055,22.480979],[113.240153,22.476155],[113.242946,22.463361],[113.245161,22.457273],[113.248692,22.453328],[113.251372,22.449869],[113.25355,22.446296],[113.255867,22.44063],[113.256461,22.437771],[113.256129,22.427221],[113.256407,22.420411],[113.256343,22.410809],[113.257391,22.40688],[113.259227,22.401521],[113.261078,22.396969],[113.26264,22.392073],[113.273828,22.38478],[113.276433,22.381588],[113.280965,22.376835],[113.307155,22.346486],[113.313854,22.340032],[113.317358,22.335729],[113.320386,22.329583],[113.321034,22.321189],[113.321296,22.312181],[113.322505,22.308667],[113.329814,22.293071],[113.336603,22.280129],[113.33884,22.274124],[113.341071,22.267696],[113.342874,22.263242],[113.345137,22.258559],[113.34931,22.252627],[113.352403,22.249129],[113.356111,22.246278],[113.363248,22.241829],[113.372445,22.236698],[113.373991,22.236527],[113.375966,22.235673],[113.376929,22.234454],[113.379491,22.235908],[113.383028,22.233618],[113.385286,22.230371],[113.38748,22.230554],[113.389588,22.232198],[113.390363,22.232295],[113.394109,22.228968],[113.400454,22.220184],[113.408528,22.210792],[113.415761,22.202894],[113.420368,22.202058],[113.424622,22.201038],[113.430555,22.201284],[113.432594,22.203976],[113.440041,22.206107],[113.442561,22.207046],[113.456007,22.212567],[113.458355,22.213793],[113.46141,22.217114],[113.462657,22.218872],[113.463529,22.219382],[113.468221,22.2211],[113.477777,22.224977],[113.485107,22.226563],[113.486354,22.227101],[113.486145,22.229134],[113.482544,22.231379],[113.480035,22.233641],[113.479222,22.235805],[113.478879,22.238319],[113.480822,22.238651],[113.482518,22.240306],[113.484128,22.240781],[113.486284,22.242029],[113.492148,22.242877],[113.492421,22.243621],[113.493935,22.244113],[113.494904,22.243753],[113.506749,22.246724],[113.509382,22.251373],[113.512768,22.250005],[113.51317,22.250795],[113.514341,22.258284],[113.514384,22.261541],[113.513737,22.261684],[113.51226,22.264862],[113.509023,22.269379],[113.508258,22.269711],[113.506551,22.268732],[113.504727,22.266986],[113.499644,22.272928],[113.501254,22.27421],[113.499478,22.275006],[113.49882,22.27468],[113.495069,22.279969],[113.496386,22.282253],[113.495503,22.283649],[113.493748,22.284955],[113.494133,22.285355],[113.492159,22.287502],[113.49286,22.288223],[113.491388,22.290278],[113.486685,22.295441],[113.487932,22.296293],[113.485904,22.298205],[113.482919,22.301496],[113.48232,22.300987],[113.48042,22.30386],[113.479393,22.303007],[113.4779,22.303396],[113.474658,22.305353],[113.475787,22.306641],[113.474412,22.307213],[113.470479,22.310355],[113.475006,22.314928],[113.477793,22.313635],[113.478927,22.31447],[113.48202,22.314985],[113.483994,22.317028],[113.48302,22.318774],[113.484631,22.31934],[113.486862,22.319323],[113.487643,22.318596],[113.488858,22.318608],[113.490292,22.317847],[113.493357,22.317995],[113.503132,22.322848],[113.504101,22.324307],[113.50393,22.325532],[113.501559,22.327621],[113.500735,22.3292],[113.501147,22.330528],[113.500741,22.331815],[113.499612,22.332736],[113.496343,22.333698],[113.495893,22.334739],[113.496113,22.338143],[113.494781,22.339711],[113.49194,22.340015],[113.490334,22.340724],[113.489639,22.34327],[113.491554,22.345914],[113.490634,22.348414],[113.489666,22.34973],[113.490516,22.351406],[113.49324,22.354044],[113.492442,22.360823],[113.489168,22.365817],[113.489227,22.367465],[113.491303,22.370422],[113.49111,22.373695],[113.49134,22.376944],[113.488034,22.380027],[113.486675,22.382286],[113.485878,22.385015],[113.486113,22.386965],[113.487006,22.390197],[113.489559,22.39253],[113.493908,22.394424],[113.495235,22.395413],[113.495337,22.398639],[113.495,22.405656],[113.495893,22.407549],[113.499125,22.408527],[113.503721,22.410672],[113.504775,22.411976],[113.506947,22.413291],[113.508921,22.413377],[113.511324,22.41185],[113.514325,22.408716],[113.516209,22.408156],[113.517707,22.408344],[113.520607,22.413028],[113.52165,22.413486],[113.523651,22.413371],[113.524587,22.412433],[113.526203,22.411804],[113.527974,22.412359],[113.52898,22.414361],[113.530414,22.415453],[113.5341,22.416396],[113.537198,22.416345],[113.54464,22.414429],[113.552532,22.412628],[113.566561,22.412582],[113.568749,22.411936]]]]}}]} 2 | -------------------------------------------------------------------------------- /packages/renderer/src/geojson/city/620200-嘉峪关市.json: -------------------------------------------------------------------------------- 1 | {"type":"FeatureCollection","features":[{"type":"Feature","properties":{"adcode":620200,"name":"嘉峪关市","center":[98.277304,39.786529],"centroid":[98.211266,39.829379],"childrenNum":0,"level":"city","acroutes":[100000,620000],"parent":{"adcode":620000}},"geometry":{"type":"MultiPolygon","coordinates":[[[[97.862609,39.714956],[97.856448,39.711194],[97.855601,39.710457],[97.856629,39.709962],[97.857516,39.709716],[97.865424,39.709394],[97.879251,39.709318],[97.880514,39.709117],[97.882093,39.708653],[97.884149,39.706836],[97.887112,39.705957],[97.88767,39.705102],[97.893059,39.704538],[97.905166,39.701492],[97.907638,39.701087],[97.909318,39.701277],[97.912342,39.701405],[97.916212,39.700007],[97.919255,39.699647],[97.92117,39.699041],[97.922258,39.698847],[97.924019,39.698864],[97.927849,39.699321],[97.931235,39.699079],[97.934823,39.697715],[97.937759,39.696725],[97.94005,39.695763],[97.941125,39.694911],[97.941877,39.693834],[97.943497,39.6912],[97.946775,39.687976],[97.950336,39.683461],[97.954428,39.677443],[97.956222,39.674611],[97.959884,39.670348],[97.968383,39.662909],[97.969243,39.662808],[97.970399,39.663386],[97.979577,39.668201],[98.026723,39.693308],[98.034281,39.69714],[98.035941,39.698293],[98.036673,39.698477],[98.03838,39.698594],[98.157545,39.696929],[98.157894,39.697172],[98.158754,39.699256],[98.160924,39.705732],[98.166279,39.713073],[98.176115,39.721767],[98.188236,39.725923],[98.203669,39.727362],[98.217591,39.727435],[98.224914,39.725421],[98.238788,39.718978],[98.240603,39.718503],[98.241711,39.718742],[98.244036,39.719652],[98.245232,39.719625],[98.246287,39.718929],[98.246408,39.717995],[98.245957,39.71723],[98.24454,39.715261],[98.2429,39.712648],[98.242652,39.710298],[98.243424,39.70793],[98.245514,39.706272],[98.253577,39.703254],[98.255908,39.702108],[98.260544,39.69669],[98.263796,39.694412],[98.269211,39.692778],[98.273155,39.691923],[98.278047,39.691511],[98.281077,39.69093],[98.282998,39.689773],[98.286761,39.688153],[98.289348,39.686543],[98.29266,39.683732],[98.295522,39.681138],[98.298774,39.679012],[98.3012,39.676824],[98.305029,39.673887],[98.315148,39.668762],[98.317943,39.666628],[98.320832,39.665298],[98.326758,39.664183],[98.328847,39.664509],[98.332341,39.666569],[98.334061,39.667473],[98.335546,39.667529],[98.338946,39.666881],[98.344872,39.665257],[98.351389,39.663792],[98.356495,39.662344],[98.360977,39.661762],[98.362858,39.66082],[98.365579,39.658749],[98.367004,39.657166],[98.3691,39.655243],[98.370719,39.654263],[98.373413,39.656019],[98.376477,39.658527],[98.379991,39.661766],[98.381792,39.663812],[98.382705,39.665814],[98.383102,39.668162],[98.384237,39.670843],[98.387039,39.676952],[98.387738,39.679459],[98.387986,39.682433],[98.388618,39.685577],[98.388887,39.686443],[98.39062,39.689292],[98.391232,39.690698],[98.393946,39.693339],[98.397547,39.696642],[98.399939,39.698317],[98.402889,39.700173],[98.405052,39.701758],[98.408996,39.706213],[98.413323,39.7091],[98.413894,39.710367],[98.413155,39.717033],[98.412638,39.717995],[98.412087,39.718074],[98.410615,39.717912],[98.409493,39.717974],[98.408916,39.718223],[98.4086,39.718701],[98.407874,39.722494],[98.407605,39.728708],[98.407404,39.729892],[98.40694,39.730992],[98.404911,39.732809],[98.402627,39.734642],[98.394403,39.740569],[98.392804,39.741583],[98.390237,39.742821],[98.384009,39.744658],[98.381604,39.745239],[98.377021,39.746921],[98.369557,39.750712],[98.365472,39.753535],[98.361064,39.756198],[98.360211,39.757007],[98.35921,39.758616],[98.356475,39.764104],[98.355884,39.765626],[98.354399,39.770803],[98.353075,39.774908],[98.35276,39.776855],[98.353129,39.777888],[98.35411,39.77885],[98.355608,39.780025],[98.366271,39.784997],[98.390533,39.795472],[98.402338,39.800671],[98.413464,39.80523],[98.433721,39.814102],[98.440837,39.816884],[98.456317,39.823402],[98.466744,39.829853],[98.46876,39.831249],[98.471461,39.832568],[98.472643,39.833066],[98.474061,39.833494],[98.475687,39.833639],[98.478952,39.834275],[98.480975,39.834939],[98.485712,39.837295],[98.49018,39.839917],[98.491066,39.840736],[98.492363,39.842739],[98.49276,39.84437],[98.492632,39.845206],[98.491476,39.84714],[98.489346,39.850211],[98.488802,39.851299],[98.487721,39.854342],[98.487116,39.855012],[98.486034,39.855751],[98.485261,39.85648],[98.484912,39.857371],[98.484811,39.858649],[98.486289,39.860141],[98.487069,39.861287],[98.486189,39.862534],[98.484771,39.863532],[98.484576,39.864409],[98.486719,39.86558],[98.488144,39.86664],[98.489548,39.868018],[98.496509,39.873505],[98.508972,39.884163],[98.518842,39.892942],[98.519225,39.893422],[98.518997,39.893871],[98.514387,39.898089],[98.514005,39.898631],[98.514455,39.899311],[98.517975,39.903656],[98.519836,39.90718],[98.520179,39.908688],[98.520011,39.909427],[98.51897,39.910235],[98.514609,39.911287],[98.513071,39.911881],[98.51174,39.912523],[98.509832,39.913123],[98.50654,39.913755],[98.497933,39.915784],[98.497248,39.916132],[98.496227,39.917802],[98.495219,39.919852],[98.49415,39.924017],[98.493949,39.928626],[98.494621,39.932428],[98.495602,39.934616],[98.49663,39.93583],[98.497349,39.937541],[98.497503,39.938335],[98.497456,39.939942],[98.497006,39.941853],[98.495259,39.945437],[98.495024,39.946203],[98.495306,39.947186],[98.495696,39.947838],[98.496643,39.948676],[98.497214,39.949845],[98.496939,39.950262],[98.495252,39.951183],[98.492068,39.953439],[98.48342,39.961064],[98.481962,39.96255],[98.480195,39.965051],[98.477239,39.967557],[98.47482,39.969285],[98.473322,39.96985],[98.471555,39.970181],[98.469741,39.970364],[98.468189,39.970416],[98.466939,39.96973],[98.466724,39.969168],[98.466126,39.965964],[98.465159,39.965213],[98.464225,39.964695],[98.459051,39.962623],[98.455618,39.961123],[98.45152,39.959102],[98.448415,39.957436],[98.444122,39.955757],[98.442584,39.955336],[98.440514,39.954991],[98.437235,39.954853],[98.426223,39.955246],[98.421466,39.955274],[98.420667,39.955353],[98.416138,39.95754],[98.414553,39.958205],[98.412235,39.958964],[98.411227,39.95955],[98.408821,39.961781],[98.407014,39.963085],[98.406,39.963526],[98.404454,39.963761],[98.399644,39.96404],[98.399012,39.964168],[98.395431,39.965716],[98.393576,39.965913],[98.392609,39.965747],[98.391359,39.964902],[98.386172,39.962619],[98.381066,39.960971],[98.378372,39.959943],[98.375859,39.958616],[98.37426,39.958029],[98.37174,39.957498],[98.370282,39.957488],[98.368737,39.957785],[98.365929,39.958947],[98.364558,39.959343],[98.363617,39.959192],[98.362818,39.958205],[98.361864,39.957691],[98.360668,39.957457],[98.353667,39.957184],[98.351638,39.957305],[98.347176,39.957381],[98.342332,39.956812],[98.337407,39.956388],[98.329466,39.955853],[98.327289,39.956167],[98.324238,39.956857],[98.319139,39.958612],[98.316485,39.958857],[98.313334,39.958826],[98.308442,39.959167],[98.307401,39.95915],[98.303672,39.958723],[98.302355,39.959057],[98.301959,39.959912],[98.302194,39.960812],[98.303034,39.96235],[98.302792,39.96294],[98.302288,39.96304],[98.298875,39.962826],[98.296664,39.96284],[98.285014,39.963471],[98.240072,39.966402],[98.176539,39.970526],[98.161992,39.971588],[98.06295,39.999715],[98.061472,39.99967],[98.059262,39.998288],[98.058375,39.997478],[98.058093,39.99672],[98.0582,39.995331],[98.059322,39.994014],[98.0616,39.992428],[98.064146,39.989643],[98.068722,39.983176],[98.069636,39.980883],[98.069535,39.978863],[98.067526,39.973857],[98.067103,39.972288],[98.064563,39.967454],[98.064395,39.966037],[98.064899,39.964147],[98.067472,39.95905],[98.068554,39.956095],[98.068561,39.955612],[98.067734,39.954539],[98.066417,39.954046],[98.064543,39.953739],[98.063374,39.953729],[98.060747,39.954056],[98.055842,39.954222],[98.053799,39.954101],[98.050742,39.953494],[98.048686,39.952977],[98.047967,39.952453],[98.045065,39.949252],[98.044084,39.948769],[98.043278,39.948666],[98.04008,39.949017],[98.038689,39.948769],[98.0367,39.94782],[98.033797,39.946941],[98.032393,39.946617],[98.02554,39.946568],[98.024875,39.946513],[98.023679,39.945906],[98.023047,39.94482],[98.022134,39.941619],[98.022248,39.937669],[98.022033,39.937027],[98.021334,39.936375],[98.019654,39.935406],[98.01741,39.933436],[98.016067,39.932915],[98.014871,39.93278],[98.012438,39.932328],[98.008891,39.931335],[98.007796,39.9306],[98.007339,39.930017],[98.007238,39.929296],[98.007561,39.928561],[98.008548,39.927101],[98.00832,39.926222],[98.007581,39.925777],[98.005646,39.925338],[98.004322,39.924652],[98.003791,39.924006],[98.003731,39.923399],[98.004752,39.923002],[98.009267,39.92215],[98.014058,39.921495],[98.021213,39.921101],[98.026696,39.919545],[98.034301,39.917875],[98.036109,39.917109],[98.036492,39.916481],[98.036713,39.915625],[98.036189,39.914766],[98.032239,39.912864],[98.020058,39.908019],[98.010947,39.903864],[98.009019,39.902852],[98.006526,39.900529],[98.005491,39.899922],[97.99843,39.896173],[97.992833,39.893339],[97.990925,39.89191],[97.984623,39.884647],[97.983883,39.883549],[97.983816,39.882786],[98.014454,39.848619],[98.020346,39.842425],[98.029592,39.832347],[98.031762,39.829334],[98.031728,39.828522],[98.031197,39.828008],[98.015475,39.820102],[98.012848,39.819027],[98.000566,39.81328],[97.993653,39.809737],[97.992174,39.808666],[97.987149,39.801176],[97.979335,39.788786],[97.974316,39.780613],[97.971655,39.778888],[97.951472,39.767331],[97.932095,39.755914],[97.917885,39.747263],[97.912684,39.744579],[97.892474,39.732449],[97.872526,39.721012],[97.862609,39.714956]]],[[[97.85974,39.716898],[97.860311,39.717334],[97.860627,39.71796],[97.860298,39.71814],[97.848305,39.713887],[97.848439,39.713537],[97.853216,39.711114],[97.854076,39.711246],[97.856454,39.714039],[97.858269,39.715773],[97.85974,39.716898]]]]}}]} 2 | -------------------------------------------------------------------------------- /packages/renderer/src/geojson/city/640200-石嘴山市.json: -------------------------------------------------------------------------------- 1 | {"type":"FeatureCollection","features":[{"type":"Feature","properties":{"adcode":640200,"name":"石嘴山市","center":[106.376173,39.01333],"centroid":[106.517117,39.007544],"childrenNum":3,"level":"city","acroutes":[100000,640000],"parent":{"adcode":640000}},"geometry":{"type":"MultiPolygon","coordinates":[[[[106.092079,39.071325],[106.088559,39.066642],[106.083545,39.053939],[106.083324,39.049535],[106.083986,39.045014],[106.082953,39.041989],[106.080887,39.038902],[106.085741,39.036095],[106.083755,39.03392],[106.080225,39.031807],[106.078009,39.027394],[106.080436,39.02362],[106.084838,39.021211],[106.0888,39.017553],[106.089321,39.015838],[106.088429,39.009887],[106.086583,39.005535],[106.080366,38.998671],[106.076565,38.991744],[106.070838,38.990254],[106.068331,38.987563],[106.065754,38.98225],[106.062725,38.977896],[106.062926,38.972972],[106.060789,38.968509],[106.055274,38.965715],[106.051302,38.962742],[106.043069,38.963834],[106.037242,38.962492],[106.030954,38.958613],[106.023763,38.955163],[106.01896,38.951097],[106.017014,38.948732],[106.00854,38.935812],[106.005431,38.933181],[105.996004,38.927356],[105.992243,38.926075],[105.989997,38.923936],[105.980329,38.917181],[105.973269,38.911433],[105.971032,38.909059],[105.968876,38.905083],[105.97014,38.897546],[105.972547,38.89096],[105.976989,38.884218],[105.980128,38.880421],[105.986085,38.874998],[105.992534,38.875326],[105.999454,38.876311],[106.003315,38.87517],[106.009964,38.875639],[106.030453,38.870114],[106.034925,38.869606],[106.043069,38.869778],[106.050711,38.868465],[106.053128,38.867207],[106.062204,38.859033],[106.065212,38.856961],[106.070186,38.856516],[106.073035,38.854116],[106.079172,38.850732],[106.087817,38.847574],[106.094255,38.84304],[106.100243,38.841367],[106.10969,38.840374],[106.124973,38.839412],[106.126789,38.837403],[106.12721,38.832501],[106.128303,38.828897],[106.130931,38.824831],[106.135464,38.821546],[106.137901,38.81852],[106.146656,38.816369],[106.164176,38.81554],[106.174024,38.815384],[106.179269,38.814211],[106.185387,38.811208],[106.196218,38.810504],[106.196448,38.805264],[106.200751,38.803175],[106.20754,38.803261],[106.213618,38.806671],[106.223426,38.811435],[106.230305,38.814203],[106.23183,38.813914],[106.236052,38.809198],[106.239512,38.800547],[106.241989,38.798185],[106.247475,38.798114],[106.255377,38.800085],[106.258306,38.799796],[106.265235,38.796159],[106.270922,38.792646],[106.275806,38.788625],[106.281512,38.787131],[106.282204,38.780997],[106.283377,38.780026],[106.287991,38.780011],[106.292323,38.781145],[106.300477,38.786395],[106.304799,38.787232],[106.31213,38.787733],[106.314256,38.786184],[106.311438,38.782131],[106.310977,38.778696],[106.311618,38.776232],[106.315921,38.772444],[106.320454,38.771067],[106.331746,38.771654],[106.335346,38.775426],[106.347592,38.772843],[106.354211,38.770308],[106.372653,38.771881],[106.381489,38.775246],[106.382672,38.771333],[106.376645,38.761222],[106.37502,38.755008],[106.397735,38.752511],[106.396953,38.746264],[106.397033,38.726221],[106.400764,38.722705],[106.405036,38.720411],[106.407764,38.720591],[106.415607,38.718767],[106.418836,38.719299],[106.423439,38.722204],[106.427862,38.72056],[106.430409,38.718853],[106.43771,38.719526],[106.440648,38.714624],[106.440117,38.712799],[106.434822,38.706416],[106.434501,38.702296],[106.436125,38.694392],[106.438231,38.693577],[106.444068,38.695544],[106.45533,38.699868],[106.45885,38.703157],[106.461287,38.708006],[106.464356,38.709071],[106.467335,38.708531],[106.473091,38.708546],[106.480152,38.70752],[106.488114,38.70362],[106.495024,38.698332],[106.506577,38.697792],[106.512344,38.698097],[106.526685,38.69769],[106.532231,38.69534],[106.535009,38.693217],[106.539833,38.688462],[106.545729,38.686511],[106.553391,38.684842],[106.560241,38.684168],[106.565416,38.682844],[106.572135,38.6817],[106.58067,38.681927],[106.591761,38.682625],[106.596274,38.682562],[106.595863,38.668544],[106.592845,38.660471],[106.587108,38.65573],[106.604067,38.653566],[106.614537,38.650157],[106.633351,38.645234],[106.639558,38.642953],[106.642417,38.640742],[106.643048,38.638845],[106.641634,38.619987],[106.649687,38.613964],[106.662524,38.601478],[106.70284,38.708249],[106.709579,38.718892],[106.756022,38.748526],[106.758239,38.752409],[106.76232,38.75709],[106.767505,38.764932],[106.775398,38.774768],[106.778176,38.777225],[106.780733,38.781114],[106.785697,38.786411],[106.787011,38.789243],[106.792998,38.797989],[106.797461,38.803644],[106.799577,38.805475],[106.802495,38.810934],[106.82139,38.832173],[106.837375,38.847456],[106.878363,38.881929],[106.904728,38.901123],[106.954321,38.941175],[106.959766,38.948825],[106.96468,38.976257],[106.97138,39.016984],[106.971741,39.026225],[106.967288,39.054773],[106.961702,39.054508],[106.959084,39.055669],[106.954471,39.061257],[106.949236,39.061849],[106.946578,39.061163],[106.944472,39.062761],[106.943319,39.067507],[106.941113,39.071162],[106.938174,39.071996],[106.936289,39.074349],[106.932598,39.076437],[106.930723,39.075806],[106.920955,39.07932],[106.917545,39.079328],[106.913744,39.080387],[106.910335,39.079694],[106.905922,39.080839],[106.903024,39.083854],[106.897598,39.085786],[106.881452,39.090235],[106.878955,39.091349],[106.870169,39.096903],[106.867893,39.099123],[106.859007,39.107776],[106.853672,39.116538],[106.850262,39.128186],[106.847735,39.137879],[106.843072,39.147797],[106.833916,39.164468],[106.829744,39.181353],[106.825521,39.193746],[106.814359,39.202341],[106.802114,39.208555],[106.795937,39.214318],[106.796147,39.220725],[106.798534,39.226914],[106.799296,39.232232],[106.796368,39.237558],[106.791053,39.241476],[106.791554,39.246777],[106.79345,39.253283],[106.803889,39.270162],[106.806477,39.277349],[106.804973,39.284543],[106.802806,39.289391],[106.801884,39.293912],[106.802385,39.298595],[106.802225,39.303108],[106.807149,39.315394],[106.806818,39.318655],[106.799557,39.326777],[106.793289,39.335899],[106.794121,39.340836],[106.797962,39.345843],[106.798053,39.348101],[106.793209,39.356639],[106.790952,39.36166],[106.787192,39.366929],[106.781515,39.371871],[106.772329,39.372965],[106.761458,39.372469],[106.755852,39.37347],[106.753094,39.37648],[106.751309,39.38157],[106.742293,39.379428],[106.736546,39.375386],[106.733347,39.373927],[106.72748,39.372857],[106.716088,39.374207],[106.692701,39.364717],[106.683735,39.357578],[106.680777,39.355901],[106.675712,39.35504],[106.66449,39.35518],[106.64373,39.357795],[106.638887,39.360115],[106.631295,39.366036],[106.602613,39.375332],[106.59824,39.371235],[106.596605,39.368271],[106.58764,39.357399],[106.584651,39.352611],[106.582254,39.347713],[106.57725,39.344112],[106.573319,39.342404],[106.567873,39.335922],[106.565627,39.331156],[106.56333,39.328772],[106.558957,39.326893],[106.558336,39.32489],[106.555969,39.322281],[106.548096,39.32003],[106.545669,39.316504],[106.541337,39.316341],[106.538088,39.313678],[106.535751,39.310152],[106.533514,39.308358],[106.525361,39.308443],[106.526775,39.30491],[106.525351,39.302712],[106.521701,39.301539],[106.523245,39.295706],[106.521129,39.29489],[106.519715,39.292809],[106.519715,39.286346],[106.515523,39.283611],[106.515031,39.276339],[106.511682,39.272291],[106.508112,39.270504],[106.504933,39.270302],[106.498534,39.27184],[106.484815,39.273954],[106.472139,39.277248],[106.470033,39.279144],[106.461448,39.28264],[106.454408,39.283774],[106.449042,39.283743],[106.442002,39.284761],[106.442153,39.28567],[106.436867,39.287301],[106.421303,39.289165],[106.410682,39.289569],[106.40292,39.291659],[106.397685,39.290136],[106.391197,39.28995],[106.380075,39.288031],[106.374158,39.288326],[106.360699,39.285631],[106.35398,39.283836],[106.342577,39.281436],[106.337352,39.279214],[106.327464,39.275935],[106.32271,39.276479],[106.31568,39.275127],[106.306805,39.275546],[106.30179,39.275407],[106.295663,39.273845],[106.290949,39.273705],[106.287178,39.272213],[106.284601,39.270309],[106.281001,39.265375],[106.279978,39.262135],[106.28074,39.258482],[106.280991,39.252979],[106.282174,39.246583],[106.285624,39.235894],[106.287028,39.224675],[106.289024,39.219683],[106.288452,39.213011],[106.286366,39.200777],[106.286306,39.195721],[106.28789,39.187048],[106.293105,39.176405],[106.295552,39.167978],[106.293737,39.162647],[106.28769,39.153284],[106.286456,39.148194],[106.285072,39.146318],[106.281643,39.14392],[106.277029,39.141717],[106.268585,39.139865],[106.263952,39.138129],[106.260813,39.135964],[106.253923,39.132329],[106.251025,39.13155],[106.246281,39.132562],[106.243232,39.134773],[106.239331,39.136502],[106.231058,39.13742],[106.21811,39.137981],[106.211903,39.138954],[106.207139,39.140433],[106.198584,39.141351],[106.189639,39.144317],[106.181335,39.152132],[106.180342,39.154631],[106.177213,39.158678],[106.170825,39.163324],[106.168518,39.162569],[106.157176,39.160195],[106.146074,39.153222],[106.143005,39.149922],[106.138613,39.143368],[106.132485,39.136533],[106.126458,39.127634],[106.12378,39.122277],[106.118515,39.115922],[106.112789,39.100829],[106.108897,39.096116],[106.105197,39.093709],[106.098447,39.087532],[106.096522,39.084727],[106.095519,39.081252],[106.094596,39.074138],[106.092079,39.071325]]]]}}]} 2 | -------------------------------------------------------------------------------- /packages/renderer/src/geojson/province/710000-台湾省.json: -------------------------------------------------------------------------------- 1 | {"type":"FeatureCollection","features":[{"type":"Feature","properties":{"adcode":710000,"name":"台湾省","center":[121.509062,25.044332],"centroid":[120.971485,23.749452],"childrenNum":0,"level":"province","acroutes":[100000],"parent":{"adcode":100000}},"geometry":{"type":"MultiPolygon","coordinates":[[[[120.443558,22.441245],[120.517584,22.408536],[120.569903,22.361728],[120.640505,22.241347],[120.659209,22.15432],[120.662001,22.066983],[120.651464,22.033165],[120.667691,21.983168],[120.70157,21.927065],[120.743246,21.915569],[120.78155,21.923957],[120.85468,21.883333],[120.87291,21.897387],[120.866482,21.98436],[120.907315,22.033208],[120.904154,22.119757],[120.914955,22.302718],[120.981658,22.528305],[121.015009,22.584168],[121.033292,22.650725],[121.078498,22.669656],[121.170544,22.723133],[121.210481,22.770665],[121.237931,22.836327],[121.324708,22.945666],[121.354687,23.01006],[121.370388,23.084347],[121.409535,23.102669],[121.430294,23.137196],[121.415015,23.195973],[121.440358,23.272096],[121.479558,23.3223],[121.497788,23.419789],[121.521497,23.483198],[121.523078,23.538708],[121.587778,23.76102],[121.621604,23.92075],[121.659381,24.006893],[121.639992,24.064276],[121.643838,24.097713],[121.678085,24.133906],[121.689044,24.174401],[121.809172,24.339055],[121.826717,24.423579],[121.867498,24.478978],[121.885464,24.529677],[121.892524,24.617912],[121.862598,24.671515],[121.837993,24.76015],[121.845053,24.836269],[121.932883,24.938645],[122.012178,25.001469],[121.980776,25.03079],[121.947425,25.031955],[121.917077,25.137908],[121.842155,25.135332],[121.782407,25.160425],[121.750531,25.160716],[121.707327,25.191493],[121.700319,25.226913],[121.655324,25.241859],[121.623026,25.294694],[121.584986,25.308926],[121.535038,25.307515],[121.444415,25.270624],[121.413487,25.238912],[121.371864,25.159885],[121.319281,25.140691],[121.209322,25.127104],[121.133135,25.078728],[121.102102,25.075153],[121.024704,25.040479],[121.009688,24.993649],[120.960899,24.940227],[120.908475,24.852012],[120.892299,24.767526],[120.823753,24.688321],[120.762371,24.658335],[120.688661,24.600678],[120.64277,24.490172],[120.589187,24.432354],[120.546299,24.370413],[120.521009,24.312038],[120.470534,24.24259],[120.451461,24.182691],[120.392029,24.11824],[120.316158,23.984881],[120.278276,23.927798],[120.245768,23.840553],[120.175377,23.807385],[120.102773,23.700981],[120.094817,23.587466],[120.121741,23.504664],[120.107831,23.341264],[120.081434,23.29191],[120.018947,23.073115],[120.029537,23.048623],[120.131382,23.002118],[120.149138,22.896715],[120.200403,22.721101],[120.274272,22.560181],[120.297191,22.531315],[120.443558,22.441245]]],[[[124.542984,25.903911],[124.586346,25.913777],[124.572805,25.93974],[124.541825,25.931031],[124.542984,25.903911]]],[[[123.445286,25.725966],[123.472104,25.713024],[123.508933,25.723237],[123.514834,25.751226],[123.483063,25.768587],[123.444496,25.746514],[123.445286,25.725966]]],[[[119.64597,23.55091],[119.701081,23.550657],[119.678057,23.600041],[119.610089,23.603953],[119.594388,23.577245],[119.566306,23.584732],[119.562565,23.530377],[119.573788,23.505885],[119.609141,23.503864],[119.64597,23.55091]]],[[[123.667207,25.914066],[123.707092,25.916873],[123.678008,25.938667],[123.667207,25.914066]]],[[[119.506031,23.625567],[119.505241,23.575814],[119.472416,23.557136],[119.523207,23.563699],[119.525578,23.624895],[119.506031,23.625567]]],[[[119.49739,23.386683],[119.495125,23.350156],[119.516885,23.349903],[119.49739,23.386683]]],[[[119.557454,23.666474],[119.604083,23.616989],[119.615516,23.660925],[119.586485,23.675974],[119.557454,23.666474]]],[[[121.46823,22.676644],[121.476502,22.64166],[121.513541,22.631833],[121.5147,22.67639],[121.46823,22.676644]]],[[[121.510538,22.087185],[121.507693,22.048523],[121.534089,22.022146],[121.594522,21.995382],[121.604586,22.022699],[121.575028,22.037122],[121.575607,22.084421],[121.510538,22.087185]]],[[[122.097533,25.500168],[122.093581,25.47183],[122.124825,25.475932],[122.097533,25.500168]]],[[[119.421467,23.216684],[119.421309,23.18935],[119.453396,23.217697],[119.421467,23.216684]]],[[[120.355042,22.327259],[120.395454,22.342287],[120.383072,22.355573],[120.355042,22.327259]]]]}}]} 2 | -------------------------------------------------------------------------------- /packages/renderer/src/geojson/province/820000-澳门特别行政区.json: -------------------------------------------------------------------------------- 1 | {"type":"FeatureCollection","features":[{"type":"Feature","properties":{"adcode":820000,"name":"澳门特别行政区","center":[113.54909,22.198951],"centroid":[113.566988,22.159307],"childrenNum":8,"level":"province","acroutes":[100000],"parent":{"adcode":100000}},"geometry":{"type":"MultiPolygon","coordinates":[[[[113.553916,22.125215],[113.553389,22.112664],[113.554447,22.107312],[113.559643,22.106216],[113.561045,22.106214],[113.576328,22.108147],[113.578953,22.108929],[113.580194,22.109694],[113.597564,22.125115],[113.603681,22.132371],[113.603873,22.132864],[113.601946,22.138113],[113.593091,22.162285],[113.586631,22.174165],[113.582019,22.182646],[113.575698,22.194265],[113.575472,22.199781],[113.573697,22.205587],[113.567629,22.209102],[113.567336,22.209241],[113.55711,22.213036],[113.556008,22.212463],[113.555864,22.212561],[113.555807,22.212703],[113.555932,22.214074],[113.555089,22.213974],[113.55474,22.213773],[113.554289,22.213656],[113.553843,22.213659],[113.553427,22.213776],[113.552808,22.213703],[113.552722,22.213604],[113.552132,22.213827],[113.550576,22.213861],[113.550287,22.213973],[113.549457,22.214005],[113.548496,22.213368],[113.54757,22.211492],[113.547515,22.211343],[113.547122,22.210753],[113.547063,22.210731],[113.546825,22.210294],[113.546644,22.210384],[113.54644,22.210146],[113.546291,22.210083],[113.546011,22.210094],[113.543261,22.210709],[113.541458,22.210708],[113.540832,22.210632],[113.540566,22.210549],[113.540073,22.210601],[113.538217,22.209386],[113.5391,22.206724],[113.539782,22.204445],[113.540515,22.203771],[113.540624,22.201871],[113.540764,22.199383],[113.53954,22.194664],[113.538742,22.191586],[113.534366,22.185627],[113.535335,22.185013],[113.534311,22.183656],[113.533508,22.181603],[113.533314,22.179271],[113.53456,22.174104],[113.535723,22.172545],[113.537107,22.170402],[113.537273,22.16918],[113.540985,22.161571],[113.544638,22.154083],[113.549079,22.149234],[113.550344,22.14785],[113.554638,22.142429],[113.553916,22.125215]]],[[[113.586501,22.201059],[113.576151,22.201038],[113.576045,22.20007],[113.576045,22.194495],[113.57635,22.193843],[113.577001,22.193432],[113.583971,22.193289],[113.586193,22.194792],[113.586499,22.195613],[113.586501,22.201059]]]]}}]} 2 | -------------------------------------------------------------------------------- /packages/renderer/src/index.js: -------------------------------------------------------------------------------- 1 | import {createApp} from 'vue'; 2 | import App from '/@/App.vue'; 3 | import router from '/@/router'; 4 | import './style/index.scss'; 5 | import naive from './naive-ui-load'; 6 | import './utils/downloadCascadeTiles'; 7 | 8 | createApp(App) 9 | .use(router) 10 | .use(naive) 11 | .mount('#app'); 12 | -------------------------------------------------------------------------------- /packages/renderer/src/naive-ui-load.js: -------------------------------------------------------------------------------- 1 | import { 2 | // create naive ui 3 | create, 4 | // component 5 | NMessageProvider, 6 | NNotificationProvider, 7 | NButton, 8 | NIcon, 9 | NDropdown, 10 | NDialog, 11 | NModal, 12 | NDescriptions, 13 | NDescriptionsItem, 14 | NPopover, 15 | NTree, 16 | NSpin, 17 | NSelect, 18 | } from 'naive-ui'; 19 | 20 | const naive = create({ 21 | components: [ 22 | NMessageProvider, 23 | NNotificationProvider, 24 | NButton, 25 | NIcon, 26 | NDropdown, 27 | NDialog, 28 | NModal, 29 | NDescriptions, 30 | NDescriptionsItem, 31 | NPopover, 32 | NTree, 33 | NSpin, 34 | NSelect, 35 | ], 36 | }); 37 | 38 | export default naive; 39 | -------------------------------------------------------------------------------- /packages/renderer/src/router.js: -------------------------------------------------------------------------------- 1 | import {createRouter, createWebHashHistory} from 'vue-router'; 2 | import Home from '/@/components/Home.vue'; 3 | 4 | const routes = [ 5 | {path: '/', name: 'Home', component: Home}, 6 | {path: '/about', name: 'About', component: () => import('/@/components/About.vue')}, // Lazy load route component 7 | ]; 8 | 9 | export default createRouter({ 10 | routes, 11 | history: createWebHashHistory(), 12 | }); 13 | -------------------------------------------------------------------------------- /packages/renderer/src/style/index.scss: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | height: 100%; 5 | -moz-osx-font-smoothing: grayscale; 6 | -webkit-font-smoothing: antialiased; 7 | text-rendering: optimizeLegibility; 8 | // font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif; 9 | font-family: PingFang SC,Helvetica Neue,Arial,Hiragino Sans GB,Microsoft YaHei,simsun,sans-serif; 10 | background-color: #f2f2f2; 11 | color: rgba(24, 40, 55, 1); 12 | font-size: 14px; 13 | } 14 | 15 | // label { 16 | // font-weight: 700; 17 | // } 18 | 19 | html { 20 | margin: 0; 21 | padding: 0; 22 | height: 100%; 23 | box-sizing: border-box; 24 | } 25 | 26 | #app { 27 | margin: 0; 28 | padding: 0; 29 | height: 100%; 30 | } 31 | 32 | *, 33 | *:before, 34 | *:after { 35 | box-sizing: inherit; 36 | } 37 | 38 | a:focus, 39 | a:active { 40 | outline: none; 41 | } 42 | 43 | a, 44 | a:focus, 45 | a:hover { 46 | cursor: pointer; 47 | color: inherit; 48 | text-decoration: none; 49 | } 50 | 51 | div:focus { 52 | outline: none; 53 | } 54 | 55 | .clearfix { 56 | &:after { 57 | visibility: hidden; 58 | display: block; 59 | font-size: 0; 60 | content: " "; 61 | clear: both; 62 | height: 0; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /packages/renderer/src/use/electron.js: -------------------------------------------------------------------------------- 1 | export function useElectron() { 2 | return window.electron; 3 | } 4 | -------------------------------------------------------------------------------- /packages/renderer/src/utils/TileLayerCollection/TileLayerCollection.js: -------------------------------------------------------------------------------- 1 | import TdtTileLayer from './tilelayers/TdtTileLayer'; 2 | import GeoqTileLayer from './tilelayers/GeoqTileLayer'; 3 | import GoogleTileLayer from './tilelayers/GoogleTileLayer'; 4 | import AmapTileLayer from './tilelayers/AmapTileLayer'; 5 | import TencentTileLayer from './tilelayers/TencentTileLayer'; 6 | import OsmTileLayer from './tilelayers/OsmTileLayer'; 7 | import CartoDbTileLayer from './tilelayers/CartoDbTileLayer'; 8 | import MapboxTileLayer from './tilelayers/MapboxTileLayer'; 9 | import BaiduTileLayer from './tilelayers/BaiduTileLayer'; 10 | import { GroupTileLayer } from 'maptalks'; 11 | import Utils from './utils'; 12 | 13 | class TileLayerCollection { 14 | 15 | static getTdtTileLayer(id, options = {}) { 16 | const baselayers = []; 17 | const baseLayer = new TdtTileLayer(Utils.uuid(), options); 18 | baselayers.push(baseLayer); 19 | 20 | if (options.style) { 21 | options.style = options.style + '_Label'; 22 | const baseLayer1 = new TdtTileLayer(Utils.uuid(), options); 23 | baselayers.push(baseLayer1); 24 | } 25 | 26 | return new GroupTileLayer(id, baselayers, {attribution: options.attribution}); 27 | 28 | } 29 | 30 | static getGeoqTileLayer(id, options = {}) { 31 | const baseLayer = new GeoqTileLayer(id, options); 32 | return baseLayer; 33 | } 34 | 35 | static getGoogleTileLayer(id, options = {}) { 36 | const baseLayer = new GoogleTileLayer(id, options); 37 | return baseLayer; 38 | } 39 | 40 | static getAmapTileLayer(id, options = {}) { 41 | if (options.style === 'Satellite') { 42 | const baseLayers = []; 43 | const baseLayer = new AmapTileLayer(Utils.uuid(), options); 44 | baseLayers.push(baseLayer); 45 | options.style = options.style + '_Label'; 46 | const baseLayer1 = new AmapTileLayer(Utils.uuid(), options); 47 | baseLayers.push(baseLayer1); 48 | return new GroupTileLayer(id, baseLayers, {attribution: options.attribution}); 49 | } else { 50 | return new AmapTileLayer(id, options); 51 | } 52 | } 53 | 54 | static getTencentTileLayer(id, options = {}) { 55 | if (options.style === 'Normal') { 56 | return new TencentTileLayer(id, options); 57 | } else { 58 | const baseLayers = []; 59 | const baseLayer = new TencentTileLayer(Utils.uuid(), options); 60 | baseLayers.push(baseLayer); 61 | options.style = options.style + '_Label'; 62 | const baseLayer1 = new TencentTileLayer(Utils.uuid(), options); 63 | baseLayers.push(baseLayer1); 64 | return new GroupTileLayer(id, baseLayers, {attribution: options.attribution}); 65 | } 66 | } 67 | 68 | static getOsmTileLayer(id, options = {}) { 69 | return new OsmTileLayer(id, options); 70 | } 71 | 72 | static getCartoDbTileLayer(id, options = {}) { 73 | return new CartoDbTileLayer(id, options); 74 | } 75 | 76 | static getMapboxTileLayer(id, options = {}) { 77 | return new MapboxTileLayer(id, options); 78 | } 79 | 80 | static getBaiduTileLayer(id, options = {}) { 81 | return new BaiduTileLayer(id, options); 82 | } 83 | } 84 | 85 | export default TileLayerCollection; 86 | -------------------------------------------------------------------------------- /packages/renderer/src/utils/TileLayerCollection/param.js: -------------------------------------------------------------------------------- 1 | import { getKeys } from '/@/utils/mapKey.js'; 2 | 3 | 4 | export default function getParams() { 5 | const { mapboxKey, tdtKey } = getKeys(); 6 | 7 | const getTdtUrl = (url, layer) => { 8 | const tdtUrlParams = 'SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk='; 9 | return `${url}?LAYER=${layer}&${tdtUrlParams}${tdtKey}`; 10 | }; 11 | 12 | const params = { 13 | TDT: { 14 | // Normal: { 15 | // url: 'https://t0.tianditu.gov.cn/DataServer?T=vec_w&X={x}&Y={y}&L={z}&tk=' + tdtKey, 16 | // }, 17 | // Normal_Label: { 18 | // url: 'https://t0.tianditu.gov.cn/DataServer?T=cva_w&X={x}&Y={y}&L={z}&tk=' + tdtKey, 19 | // }, 20 | // Satellite: { 21 | // url: 'https://t0.tianditu.gov.cn/DataServer?T=img_w&X={x}&Y={y}&L={z}&tk=' + tdtKey, 22 | // }, 23 | // Satellite_Label: { 24 | // url: 'https://t0.tianditu.gov.cn/DataServer?T=cia_w&X={x}&Y={y}&L={z}&tk=' + tdtKey, 25 | // }, 26 | 27 | // Terrain: { 28 | // url: 'https://t0.tianditu.gov.cn/DataServer?T=ter_w&X={x}&Y={y}&L={z}&tk=' + tdtKey, 29 | // }, 30 | 31 | // Terrain_Label: { 32 | // url: 'https://t0.tianditu.gov.cn/DataServer?T=cta_w&X={x}&Y={y}&L={z}&tk=' + tdtKey, 33 | // }, 34 | 35 | Normal: { 36 | url: getTdtUrl('https://t0.tianditu.gov.cn/vec_w/wmts', 'vec'), 37 | }, 38 | Normal_Label: { 39 | url: getTdtUrl('https://t0.tianditu.gov.cn/cva_w/wmts', 'cva'), 40 | }, 41 | Satellite: { 42 | url: getTdtUrl('https://t0.tianditu.gov.cn/img_w/wmts', 'img'), 43 | }, 44 | Satellite_Label: { 45 | url: getTdtUrl('https://t0.tianditu.gov.cn/cia_w/wmts', 'cia'), 46 | }, 47 | 48 | Terrain: { 49 | url: getTdtUrl('https://t0.tianditu.gov.cn/ter_w/wmts', 'ter'), 50 | }, 51 | 52 | Terrain_Label: { 53 | url: getTdtUrl('https://t0.tianditu.gov.cn/cta_w/wmts', 'cta'), 54 | }, 55 | }, 56 | GEOQ: { 57 | Satellite: { 58 | url: 'https://services.arcgisonline.com/arcgis/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', 59 | }, 60 | Street: { 61 | url: 'https://services.arcgisonline.com/arcgis/rest/services/World_Street_Map/MapServer/tile/{z}/{y}/{x}', 62 | }, 63 | Physical: { 64 | url: 'https://services.arcgisonline.com/arcgis/rest/services/World_Physical_Map/MapServer/tile/{z}/{y}/{x}', 65 | }, 66 | }, 67 | Google: { 68 | Normal: { 69 | url: 'https://gac-geo.googlecnapps.club/maps/vt?lyrs=m&x={x}&y={y}&z={z}', 70 | }, 71 | Satellite: { 72 | url: 'https://gac-geo.googlecnapps.club/maps/vt?lyrs=s&x={x}&y={y}&z={z}', 73 | }, 74 | Satellite_Label: { 75 | url: 'https://gac-geo.googlecnapps.club/maps/vt?lyrs=s,m&gl=CN&x={x}&y={y}&z={z}', 76 | }, 77 | 78 | }, 79 | Amap: { 80 | Normal: { 81 | url: 'https://webrd01.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=8&x={x}&y={y}&z={z}', 82 | }, 83 | Satellite: { 84 | url: 'https://webst01.is.autonavi.com/appmaptile?style=6&x={x}&y={y}&z={z}', 85 | }, 86 | Satellite_Label: { 87 | url: 'https://webst01.is.autonavi.com/appmaptile?style=8&x={x}&y={y}&z={z}', 88 | }, 89 | NormalEn: { 90 | url: 'https://webrd01.is.autonavi.com/appmaptile?lang=en&size=1&scale=1&style=8&x={x}&y={y}&z={z}', 91 | }, 92 | }, 93 | Tencent: { 94 | Normal: { 95 | url: 'https://rt0.map.gtimg.com/realtimerender?z={z}&x={x}&y={y}&type=vector&style=0', 96 | }, 97 | Satellite: { 98 | url: 'https://p0.map.gtimg.com/sateTiles/{z}/{m}/{n}/{x}_{y}.jpg', 99 | }, 100 | Satellite_Label: { 101 | url: 'https://rt3.map.gtimg.com/tile?z={z}&x={x}&y={y}&type=vector&styleid=3&version=117', 102 | }, 103 | Terrain: { 104 | url: 'https://p0.map.gtimg.com/demTiles/{z}/{m}/{n}/{x}_{y}.jpg', 105 | }, 106 | Terrain_Label: { 107 | url: 'https://rt3.map.gtimg.com/tile?z={z}&x={x}&y={y}&type=vector&styleid=3&version=117', 108 | }, 109 | }, 110 | Osm: { 111 | Normal: { 112 | url: 'https://tile-{s}.openstreetmap.fr/hot/{z}/{x}/{y}.png', 113 | }, 114 | Bike: { 115 | url: 'https://c.tile.thunderforest.com/cycle/{z}/{x}/{y}.png?apikey=6170aad10dfd42a38d4d8c709a536f38', 116 | }, 117 | Transport: { 118 | url: 'https://c.tile.thunderforest.com/transport/{z}/{x}/{y}.png?apikey=6170aad10dfd42a38d4d8c709a536f38', 119 | }, 120 | Humanitarian: { 121 | url: 'https://tile-b.openstreetmap.fr/hot/{z}/{x}/{y}.png', 122 | }, 123 | }, 124 | CartoDb: { 125 | Dark: { 126 | url: 'https://a.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png', 127 | }, 128 | Light: { 129 | url: 'https://a.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png', 130 | }, 131 | }, 132 | Mapbox: { 133 | Streets: { 134 | url: 'https://a.tiles.mapbox.com/v4/mapbox.streets/{z}/{x}/{y}.png?access_token=' + mapboxKey, 135 | }, 136 | Dark: { 137 | url: 'https://a.tiles.mapbox.com/v4/mapbox.dark/{z}/{x}/{y}.png?access_token=' + mapboxKey, 138 | }, 139 | LightDark: { 140 | url: 'https://a.tiles.mapbox.com/v3/spatialdev.map-c9z2cyef/{z}/{x}/{y}.png', 141 | }, 142 | Satellite: { 143 | url: 'https://a.tiles.mapbox.com/v4/mapbox.satellite/{z}/{x}/{y}.png?access_token=' + mapboxKey, 144 | }, 145 | Light: { 146 | url: 'https://a.tiles.mapbox.com/v4/mapbox.light/{z}/{x}/{y}.png?access_token=' + mapboxKey, 147 | }, 148 | Emerald: { 149 | url: 'https://a.tiles.mapbox.com/v4/mapbox.emerald/{z}/{x}/{y}.png?access_token=' + mapboxKey, 150 | }, 151 | White: { 152 | url: 'https://a.tiles.mapbox.com/v4/examples.map-h67hf2ic/{z}/{x}/{y}.png?access_token=' + mapboxKey, 153 | }, 154 | Red: { 155 | url: 'https://a.tiles.mapbox.com/v4/examples.map-h68a1pf7,examples.npr-stations/{z}/{x}/{y}.png?access_token=' + mapboxKey, 156 | }, 157 | Outdoors: { 158 | url: 'https://a.tiles.mapbox.com/v4/mapbox.outdoors/{z}/{x}/{y}.png?access_token=' + mapboxKey, 159 | }, 160 | StreetsSatellite: { 161 | url: 'https://a.tiles.mapbox.com/v4/mapbox.streets-satellite/{z}/{x}/{y}.png?access_token=' + mapboxKey, 162 | }, 163 | Comic: { 164 | url: 'https://a.tiles.mapbox.com/v4/mapbox.comic/{z}/{x}/{y}.png?access_token=' + mapboxKey, 165 | }, 166 | Building: { 167 | url: 'https://b.tiles.mapbox.com/v3/osmbuildings.kbpalbpk/{z}/{x}/{y}.png', 168 | }, 169 | }, 170 | Baidu: { 171 | Normal: { 172 | // url: 'https://api0.map.bdimg.com/customimage/tile?&x=3&y=1&z=5&scale=1&customid=normal', 173 | url: 'https://gss{s}.bdstatic.com/8bo_dTSlRsgBo1vgoIiO_jowehsv/tile/?qt=tile&x={x}&y={y}&z={z}&styles=pl&scaler=1&udt=20170927', 174 | }, 175 | Satellite: { 176 | url: 'https://shangetu{s}.map.bdimg.com/it/u=x={x};y={y};z={z};v=009;type=sate&fm=46', 177 | }, 178 | midnight: { 179 | url: 'https://api{s}.map.bdimg.com/customimage/tile?&x={x}&y={y}&z={z}&scale=1&customid=midnight', 180 | }, 181 | light: { 182 | url: 'https://api{s}.map.bdimg.com/customimage/tile?&x={x}&y={y}&z={z}&scale=1&customid=light', 183 | }, 184 | dark: { 185 | url: 'https://api{s}.map.bdimg.com/customimage/tile?&x={x}&y={y}&z={z}&scale=1&customid=dark', 186 | }, 187 | redalert: { 188 | url: 'https://api{s}.map.bdimg.com/customimage/tile?&x={x}&y={y}&z={z}&scale=1&customid=redalert', 189 | }, 190 | googlelite: { 191 | url: 'https://api{s}.map.bdimg.com/customimage/tile?&x={x}&y={y}&z={z}&scale=1&customid=googlelite', 192 | }, 193 | grassgreen: { 194 | url: 'https://api{s}.map.bdimg.com/customimage/tile?&x={x}&y={y}&z={z}&scale=1&customid=grassgreen', 195 | }, 196 | pink: { 197 | url: 'https://api{s}.map.bdimg.com/customimage/tile?&x={x}&y={y}&z={z}&scale=1&customid=pink', 198 | }, 199 | darkgreen: { 200 | url: 'https://api{s}.map.bdimg.com/customimage/tile?&x={x}&y={y}&z={z}&scale=1&customid=darkgreen', 201 | }, 202 | bluish: { 203 | url: 'https://api{s}.map.bdimg.com/customimage/tile?&x={x}&y={y}&z={z}&scale=1&customid=bluish', 204 | }, 205 | grayscale: { 206 | url: 'https://api{s}.map.bdimg.com/customimage/tile?&x={x}&y={y}&z={z}&scale=1&customid=grayscale', 207 | }, 208 | hardedge: { 209 | url: 'https://api{s}.map.bdimg.com/customimage/tile?&x={x}&y={y}&z={z}&scale=1&customid=hardedge', 210 | }, 211 | }, 212 | }; 213 | 214 | return params; 215 | } 216 | 217 | -------------------------------------------------------------------------------- /packages/renderer/src/utils/TileLayerCollection/tilelayers/AmapTileLayer.js: -------------------------------------------------------------------------------- 1 | import BaseTileLayer from './BaseTileLayer'; 2 | import params from './../param'; 3 | 4 | class AmapTileLayer extends BaseTileLayer { 5 | constructor(id, options = {}) { 6 | const style = options.style || 'Normal'; 7 | options.urlTemplate = params().Amap[style].url; 8 | super(id, options); 9 | } 10 | } 11 | 12 | export default AmapTileLayer; 13 | -------------------------------------------------------------------------------- /packages/renderer/src/utils/TileLayerCollection/tilelayers/BaiduTileLayer.js: -------------------------------------------------------------------------------- 1 | import BaseTileLayer from './BaseTileLayer'; 2 | import params from './../param'; 3 | 4 | class BaiduTileLayer extends BaseTileLayer { 5 | constructor(id, options = {}) { 6 | const style = options.style || 'Normal'; 7 | options.urlTemplate = params().Baidu[style].url; 8 | super(id, options); 9 | } 10 | } 11 | 12 | export default BaiduTileLayer; 13 | -------------------------------------------------------------------------------- /packages/renderer/src/utils/TileLayerCollection/tilelayers/BaseTileLayer.js: -------------------------------------------------------------------------------- 1 | import { TileLayer } from 'maptalks'; 2 | 3 | class BaseTileLayer extends TileLayer { 4 | // constructor(id, options) { 5 | // super(id, options); 6 | // } 7 | } 8 | 9 | export default BaseTileLayer; 10 | -------------------------------------------------------------------------------- /packages/renderer/src/utils/TileLayerCollection/tilelayers/CartoDbTileLayer.js: -------------------------------------------------------------------------------- 1 | import BaseTileLayer from './BaseTileLayer'; 2 | import params from './../param'; 3 | 4 | class CartoDbTileLayer extends BaseTileLayer { 5 | constructor(id, options = {}) { 6 | const style = options.style || 'light'; 7 | options.urlTemplate = params().CartoDb[style].url; 8 | super(id, options); 9 | } 10 | } 11 | 12 | export default CartoDbTileLayer; 13 | -------------------------------------------------------------------------------- /packages/renderer/src/utils/TileLayerCollection/tilelayers/GeoqTileLayer.js: -------------------------------------------------------------------------------- 1 | import BaseTileLayer from './BaseTileLayer'; 2 | import params from './../param'; 3 | 4 | class GeoqTileLayer extends BaseTileLayer { 5 | constructor(id, options = {}) { 6 | const style = options.style || 'Colour'; 7 | options.urlTemplate = params().GEOQ[style].url; 8 | super(id, options); 9 | } 10 | } 11 | 12 | export default GeoqTileLayer; 13 | -------------------------------------------------------------------------------- /packages/renderer/src/utils/TileLayerCollection/tilelayers/GoogleTileLayer.js: -------------------------------------------------------------------------------- 1 | import BaseTileLayer from './BaseTileLayer'; 2 | import params from './../param'; 3 | 4 | class GoogleTileLayer extends BaseTileLayer { 5 | constructor(id, options = {}) { 6 | const style = options.style || 'Normal'; 7 | options.urlTemplate = params().Google[style].url; 8 | super(id, options); 9 | } 10 | } 11 | 12 | export default GoogleTileLayer; 13 | -------------------------------------------------------------------------------- /packages/renderer/src/utils/TileLayerCollection/tilelayers/MapboxTileLayer.js: -------------------------------------------------------------------------------- 1 | import BaseTileLayer from './BaseTileLayer'; 2 | import params from './../param'; 3 | 4 | class MapboxTileLayer extends BaseTileLayer { 5 | constructor(id, options = {}) { 6 | const style = options.style || 'light'; 7 | options.urlTemplate = params().Mapbox[style].url; 8 | super(id, options); 9 | } 10 | } 11 | 12 | export default MapboxTileLayer; 13 | -------------------------------------------------------------------------------- /packages/renderer/src/utils/TileLayerCollection/tilelayers/OsmTileLayer.js: -------------------------------------------------------------------------------- 1 | import BaseTileLayer from './BaseTileLayer'; 2 | import params from './../param'; 3 | 4 | class OsmTileLayer extends BaseTileLayer { 5 | constructor(id, options = {}) { 6 | const style = options.style || 'Normal'; 7 | options.urlTemplate = params().Osm[style].url; 8 | super(id, options); 9 | } 10 | } 11 | 12 | export default OsmTileLayer; 13 | -------------------------------------------------------------------------------- /packages/renderer/src/utils/TileLayerCollection/tilelayers/TdtTileLayer.js: -------------------------------------------------------------------------------- 1 | import BaseTileLayer from './BaseTileLayer'; 2 | import params from './../param'; 3 | 4 | class TDTTileLayer extends BaseTileLayer { 5 | constructor(id, options = {}) { 6 | const style = options.style || 'Normal'; 7 | options.urlTemplate = params().TDT[style].url; 8 | super(id, options); 9 | } 10 | } 11 | 12 | export default TDTTileLayer; 13 | -------------------------------------------------------------------------------- /packages/renderer/src/utils/TileLayerCollection/tilelayers/TencentTileLayer.js: -------------------------------------------------------------------------------- 1 | import BaseTileLayer from './BaseTileLayer'; 2 | import params from './../param'; 3 | 4 | class TencentTileLayer extends BaseTileLayer { 5 | constructor(id, options = {}) { 6 | const style = options.style || 'Normal'; 7 | options.urlTemplate = params().Tencent[style].url; 8 | super(id, options); 9 | } 10 | 11 | getTileUrl(x, y, z) { 12 | const urlArgs = this.getUrlArgs(x, y, z); 13 | const l = urlArgs.z; 14 | const r = urlArgs.x; 15 | const c = urlArgs.y; 16 | 17 | const m = Math.floor(r / 16.0); 18 | const n = Math.floor(c / 16.0); 19 | const urlTemplate = this.options['urlTemplate']; 20 | const url = urlTemplate.replace('{x}', r).replace('{y}', c).replace('{z}', l).replace('{m}', m).replace('{n}', n); 21 | return url; 22 | 23 | } 24 | 25 | getUrlArgs(x, y, z) { 26 | return { 27 | z: z, 28 | x: x, 29 | y: Math.pow(2, z) - 1 - y 30 | }; 31 | } 32 | } 33 | 34 | export default TencentTileLayer; 35 | -------------------------------------------------------------------------------- /packages/renderer/src/utils/TileLayerCollection/utils.js: -------------------------------------------------------------------------------- 1 | const CHARS = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split(''); 2 | 3 | class Utils { 4 | 5 | static uuid(prefix = 'ID') { 6 | const uuid = new Array(36); 7 | let rnd = 0, r; 8 | for (let i = 0; i < 36; i++) { 9 | if (i === 8 || i === 13 || i === 18 || i === 23) { 10 | uuid[i] = '-'; 11 | } else if (i === 14) { 12 | uuid[i] = '4'; 13 | } else { 14 | if (rnd <= 0x02) rnd = 0x2000000 + (Math.random() * 0x1000000) | 0; 15 | r = rnd & 0xf; 16 | rnd = rnd >> 4; 17 | uuid[i] = CHARS[(i === 19) ? (r & 0x3) | 0x8 : r]; 18 | } 19 | } 20 | return prefix + '-' + uuid.join(''); 21 | } 22 | } 23 | 24 | export default Utils; 25 | -------------------------------------------------------------------------------- /packages/renderer/src/utils/areaList.js: -------------------------------------------------------------------------------- 1 | // 下载范围区域列表 2 | const modules = import.meta.glob('../geojson/**/*.json'); 3 | 4 | function formate () { 5 | const pre = '../geojson/'; 6 | const ext = '.json'; 7 | const provinceId = 'province'; 8 | const cityId = 'city'; 9 | const contryList = []; 10 | const provinceList = []; 11 | const cityMap = {}; 12 | for (const path in modules) { 13 | const params = path.substring(pre.length, path.length - ext.length).split('/'); 14 | if (params.length === 1) { 15 | contryList.push({ 16 | areaCode: -1, 17 | areaName: '中国', 18 | fetchLoad: modules[path], 19 | }); 20 | continue; 21 | } 22 | const [folderName, fileName] = params; 23 | const [areaCode, areaName] = fileName.split('-'); 24 | const provinceCode = areaCode.substring(0, 2); // 省份代码 25 | const cityCode = areaCode.substring(2, 4); // 城市代码 26 | // const countyCode = areaCode.substring(4, 6); // 区/乡镇代码 27 | if (folderName === provinceId) { 28 | provinceList.push({ 29 | areaCode, 30 | provinceCode, 31 | cityCode, 32 | areaName, 33 | fetchLoad: modules[path], 34 | }); 35 | } else if (folderName === cityId) { 36 | if (!cityMap[provinceCode]) cityMap[provinceCode] = []; 37 | cityMap[provinceCode].push({ 38 | areaCode, 39 | provinceCode, 40 | cityCode, 41 | areaName, 42 | fetchLoad: modules[path], 43 | }); 44 | } 45 | } 46 | for (let index = 0; index < provinceList.length; index++) { 47 | const province = provinceList[index]; 48 | province.children = cityMap[province.provinceCode]; 49 | } 50 | contryList[0].children = provinceList; 51 | return contryList; 52 | } 53 | 54 | const areaList = formate(); 55 | 56 | export function getAreaList() { 57 | return areaList; 58 | } 59 | -------------------------------------------------------------------------------- /packages/renderer/src/utils/clipImage.js: -------------------------------------------------------------------------------- 1 | // 裁切瓦片 2 | import * as maptalks from 'maptalks'; 3 | 4 | export class ClipImage { 5 | constructor() { 6 | this.tileSize = { 7 | width: 256, 8 | height: 256, 9 | }; 10 | this.createMap(); 11 | } 12 | setSize(width, height) { 13 | if (this.tileSize.width === width && this.tileSize.height === height) return; 14 | this.tileSize.width = width; 15 | this.tileSize.height = height; 16 | this.dom.style.width = width + 'px'; 17 | this.dom.style.height = height + 'px'; 18 | } 19 | createMap() { 20 | if (this.map) return; 21 | const dom = document.createElement('div'); 22 | dom.id = 'map-clip-image'; 23 | dom.style = ` 24 | position: fixed; 25 | margin: 0; 26 | padding: 0; 27 | top: 0; 28 | left: 0; 29 | width: ${this.tileSize.width}px; 30 | height: ${this.tileSize.height}px; 31 | z-index: -1; 32 | `; 33 | document.body.append(dom); 34 | this.dom = dom; 35 | const map = new maptalks.Map(dom, { 36 | center: [105.08052356963802, 36.04231948670001], 37 | zoom: 5, 38 | zoomAnimation: false, 39 | zoomAnimationDuration: 1, 40 | panAnimation: false, 41 | panAnimationDuration: 1, 42 | rotateAnimation: false, 43 | rotateAnimationDuration: 1, 44 | }); 45 | this.map = map; 46 | this.vectorLayer = new maptalks.VectorLayer('vector', { 47 | drawImmediate: true, 48 | geometryEvents: false, 49 | hitDetect: false, 50 | forceRenderOnMoving: true, 51 | forceRenderOnZooming: true, 52 | forceRenderOnRotating: true, 53 | }).addTo(map); 54 | } 55 | addTempGeometry(intersection, rect) { 56 | this.vectorLayer.clear(); 57 | // const polygon = new maptalks.Polygon(intersection.geometry.coordinates, { 58 | // symbol: { 59 | // 'lineWidth' : 0, 60 | // 'polygonFill' : 'rgb(0,0,0)', 61 | // }, 62 | // }); 63 | const polygon = maptalks.GeoJSON.toGeometry(intersection); 64 | this.vectorLayer.addGeometry(polygon); 65 | const extent = new maptalks.Polygon(rect.geometry.coordinates, { 66 | symbol: { 67 | 'lineWidth' : 0, 68 | 'polygonFill' : 'rgba(0,0,0,0)', 69 | }, 70 | }); 71 | this.vectorLayer.addGeometry(extent); 72 | 73 | const zoom = this.map.getFitZoom(extent.getExtent()); 74 | const center = extent.getCenter(); 75 | this.map.setCenterAndZoom(center, zoom); 76 | } 77 | getImage(imageType) { 78 | return new Promise(resolve => { 79 | // setTimeout(() => { 80 | // const img = this.map.toDataURL({ 81 | // 'mimeType' : 'image/' + imageType, 82 | // 'save' : false, 83 | // }); 84 | // resolve(img); 85 | // }, 100); 86 | 87 | const isComplete = () => { 88 | const over = !this.map.isMoving() && !this.map.isZooming() && !this.map.isAnimating(); 89 | if (!over) { 90 | requestAnimationFrame(isComplete); 91 | } else { 92 | const img = this.map.toDataURL({ 93 | 'mimeType' : 'image/' + imageType, 94 | 'save' : false, 95 | }); 96 | resolve(img); 97 | } 98 | }; 99 | requestAnimationFrame(isComplete); 100 | }); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /packages/renderer/src/utils/download.js: -------------------------------------------------------------------------------- 1 | // 下载 2 | import { setState, setProgress } from './progress'; 3 | // eslint-disable-next-line 4 | import { judgeTile, testDraw2 } from './baseMap'; 5 | import { ClipImage } from './clipImage'; 6 | const CLIPIMAGE = new ClipImage(); 7 | /** 8 | * 下载瓦片 9 | * @param {Array} list 瓦片列表 10 | * @param {Function} apiDownload 下载方法 11 | * @returns 12 | */ 13 | export function downloadLoop (list, apiDownload) { 14 | 15 | if (!Array.isArray(list) || typeof apiDownload !== 'function') return; 16 | const length = list.length; 17 | if (length === 0) return; 18 | 19 | const statistics = {success: 0, error: 0, percentage: 0, count: length}; 20 | let index = 0; 21 | const download = () => { 22 | if (index >= length) { 23 | statistics.percentage = 100; 24 | setProgress(statistics); 25 | setState(false); 26 | window.$message.success(`下载完成。下载成功${statistics.success},下载失败${statistics.error}`); 27 | return; 28 | } 29 | const item = list[index]; 30 | statistics.percentage = Number((index / length * 100).toFixed(2)); 31 | apiDownload(item); 32 | index++; 33 | }; 34 | download(); 35 | window.electron.imageDownloadDone(state => { 36 | if (state.state === 'completed') { 37 | statistics.success++; 38 | } else { 39 | statistics.error++; 40 | } 41 | setProgress(statistics); 42 | download(); 43 | }); 44 | } 45 | 46 | /** 47 | * 下载瓦片并裁切 48 | * @param {Array} list 瓦片列表 49 | * @param {Function} apiDownload 下载方法 50 | * @param {maptalks.TileLayer} tileLayer 下载瓦片图层 51 | * @param {maptalks.Geometry} downloadGeometry 下载范围 52 | * @param {String} imageType 瓦片格式 53 | * @returns 54 | */ 55 | export function downloadClipLoop (list, apiDownload, tileLayer, downloadGeometry, imageType) { 56 | if (!Array.isArray(list) || typeof apiDownload !== 'function') return; 57 | const length = list.length; 58 | if (length === 0) return; 59 | 60 | // 获取坐标投影信息 61 | const { width, height } = tileLayer.getTileSize(); 62 | const spatialReference = tileLayer.getSpatialReference(); 63 | const prj = spatialReference.getProjection(); 64 | const fullExtent = spatialReference.getFullExtent(); 65 | const code = prj.code; 66 | const statistics = {success: 0, error: 0, percentage: 0, count: length}; 67 | let index = 0; 68 | const download = async () => { 69 | if (index >= length) { 70 | statistics.percentage = 100; 71 | setProgress(statistics); 72 | setState(false); 73 | window.$message.success(`下载完成。下载成功${statistics.success},下载失败${statistics.error}`); 74 | return; 75 | } 76 | const item = list[index]; 77 | statistics.percentage = Number((index / length * 100).toFixed(2)); 78 | const relation = judgeTile(downloadGeometry, { 79 | width, 80 | height, 81 | spatialReference, 82 | prj, 83 | fullExtent, 84 | code, 85 | tile: {x:item.x, y:item.y,z:item.zoom}, 86 | }); 87 | if (relation === 1) { 88 | apiDownload(item); 89 | index++; 90 | } else if (relation === 2) { 91 | index++; 92 | statistics.success++; 93 | setProgress(statistics); 94 | download(); 95 | } else if (typeof relation === 'object') { 96 | // testDraw2(tileLayer, relation.intersection); 97 | // 裁切下载 98 | CLIPIMAGE.addTempGeometry(relation.intersection, relation.rect); 99 | const imageBuffer = await CLIPIMAGE.getImage(imageType); 100 | item.imageBuffer = imageBuffer; 101 | apiDownload(item); 102 | index++; 103 | } 104 | }; 105 | download(); 106 | window.electron.imageDownloadDone(state => { 107 | if (state.state === 'completed') { 108 | statistics.success++; 109 | } else { 110 | statistics.error++; 111 | } 112 | setProgress(statistics); 113 | download(); 114 | }); 115 | } 116 | 117 | /** 118 | * 下载单张瓦片 119 | * @param {*} tile 瓦片参数 120 | * @param {*} downloadOption 下载参数 121 | * @returns Promise 122 | */ 123 | export function downloadImage (tile, downloadOption) { 124 | const { clipImage } = downloadOption; 125 | if (clipImage) { 126 | return _downloadClipImage(tile, downloadOption); 127 | } else { 128 | return _downloadImage(tile, downloadOption); 129 | } 130 | } 131 | 132 | /** 133 | * 下载单张瓦片 134 | * @param {*} tile 135 | * @param {*} downloadOption 136 | * @returns 137 | */ 138 | function _downloadImage (tile, downloadOption) { 139 | return new Promise((resolve) => { 140 | 141 | const temppath = downloadOption.downloadPath + tile.z + '/' + tile.x; 142 | window.electron.ipcRenderer.send('ensure-dir', temppath); 143 | const savePath = temppath + '/' + tile.y + downloadOption.pictureType; 144 | const param = {zoom: tile.z, url:tile.url, savePath, x:tile.x, y:tile.y}; 145 | 146 | window.electron.ipcRenderer.send('save-image', param); 147 | window.electron.imageDownloadDone(state => { 148 | if (state.state === 'completed') { 149 | resolve(true); 150 | } else { 151 | resolve(false); 152 | } 153 | }); 154 | }); 155 | } 156 | 157 | /** 158 | * 下载单张瓦片并裁切 159 | * @param {*} tile 160 | * @param {*} downloadOption 161 | * @returns 162 | */ 163 | function _downloadClipImage (tile, downloadOption) { 164 | return new Promise((resolve) => { 165 | const { tileLayer, downloadGeometry, pictureType, downloadPath, imageType } = downloadOption; 166 | // 获取坐标投影信息 167 | const { width, height } = tileLayer.getTileSize(); 168 | const spatialReference = tileLayer.getSpatialReference(); 169 | const prj = spatialReference.getProjection(); 170 | const fullExtent = spatialReference.getFullExtent(); 171 | const code = prj.code; 172 | 173 | const apiDownload = (temp, imageBuffer) => { 174 | const temppath = downloadPath + temp.z + '/' + temp.x; 175 | window.electron.ipcRenderer.send('ensure-dir', temppath); 176 | const savePath = temppath + '/' + temp.y + pictureType; 177 | const param = {zoom: temp.z, url:temp.url, savePath, x:temp.x, y:temp.y, imageBuffer}; 178 | window.electron.ipcRenderer.send('save-image', param); 179 | }; 180 | 181 | const item = tile; 182 | const relation = judgeTile(downloadGeometry, { 183 | width, 184 | height, 185 | spatialReference, 186 | prj, 187 | fullExtent, 188 | code, 189 | tile: {x:item.x, y:item.y,z:item.zoom || item.z}, 190 | }); 191 | if (relation === 1) { 192 | apiDownload(item); 193 | } else if (relation === 2) { 194 | resolve(true); 195 | return; 196 | } else if (typeof relation === 'object') { 197 | // testDraw2(tileLayer, relation.intersection); 198 | // 裁切下载 199 | CLIPIMAGE.addTempGeometry(relation.intersection, relation.rect); 200 | CLIPIMAGE.getImage(imageType).then(imageBuffer => { 201 | apiDownload(item, imageBuffer); 202 | }); 203 | } 204 | 205 | window.electron.imageDownloadDone(state => { 206 | if (state.state === 'completed') { 207 | resolve(true); 208 | } else { 209 | resolve(false); 210 | } 211 | }); 212 | }); 213 | } 214 | -------------------------------------------------------------------------------- /packages/renderer/src/utils/fileSave.js: -------------------------------------------------------------------------------- 1 | // 地图 2 | import {TileTMS, TileTMSList, TileTMSListMerge} from './fileSaveTms'; 3 | import TileBaidu from './fileSaveBaidu'; 4 | import { getState } from './progress'; 5 | export default class FileSave{ 6 | constructor(data) { 7 | const projection = data.mapConfig.projection.code; // BAIDU,EPSG:4326,EPSG:3857 8 | if (getState()) { 9 | return window.$message.warning('下载任务执行中,请稍后..'); 10 | } 11 | if (projection === 'BAIDU') { 12 | this.downloadBaidu(data); 13 | } else { 14 | this.downloadTms(data); 15 | } 16 | } 17 | // 保存单张图片 18 | saveImage(param) { 19 | // const param = { 20 | // url: 'https://map.geoq.cn/MapServer/tile/9/207/421', 21 | // savePath: '', 22 | // }; 23 | window.electron.ipcRenderer.send('save-image', param); 24 | 25 | } 26 | // 保存图片并合并 27 | saveImagesAndMerge(param) { 28 | // const param = { 29 | // layers: [{url:'https://map.geoq.cn/MapServer/tile/9/207/421',isLabel: true}], 30 | // savePath: '', 31 | // }; 32 | window.electron.ipcRenderer.send('save-image-merge', param); 33 | 34 | } 35 | ensureDirSync(path) { 36 | window.electron.ipcRenderer.send('ensure-dir', path); 37 | } 38 | downloadTms(data) { 39 | if (Array.isArray(data.mapConfig.tileLayer) && data.mapConfig.tileLayer.length > 1) { 40 | if (data.mergeLayers) { 41 | new TileTMSListMerge(data, this.saveImagesAndMerge, this.ensureDirSync); 42 | } else { 43 | new TileTMSList(data, this.saveImage, this.ensureDirSync); 44 | } 45 | } else { 46 | new TileTMS(data); 47 | } 48 | 49 | } 50 | downloadBaidu(data) { 51 | new TileBaidu(data, this.saveImage, this.ensureDirSync); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /packages/renderer/src/utils/fileSaveBaidu.js: -------------------------------------------------------------------------------- 1 | // 瓦片转换 2 | import {TileTMS} from './fileSaveTms'; 3 | 4 | /** 5 | * 下载百度瓦片 6 | */ 7 | export default TileTMS; 8 | 9 | -------------------------------------------------------------------------------- /packages/renderer/src/utils/fileSaveTms.js: -------------------------------------------------------------------------------- 1 | // 瓦片转换 2 | import { setState, setProgress } from './progress'; 3 | import { downloadLoop, downloadClipLoop } from './download'; 4 | import {setMapLoading} from './baseMap.js'; 5 | 6 | /** 7 | * 下载TMS瓦片 8 | */ 9 | export class TileTMS { 10 | constructor(data) { 11 | this.rootPath = data.savePath; // 文件根目录 12 | this.maxZoom = data.maxZoom; 13 | this.minZoom = data.minZoom; 14 | this.imageType = data.imageType; 15 | this.tileLayer = data.mapConfig.tileLayer; 16 | this.downloadGeometry = data.downloadGeometry; 17 | this.downloadTiles(data.clipImage); 18 | } 19 | async downloadTiles(clipImage) { 20 | // 当前绝对路径 21 | const downloadPath = this.rootPath + '/'; 22 | // 下载范围 23 | const zmin = this.minZoom; 24 | const zmax = this.maxZoom + 1; 25 | const pictureType = '.' + this.imageType; 26 | // 遍历下载 27 | const option = { 28 | downloadPath, 29 | pictureType, 30 | imageType: this.imageType, 31 | }; 32 | if (clipImage) { 33 | option.clipImage = clipImage; 34 | option.tileLayer = this.tileLayer; 35 | option.downloadGeometry = this.downloadGeometry; 36 | } 37 | const statistics = {percentage: 0, count: 100}; 38 | setState(true); 39 | for (let z = zmin; z < zmax; z++) { 40 | statistics.percentage = Number(((z - zmin) / (zmax - zmin) * 100).toFixed(2)); 41 | setProgress(statistics); 42 | await this.tileLayer.downloadCascadeTiles(z, option); 43 | } 44 | statistics.percentage = 100; 45 | setProgress(statistics); 46 | setState(false); 47 | setMapLoading(false); 48 | window.$message.success('瓦片数据下载完成。'); 49 | } 50 | } 51 | 52 | /** 53 | * 下载TMS瓦片集合 54 | */ 55 | export class TileTMSList { 56 | constructor(data) { 57 | this.rootPath = data.savePath; // 文件根目录 58 | this.maxZoom = data.maxZoom; 59 | this.minZoom = data.minZoom; 60 | this.imageType = data.imageType; 61 | this.tileLayer = data.mapConfig.tileLayer; 62 | this.downloadGeometry = data.downloadGeometry; 63 | 64 | this.downloadLayers(data); 65 | } 66 | async downloadLayers(data) { 67 | setState(true); 68 | const statistics = {percentage: 0, count: 100}; 69 | for (let index = 0; index < data.mapConfig.tileLayer.length; index++) { 70 | const layer = data.mapConfig.tileLayer[index]; 71 | await this.downloadTiles(data.clipImage, layer, (index + 1) / (data.mapConfig.tileLayer.length) * 100); 72 | } 73 | statistics.percentage = 100; 74 | setProgress(statistics); 75 | setState(false); 76 | setMapLoading(false); 77 | window.$message.success('瓦片数据下载完成。'); 78 | } 79 | async downloadTiles(clipImage, tileLayer, count) { 80 | // 当前绝对路径 81 | const downloadPath = this.rootPath + '/' + tileLayer.config().style + '/'; 82 | // 下载范围 83 | const zmin = this.minZoom; 84 | const zmax = this.maxZoom + 1; 85 | const pictureType = '.' + this.imageType; 86 | // 遍历下载 87 | const option = { 88 | downloadPath, 89 | pictureType, 90 | imageType: this.imageType, 91 | }; 92 | if (clipImage) { 93 | option.clipImage = clipImage; 94 | option.tileLayer = tileLayer; 95 | option.downloadGeometry = this.downloadGeometry; 96 | } 97 | for (let z = zmin; z < zmax; z++) { 98 | const percentage = Number(((z - zmin) / (zmax - zmin) * count).toFixed(2)); 99 | setProgress({percentage}); 100 | await tileLayer.downloadCascadeTiles(z, option); 101 | } 102 | return Promise.resolve(); 103 | } 104 | } 105 | 106 | /** 107 | * 下载TMS瓦片集合,合并多张瓦片 108 | */ 109 | export class TileTMSListMerge { 110 | constructor(data, apiDownload, apiEnsureDirSync) { 111 | this.rootPath = data.savePath; // 文件根目录 112 | this.maxZoom = data.maxZoom; 113 | this.minZoom = data.minZoom; 114 | this.mapExtent = data.extent; 115 | this.imageType = data.imageType; 116 | this.apiEnsureDirSync = apiEnsureDirSync; 117 | this.tileLayer = data.mapConfig.tileLayer; 118 | setState(true); 119 | 120 | const list = this.calcTiles(); 121 | if (data.clipImage) { 122 | downloadClipLoop(list, apiDownload, this.tileLayer[0], data.downloadGeometry, this.imageType); 123 | } else { 124 | downloadLoop(list, apiDownload); 125 | } 126 | } 127 | calcTiles() { 128 | // 当前绝对路径 129 | const downloadPath = this.rootPath + '/'; 130 | 131 | // 下载范围 132 | const zmin = this.minZoom; 133 | const zmax = this.maxZoom + 1; 134 | // 下载地址 135 | const pictureType = '.' + this.imageType; 136 | 137 | const imgLyr = this.tileLayer.find(t => { return !t.config().style.includes('_Label'); }); 138 | const imgLyrLabel = this.tileLayer.find(t => { return t.config().style.includes('_Label'); }); 139 | const storeMap = {}; 140 | for (let z = zmin; z < zmax; z++) { 141 | const tileGridsList = imgLyr._getCascadeTiles(z).tileGrids; 142 | tileGridsList.forEach(tileGrids => { 143 | for (let x = 0; x < tileGrids.tiles.length; x++) { 144 | const tile = tileGrids.tiles[x]; 145 | const temppath = downloadPath + tile.z + '/' + tile.x; 146 | this.apiEnsureDirSync(temppath); 147 | const savePath = temppath + '/' + tile.y + pictureType; 148 | 149 | storeMap[`${tile.x}${tile.y}${tile.z}`] = {zoom: tile.z, layers:[ 150 | { 151 | url: tile.url, 152 | isLabel: false, 153 | }, 154 | ], savePath, x:tile.x, y:tile.y}; 155 | } 156 | }); 157 | } 158 | for (let z = zmin; z < zmax; z++) { 159 | const tileGridsList = imgLyrLabel._getCascadeTiles(z).tileGrids; 160 | tileGridsList.forEach(tileGrids => { 161 | for (let x = 0; x < tileGrids.tiles.length; x++) { 162 | const tile = tileGrids.tiles[x]; 163 | storeMap[`${tile.x}${tile.y}${tile.z}`].layers.push({ 164 | url: tile.url, 165 | isLabel: true, 166 | }); 167 | } 168 | }); 169 | } 170 | setMapLoading(false); 171 | return Object.values(storeMap); 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /packages/renderer/src/utils/mapKey.js: -------------------------------------------------------------------------------- 1 | // 地图Key 2 | 3 | const TDTKEY = 'map-key-tdt'; 4 | const MAPBOXKEY = 'map-key-mapbox'; 5 | 6 | /** 7 | * 获取地图key 8 | * @returns 地图key 9 | */ 10 | export function getKeys() { 11 | return { 12 | tdtKey: localStorage.getItem(TDTKEY), 13 | mapboxKey: localStorage.getItem(MAPBOXKEY), 14 | }; 15 | } 16 | /** 17 | * 设置key 18 | * @param {*} param key对象 19 | */ 20 | export function setKeys(param) { 21 | localStorage.setItem(TDTKEY, param?.tdtKey); 22 | localStorage.setItem(MAPBOXKEY, param?.mapboxKey); 23 | } 24 | -------------------------------------------------------------------------------- /packages/renderer/src/utils/progress.js: -------------------------------------------------------------------------------- 1 | // 下载进度 2 | let downloading = false; // 下载状态 3 | const statistics = {success: 0, error: 0, percentage: 0, count: 0}; // 进度统计 4 | let progressDom = null; 5 | let successDom = null; 6 | let errorDom = null; 7 | let containerDom = null; 8 | export function getState() { 9 | return downloading; 10 | } 11 | export function setState(val) { 12 | downloading = val; 13 | if (val) { 14 | setProgress({success: 0, error: 0, percentage: 0}); 15 | showProgress(true); 16 | } 17 | } 18 | export function getProgress() { 19 | return statistics; 20 | } 21 | export function setProgress(val) { 22 | const {success, error, percentage, count} = val; 23 | if (typeof success !== 'undefined') statistics.success = success; 24 | if (typeof error !== 'undefined') statistics.error = error; 25 | if (typeof percentage !== 'undefined') statistics.percentage = percentage; 26 | if (typeof count !== 'undefined') statistics.count = count; 27 | updateProgress(); 28 | } 29 | export function setProgressDom(val) { 30 | progressDom = val.progress; 31 | successDom = val.success; 32 | errorDom = val.error; 33 | containerDom = val.container; 34 | } 35 | export function showProgress(visible) { 36 | containerDom.style.display = visible ? 'block' : 'none'; 37 | } 38 | function updateProgress() { 39 | progressDom.value = statistics.percentage; 40 | // successDom.innerText = `${statistics.success}/${statistics.count}`; 41 | successDom.innerText = `${statistics.success}`; 42 | errorDom.innerText = statistics.error; 43 | } 44 | 45 | export function progressAddSuccess() { 46 | statistics.success++; 47 | updateProgress(); 48 | } 49 | export function progressAddError() { 50 | statistics.error++; 51 | updateProgress(); 52 | } 53 | -------------------------------------------------------------------------------- /packages/renderer/src/utils/random.js: -------------------------------------------------------------------------------- 1 | // 随机数 + 唯一id 2 | 3 | /** 4 | * 生成唯一id 5 | */ 6 | export function uuid() { 7 | const temp_url = URL.createObjectURL(new Blob()); 8 | const temp_uuid = temp_url.toString(); // blob:https://xxx.com/b250d159-e1b6-4a87-9002-885d90033be3 9 | URL.revokeObjectURL(temp_uuid); 10 | return temp_uuid.substr(temp_uuid.lastIndexOf('/') + 1); 11 | } 12 | -------------------------------------------------------------------------------- /packages/renderer/src/utils/tileBaidu.js: -------------------------------------------------------------------------------- 1 | // 瓦片转换 2 | import { setState } from './progress'; 3 | import { downloadLoop } from './download'; 4 | 5 | // const x_pi = Math.PI * 3000.0 / 180.0; 6 | // const pi = Math.PI; // π 7 | // const a = 6378245.0; // 长半轴 8 | // const ee = 0.00669342162296594323; // 扁率 9 | // 百度墨卡托投影纠正矩阵 10 | const LLBAND = [75, 60, 45, 30, 15, 0]; 11 | const LL2MC = [ 12 | [-0.0015702102444, 111320.7020616939, 1704480524535203, -10338987376042340, 26112667856603880, -35149669176653700, 13 | 26595700718403920, -10725012454188240, 1800819912950474, 82.5], 14 | [0.0008277824516172526, 111320.7020463578, 647795574.6671607, -4082003173.641316, 10774905663.51142, 15 | -15171875531.51559, 12053065338.62167, -5124939663.577472, 913311935.9512032, 67.5], 16 | [0.00337398766765, 111320.7020202162, 4481351.045890365, -23393751.19931662, 79682215.47186455, -115964993.2797253, 17 | 97236711.15602145, -43661946.33752821, 8477230.501135234, 52.5], 18 | [0.00220636496208, 111320.7020209128, 51751.86112841131, 3796837.749470245, 992013.7397791013, -1221952.21711287, 19 | 1340652.697009075, -620943.6990984312, 144416.9293806241, 37.5], 20 | [-0.0003441963504368392, 111320.7020576856, 278.2353980772752, 2485758.690035394, 6070.750963243378, 21 | 54821.18345352118, 9540.606633304236, -2710.55326746645, 1405.483844121726, 22.5], 22 | [-0.0003218135878613132, 111320.7020701615, 0.00369383431289, 823725.6402795718, 0.46104986909093, 23 | 2351.343141331292, 1.58060784298199, 8.77738589078284, 0.37238884252424, 7.45]]; 24 | // 百度墨卡托转回到百度经纬度纠正矩阵 25 | // const MCBAND = [12890594.86, 8362377.87, 5591021, 3481989.83, 1678043.12, 0]; 26 | // const MC2LL = [[1.410526172116255e-8, 0.00000898305509648872, -1.9939833816331, 200.9824383106796, -187.2403703815547, 27 | // 91.6087516669843, -23.38765649603339, 2.57121317296198, -0.03801003308653, 17337981.2], 28 | // [-7.435856389565537e-9, 0.000008983055097726239, -0.78625201886289, 96.32687599759846, -1.85204757529826, 29 | // -59.36935905485877, 47.40033549296737, -16.50741931063887, 2.28786674699375, 10260144.86], 30 | // [-3.030883460898826e-8, 0.00000898305509983578, 0.30071316287616, 59.74293618442277, 7.357984074871, 31 | // -25.38371002664745, 13.45380521110908, -3.29883767235584, 0.32710905363475, 6856817.37], 32 | // [-1.981981304930552e-8, 0.000008983055099779535, 0.03278182852591, 40.31678527705744, 0.65659298677277, 33 | // -4.44255534477492, 0.85341911805263, 0.12923347998204, -0.04625736007561, 4482777.06], 34 | // [3.09191371068437e-9, 0.000008983055096812155, 0.00006995724062, 23.10934304144901, -0.00023663490511, 35 | // -0.6321817810242, -0.00663494467273, 0.03430082397953, -0.00466043876332, 2555164.4], 36 | // [2.890871144776878e-9, 0.000008983055095805407, -3.068298e-8, 7.47137025468032, -0.00000353937994, 37 | // -0.02145144861037, -0.00001234426596, 0.00010322952773, -0.00000323890364, 826088.5]]; 38 | 39 | function getRange(cC, cB, T) { 40 | if (cB != null && cB !== undefined) cC = Math.max(cC, cB); 41 | if (T != null && T !== undefined) cC = Math.min(cC, T); 42 | return cC; 43 | } 44 | 45 | 46 | function getLoop(cC, cB, T) { 47 | while (cC > T) { 48 | cC -= T - cB; 49 | } 50 | while (cC < cB) { 51 | cC += T - cB; 52 | } 53 | return cC; 54 | } 55 | 56 | 57 | function convertor(cC, cD) { 58 | if (cC == null || cD == null) { 59 | return null; 60 | } 61 | let T = cD[0] + cD[1] * Math.abs(cC.x); 62 | const cB = Math.abs(cC.y) / cD[9]; 63 | let cE = cD[2] + cD[3] * cB + cD[4] * cB * cB + cD[5] * cB * cB * cB + cD[6] * cB * cB * cB * cB + cD[ 64 | 7] * cB * cB * cB * cB * cB + cD[8] * cB * cB * cB * cB * cB * cB; 65 | if (cC.x < 0) { 66 | T = T * -1; 67 | } 68 | else { 69 | // T = T; 70 | } 71 | if (cC.y < 0) { 72 | cE = cE * -1; 73 | } 74 | else { 75 | // cE = cE; 76 | } 77 | return [T, cE]; 78 | } 79 | 80 | 81 | function convertLL2MC(T) { 82 | let cD = null; 83 | T.x = getLoop(T.x, -180, 180); 84 | T.y = getRange(T.y, -74, 74); 85 | let cB = T; 86 | for (let cC = 0; cC < LLBAND.length; cC++) { 87 | if (cB.y >= LLBAND[cC]) { 88 | cD = LL2MC[cC]; 89 | break; 90 | } 91 | } 92 | if (cD != null) { 93 | for (let cC = LLBAND.length - 1; cC > -1; cC--) { 94 | if (cB.y <= -LLBAND[cC]) { 95 | cD = LL2MC[cC]; 96 | break; 97 | } 98 | } 99 | } 100 | const cE = convertor(T, cD); 101 | return cE; 102 | } 103 | 104 | function LLT(x, y) { 105 | this.x = x; 106 | this.y = y; 107 | } 108 | // bd09投影到百度墨卡托 109 | function bd09tomercator(lng, lat) { 110 | const baidut = new LLT(lng, lat); 111 | return convertLL2MC(baidut); 112 | } 113 | 114 | function getResolution(level) { 115 | return Math.pow(2, (level - 18)); 116 | } 117 | 118 | // 经纬度转瓦片行列号 119 | function lngToTileX(lng, level) { 120 | const point = bd09tomercator(lng, 0); 121 | return Math.floor(point[0] * getResolution(level) / 256); 122 | } 123 | // 经纬度转瓦片行列号 124 | function latToTileY(lat, level) { 125 | const point = bd09tomercator(0, lat); 126 | return Math.floor(point[1] * getResolution(level) / 256); 127 | } 128 | class TileBaidu { 129 | constructor(data, apiDownload, apiEnsureDirSync) { 130 | this.apiDownload = apiDownload; 131 | this.rootPath = data.savePath; // 文件根目录 132 | this.maxZoom = data.maxZoom; 133 | this.minZoom = data.minZoom; 134 | this.mapExtent = data.extent; 135 | // this.projection = data.mapConfig.projection.code; // BAIDU,EPSG:4326,EPSG:3857 136 | this.urlTemplate = data.mapConfig.config.urlTemplate; 137 | this.apiEnsureDirSync = apiEnsureDirSync; 138 | this.tileLayer = data.mapConfig.tileLayer; 139 | setState(true); 140 | downloadLoop(this.calcTiles(), this.apiDownload); 141 | } 142 | calcTiles() { 143 | // 当前绝对路径 144 | const downloadPath = this.rootPath + '/'; 145 | 146 | // 下载范围 147 | const zmin = this.minZoom; 148 | const zmax = this.maxZoom + 1; 149 | const south_edge = this.mapExtent.ymin; 150 | const north_edge = this.mapExtent.ymax; 151 | const west_edge = this.mapExtent.xmin; 152 | const east_edge = this.mapExtent.xmax; 153 | // 下载地址 154 | // const baseUrl = this.urlTemplate; 155 | const pictureType = '.png'; 156 | // 遍历URL,获取数据 157 | const list = []; 158 | for (let z = zmin; z < zmax; z++) { 159 | const top_tile = latToTileY(north_edge, z); 160 | const left_tile = lngToTileX(west_edge, z); 161 | const bottom_tile = latToTileY(south_edge, z); 162 | const right_tile = lngToTileX(east_edge, z); 163 | const minLong = Math.min(left_tile, right_tile); 164 | const maxLong = Math.max(left_tile, right_tile); 165 | let minLat = Math.min(bottom_tile, top_tile); 166 | if (minLat < 0) minLat = 0; 167 | const maxLat = Math.max(bottom_tile, top_tile); 168 | for (let x = minLong; x < maxLong; x++) { 169 | const temppath = downloadPath + z + '/' + x; 170 | this.apiEnsureDirSync(temppath); 171 | for (let y = minLat; y < maxLat; y++) { 172 | // const str3 = baseUrl.replace('{z}', z).replace('{x}', x).replace('{y}', y); 173 | const str3 = this.tileLayer.getTileUrl(x, y, z); 174 | const path2 = temppath + '/' + y + pictureType; 175 | list.push({ zoom: z, url: str3, savePath: path2 }); 176 | } 177 | } 178 | } 179 | return list; 180 | } 181 | } 182 | 183 | export default TileBaidu; 184 | -------------------------------------------------------------------------------- /packages/renderer/src/utils/tileTms.js: -------------------------------------------------------------------------------- 1 | // 瓦片转换 2 | import { setState } from './progress'; 3 | import { downloadLoop } from './download'; 4 | // 经纬度转瓦片行列号 5 | function long2tile(lon, zoom) { 6 | return (Math.floor((lon + 180) / 360 * Math.pow(2, zoom))); 7 | } 8 | 9 | // 经纬度转瓦片行列号Google 10 | // eslint-disable-next-line 11 | function lat2tileGoogle(lat, zoom) { 12 | return (Math.floor((1 - Math.log(Math.tan(lat * Math.PI / 180) + 1 / Math.cos(lat * Math.PI / 180)) / Math.PI) / 2 * Math.pow(2, zoom))); 13 | } 14 | // 经纬度转瓦片行列号TMS 15 | // eslint-disable-next-line 16 | function lat2tileTMS(lat, zoom) { 17 | return ((1 << zoom) - (Math.floor((1 - Math.log(Math.tan(lat * Math.PI / 180) + 1 / Math.cos(lat * Math.PI / 180)) / Math.PI) / 2 * Math.pow(2, zoom))) - 1); 18 | } 19 | /** 20 | * 下载TMS瓦片 21 | */ 22 | export class TileTMS { 23 | constructor(data, apiDownload, apiEnsureDirSync) { 24 | this.apiDownload = apiDownload; 25 | this.rootPath = data.savePath; // 文件根目录 26 | this.maxZoom = data.maxZoom; 27 | this.minZoom = data.minZoom; 28 | this.mapExtent = data.extent; 29 | // this.projection = data.mapConfig.projection.code; // BAIDU,EPSG:4326,EPSG:3857 30 | this.urlTemplate = data.mapConfig.config.urlTemplate; 31 | this.apiEnsureDirSync = apiEnsureDirSync; 32 | this.tileLayer = data.mapConfig.tileLayer; 33 | setState(true); 34 | downloadLoop(this.calcTiles(), this.apiDownload); 35 | } 36 | calcTiles() { 37 | // 当前绝对路径 38 | const downloadPath = this.rootPath + '/'; 39 | 40 | // 下载范围 41 | const zmin = this.minZoom; 42 | const zmax = this.maxZoom + 1; 43 | const south_edge = this.mapExtent.ymin; 44 | const north_edge = this.mapExtent.ymax; 45 | const west_edge = this.mapExtent.xmin; 46 | const east_edge = this.mapExtent.xmax; 47 | // 下载地址 48 | // const baseUrl = this.urlTemplate; 49 | const pictureType = '.png'; 50 | // 遍历URL,获取数据 51 | const list = []; 52 | for (let z = zmin; z < zmax; z++) { 53 | const top_tile = lat2tileGoogle(north_edge, z); 54 | const left_tile = long2tile(west_edge, z); 55 | const bottom_tile = lat2tileGoogle(south_edge, z); 56 | const right_tile = long2tile(east_edge, z); 57 | const minLong = Math.min(left_tile, right_tile); 58 | const maxLong = Math.max(left_tile, right_tile); 59 | let minLat = Math.min(bottom_tile, top_tile); 60 | if (minLat < 0) minLat = 0; 61 | const maxLat = Math.max(bottom_tile, top_tile); 62 | for (let x = minLong; x < maxLong; x++) { 63 | const temppath = downloadPath + z + '/' + x; 64 | this.apiEnsureDirSync(temppath); 65 | for (let y = minLat; y < maxLat; y++) { 66 | // const str3 = baseUrl.replace('{z}', z).replace('{x}', x).replace('{y}', y); 67 | const str3 = this.tileLayer.getTileUrl(x, y, z); 68 | const path2 = temppath + '/' + y + pictureType; 69 | list.push({zoom: z, url:str3, savePath:path2}); 70 | } 71 | } 72 | } 73 | return list; 74 | } 75 | } 76 | 77 | /** 78 | * 下载TMS瓦片集合 79 | */ 80 | export class TileTMSList { 81 | constructor(data, apiDownload, apiEnsureDirSync) { 82 | this.apiDownload = apiDownload; 83 | this.rootPath = data.savePath; // 文件根目录 84 | this.maxZoom = data.maxZoom; 85 | this.minZoom = data.minZoom; 86 | this.mapExtent = data.extent; 87 | this.apiEnsureDirSync = apiEnsureDirSync; 88 | this.tileLayer = data.mapConfig.tileLayer; 89 | setState(true); 90 | 91 | let list = []; 92 | data.mapConfig.tileLayer.forEach(layer => { 93 | list = [...list, ...this.calcTiles(layer.config().style, layer)]; 94 | }); 95 | downloadLoop(list, this.apiDownload); 96 | } 97 | calcTiles(subpath, layer) { 98 | // 当前绝对路径 99 | const downloadPath = this.rootPath + '/' + subpath + '/'; 100 | 101 | // 下载范围 102 | const zmin = this.minZoom; 103 | const zmax = this.maxZoom + 1; 104 | const south_edge = this.mapExtent.ymin; 105 | const north_edge = this.mapExtent.ymax; 106 | const west_edge = this.mapExtent.xmin; 107 | const east_edge = this.mapExtent.xmax; 108 | // 下载地址 109 | const pictureType = '.png'; 110 | // 遍历URL,获取数据 111 | const list = []; 112 | for (let z = zmin; z < zmax; z++) { 113 | const top_tile = lat2tileGoogle(north_edge, z); 114 | const left_tile = long2tile(west_edge, z); 115 | const bottom_tile = lat2tileGoogle(south_edge, z); 116 | const right_tile = long2tile(east_edge, z); 117 | const minLong = Math.min(left_tile, right_tile); 118 | const maxLong = Math.max(left_tile, right_tile); 119 | let minLat = Math.min(bottom_tile, top_tile); 120 | if (minLat < 0) minLat = 0; 121 | const maxLat = Math.max(bottom_tile, top_tile); 122 | for (let x = minLong; x < maxLong; x++) { 123 | const temppath = downloadPath + z + '/' + x; 124 | this.apiEnsureDirSync(temppath); 125 | for (let y = minLat; y < maxLat; y++) { 126 | const str3 = layer.getTileUrl(x, y, z); 127 | const path2 = temppath + '/' + y + pictureType; 128 | list.push({zoom: z, url:str3, savePath:path2}); 129 | } 130 | } 131 | } 132 | return list; 133 | } 134 | } 135 | 136 | /** 137 | * 下载TMS瓦片集合,合并多张瓦片 138 | */ 139 | export class TileTMSListMerge { 140 | constructor(data, apiDownload, apiEnsureDirSync) { 141 | this.apiDownload = apiDownload; 142 | this.rootPath = data.savePath; // 文件根目录 143 | this.maxZoom = data.maxZoom; 144 | this.minZoom = data.minZoom; 145 | this.mapExtent = data.extent; 146 | this.apiEnsureDirSync = apiEnsureDirSync; 147 | this.tileLayer = data.mapConfig.tileLayer; 148 | setState(true); 149 | 150 | downloadLoop(this.calcTiles(data.mapConfig.tileLayer), this.apiDownload); 151 | } 152 | calcTiles(layers) { 153 | // 当前绝对路径 154 | const downloadPath = this.rootPath + '/'; 155 | 156 | // 下载范围 157 | const zmin = this.minZoom; 158 | const zmax = this.maxZoom + 1; 159 | const south_edge = this.mapExtent.ymin; 160 | const north_edge = this.mapExtent.ymax; 161 | const west_edge = this.mapExtent.xmin; 162 | const east_edge = this.mapExtent.xmax; 163 | // 下载地址 164 | const pictureType = '.png'; 165 | // 遍历URL,获取数据 166 | const list = []; 167 | for (let z = zmin; z < zmax; z++) { 168 | const top_tile = lat2tileGoogle(north_edge, z); 169 | const left_tile = long2tile(west_edge, z); 170 | const bottom_tile = lat2tileGoogle(south_edge, z); 171 | const right_tile = long2tile(east_edge, z); 172 | const minLong = Math.min(left_tile, right_tile); 173 | const maxLong = Math.max(left_tile, right_tile); 174 | let minLat = Math.min(bottom_tile, top_tile); 175 | if (minLat < 0) minLat = 0; 176 | const maxLat = Math.max(bottom_tile, top_tile); 177 | for (let x = minLong; x < maxLong; x++) { 178 | const temppath = downloadPath + z + '/' + x; 179 | this.apiEnsureDirSync(temppath); 180 | for (let y = minLat; y < maxLat; y++) { 181 | const str3 = layers.map(ll => {return {url: ll.getTileUrl(x, y, z), isLabel: ll.config().style.includes('_Label')};}); 182 | const path2 = temppath + '/' + y + pictureType; 183 | list.push({zoom: z, layers:str3, savePath:path2}); 184 | } 185 | } 186 | } 187 | return list; 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /packages/renderer/vite.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | 3 | import {chrome} from '../../.electron-vendors.cache.json'; 4 | import {join} from 'path'; 5 | import {builtinModules} from 'module'; 6 | import vue from '@vitejs/plugin-vue'; 7 | 8 | const PACKAGE_ROOT = __dirname; 9 | 10 | const config = { 11 | mode: process.env.MODE, 12 | root: PACKAGE_ROOT, 13 | resolve: { 14 | alias: { 15 | '/@/': join(PACKAGE_ROOT, 'src') + '/', 16 | }, 17 | }, 18 | plugins: [vue()], 19 | base: '', 20 | server: { 21 | fs: { 22 | strict: true, 23 | }, 24 | }, 25 | build: { 26 | sourcemap: true, 27 | target: `chrome${chrome}`, 28 | outDir: 'dist', 29 | assetsDir: '.', 30 | rollupOptions: { 31 | external: [ 32 | ...builtinModules, 33 | ], 34 | }, 35 | emptyOutDir: true, 36 | brotliSize: false, 37 | }, 38 | }; 39 | 40 | export default config; 41 | -------------------------------------------------------------------------------- /scripts/build.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | const {build} = require('vite'); 3 | const {dirname} = require('path'); 4 | 5 | /** @type 'production' | 'development' */ 6 | const mode = process.env.MODE = process.env.MODE || 'production'; 7 | 8 | const packagesConfigs = [ 9 | 'packages/main/vite.config.js', 10 | 'packages/preload/vite.config.js', 11 | 'packages/renderer/vite.config.js', 12 | ]; 13 | 14 | 15 | /** 16 | * Run `vite build` for config file 17 | */ 18 | const buildByConfig = (configFile) => build({configFile, mode}); 19 | (async () => { 20 | try { 21 | const totalTimeLabel = 'Total bundling time'; 22 | console.time(totalTimeLabel); 23 | 24 | for (const packageConfigPath of packagesConfigs) { 25 | 26 | const consoleGroupName = `${dirname(packageConfigPath)}/`; 27 | console.group(consoleGroupName); 28 | 29 | const timeLabel = 'Bundling time'; 30 | console.time(timeLabel); 31 | 32 | await buildByConfig(packageConfigPath); 33 | 34 | console.timeEnd(timeLabel); 35 | console.groupEnd(); 36 | console.log('\n'); // Just for pretty print 37 | } 38 | console.timeEnd(totalTimeLabel); 39 | } catch (e) { 40 | console.error(e); 41 | process.exit(1); 42 | } 43 | })(); 44 | -------------------------------------------------------------------------------- /scripts/update-electron-vendors.js: -------------------------------------------------------------------------------- 1 | const {writeFile, readFile} = require('fs/promises'); 2 | const {execSync} = require('child_process'); 3 | const electron = require('electron'); 4 | const path = require('path'); 5 | 6 | /** 7 | * Returns versions of electron vendors 8 | * The performance of this feature is very poor and can be improved 9 | * @see https://github.com/electron/electron/issues/28006 10 | * 11 | * @returns {NodeJS.ProcessVersions} 12 | */ 13 | function getVendors() { 14 | const output = execSync(`${electron} -p "JSON.stringify(process.versions)"`, { 15 | env: {'ELECTRON_RUN_AS_NODE': '1'}, 16 | encoding: 'utf-8', 17 | }); 18 | 19 | return JSON.parse(output); 20 | } 21 | 22 | 23 | function formattedJSON(obj) { 24 | return JSON.stringify(obj, null, 2) + '\n'; 25 | } 26 | 27 | function updateVendors() { 28 | const electronRelease = getVendors(); 29 | 30 | const nodeMajorVersion = electronRelease.node.split('.')[0]; 31 | const chromeMajorVersion = electronRelease.v8.split('.')[0] + electronRelease.v8.split('.')[1]; 32 | 33 | const packageJSONPath = path.resolve(process.cwd(), 'package.json'); 34 | 35 | return Promise.all([ 36 | writeFile('./.electron-vendors.cache.json', 37 | formattedJSON({ 38 | chrome: chromeMajorVersion, 39 | node: nodeMajorVersion, 40 | }), 41 | ), 42 | 43 | readFile(packageJSONPath).then(JSON.parse).then((packageJSON) => { 44 | if (!packageJSON || !Array.isArray(packageJSON.browserslist)) { 45 | throw new Error(`Can't find browserslist in ${packageJSONPath}`); 46 | } 47 | 48 | packageJSON.browserslist = [`Chrome ${chromeMajorVersion}`]; 49 | 50 | return writeFile(packageJSONPath, formattedJSON(packageJSON)); 51 | }), 52 | ]); 53 | } 54 | 55 | updateVendors().catch(err => { 56 | console.error(err); 57 | process.exit(1); 58 | }); 59 | -------------------------------------------------------------------------------- /scripts/watch.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const {createServer, build, createLogger} = require('vite'); 4 | const electronPath = require('electron'); 5 | const {spawn} = require('child_process'); 6 | 7 | 8 | /** @type 'production' | 'development'' */ 9 | const mode = process.env.MODE = process.env.MODE || 'development'; 10 | 11 | 12 | /** @type {import('vite').LogLevel} */ 13 | const LOG_LEVEL = 'info'; 14 | 15 | 16 | /** @type {import('vite').InlineConfig} */ 17 | const sharedConfig = { 18 | mode, 19 | build: { 20 | watch: {}, 21 | }, 22 | logLevel: LOG_LEVEL, 23 | }; 24 | 25 | /** Messages on stderr that match any of the contained patterns will be stripped from output */ 26 | const stderrFilterPatterns = [ 27 | // warning about devtools extension 28 | // https://github.com/cawa-93/vite-electron-builder/issues/492 29 | // https://github.com/MarshallOfSound/electron-devtools-installer/issues/143 30 | /ExtensionLoadWarning/, 31 | ]; 32 | 33 | /** 34 | * 35 | * @param {{name: string; configFile: string; writeBundle: import('rollup').OutputPlugin['writeBundle'] }} param0 36 | * @returns {import('rollup').RollupWatcher} 37 | */ 38 | const getWatcher = ({name, configFile, writeBundle}) => { 39 | return build({ 40 | ...sharedConfig, 41 | configFile, 42 | plugins: [{name, writeBundle}], 43 | }); 44 | }; 45 | 46 | 47 | /** 48 | * Start or restart App when source files are changed 49 | * @param {import('vite').ViteDevServer} viteDevServer 50 | * @returns {Promise | import('vite').RollupWatcher>} 51 | */ 52 | const setupMainPackageWatcher = (viteDevServer) => { 53 | // Write a value to an environment variable to pass it to the main process. 54 | { 55 | const protocol = `http${viteDevServer.config.server.https ? 's' : ''}:`; 56 | const host = viteDevServer.config.server.host || 'localhost'; 57 | const port = viteDevServer.config.server.port; // Vite searches for and occupies the first free port: 3000, 3001, 3002 and so on 58 | const path = '/'; 59 | process.env.VITE_DEV_SERVER_URL = `${protocol}//${host}:${port}${path}`; 60 | } 61 | 62 | const logger = createLogger(LOG_LEVEL, { 63 | prefix: '[main]', 64 | }); 65 | 66 | /** @type {ChildProcessWithoutNullStreams | null} */ 67 | let spawnProcess = null; 68 | 69 | return getWatcher({ 70 | name: 'reload-app-on-main-package-change', 71 | configFile: 'packages/main/vite.config.js', 72 | writeBundle() { 73 | if (spawnProcess !== null) { 74 | spawnProcess.kill('SIGINT'); 75 | spawnProcess = null; 76 | } 77 | 78 | spawnProcess = spawn(String(electronPath), ['.']); 79 | 80 | spawnProcess.stdout.on('data', d => d.toString().trim() && logger.warn(d.toString(), {timestamp: true})); 81 | spawnProcess.stderr.on('data', d => { 82 | const data = d.toString().trim(); 83 | if (!data) return; 84 | const mayIgnore = stderrFilterPatterns.some((r) => r.test(data)); 85 | if (mayIgnore) return; 86 | logger.error(data, { timestamp: true }); 87 | }); 88 | }, 89 | }); 90 | }; 91 | 92 | 93 | /** 94 | * Start or restart App when source files are changed 95 | * @param {import('vite').ViteDevServer} viteDevServer 96 | * @returns {Promise | import('vite').RollupWatcher>} 97 | */ 98 | const setupPreloadPackageWatcher = (viteDevServer) => { 99 | return getWatcher({ 100 | name: 'reload-page-on-preload-package-change', 101 | configFile: 'packages/preload/vite.config.js', 102 | writeBundle() { 103 | viteDevServer.ws.send({ 104 | type: 'full-reload', 105 | }); 106 | }, 107 | }); 108 | }; 109 | 110 | (async () => { 111 | try { 112 | const viteDevServer = await createServer({ 113 | ...sharedConfig, 114 | configFile: 'packages/renderer/vite.config.js', 115 | }); 116 | 117 | await viteDevServer.listen(); 118 | 119 | await setupPreloadPackageWatcher(viteDevServer); 120 | await setupMainPackageWatcher(viteDevServer); 121 | } catch (e) { 122 | console.error(e); 123 | process.exit(1); 124 | } 125 | })(); 126 | -------------------------------------------------------------------------------- /tests/app.spec.js: -------------------------------------------------------------------------------- 1 | const {_electron: electron} = require('playwright'); 2 | const {strict: assert} = require('assert'); 3 | 4 | // Playwright has EXPERIMENTAL electron support. 5 | (async () => { 6 | const electronApp = await electron.launch({args: ['.']}); 7 | 8 | /** 9 | * App main window state 10 | * @type {{isVisible: boolean; isDevToolsOpened: boolean; isCrashed: boolean}} 11 | */ 12 | const windowState = await electronApp.evaluate(({BrowserWindow}) => { 13 | const mainWindow = BrowserWindow.getAllWindows()[0]; 14 | 15 | const getState = () => ({ 16 | isVisible: mainWindow.isVisible(), 17 | isDevToolsOpened: mainWindow.webContents.isDevToolsOpened(), 18 | isCrashed: mainWindow.webContents.isCrashed(), 19 | }); 20 | 21 | return new Promise((resolve) => { 22 | if (mainWindow.isVisible()) { 23 | resolve(getState()); 24 | } else 25 | mainWindow.once('ready-to-show', () => setTimeout(() => resolve(getState()), 0)); 26 | }); 27 | }); 28 | 29 | // Check main window state 30 | assert.ok(windowState.isVisible, 'Main window not visible'); 31 | assert.ok(!windowState.isDevToolsOpened, 'DevTools opened'); 32 | assert.ok(!windowState.isCrashed, 'Window crashed'); 33 | 34 | /** 35 | * Rendered Main window web-page 36 | * @type {Page} 37 | */ 38 | const page = await electronApp.firstWindow(); 39 | 40 | 41 | // Check web-page content 42 | const element = await page.$('#app', {strict: true}); 43 | assert.notStrictEqual(element, null, 'Can\'t find root element'); 44 | assert.notStrictEqual((await element.innerHTML()).trim(), '', 'Window content is empty'); 45 | 46 | 47 | // Checking the framework. 48 | // It is assumed that on the main screen there is a `