WME GIS Layers

Adds GIS layers in WME

当前为 2018-09-25 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name WME GIS Layers
  3. // @namespace https://greasyfork.org/users/45389
  4. // @version 2018.09.24.001
  5. // @description Adds GIS layers in WME
  6. // @author MapOMatic
  7. // @include /^https:\/\/(www|beta)\.waze\.com\/(?!user\/)(.{2,6}\/)?editor\/?.*$/
  8. // @require https://greasyfork.org/scripts/24851-wazewrap/code/WazeWrap.js
  9. // @grant GM_xmlhttpRequest
  10. // @grant GM_info
  11. // @license GNU GPLv3
  12. // @contributionURL https://github.com/WazeDev/Thank-The-Authors
  13. // @connect *
  14. // @connect tigerweb.geo.census.gov
  15. // @connect 131.156.137.22
  16. // @connect 131.187.129.114
  17. // @connect 136.234.11.196
  18. // @connect 136.234.13.165
  19. // @connect 161.6.109.206
  20. // @connect 184.12.255.122
  21. // @connect 204.63.176.116
  22. // @connect 206.176.83.74
  23. // @connect 206.74.124.99
  24. // @connect 216.110.207.61
  25. // @connect 216.81.174.57
  26. // @connect 23.96.59.134
  27. // @connect 50.120.220.154
  28. // @connect 52.37.30.30
  29. // @connect 54.213.14.253
  30. // @connect 63.238.120.156
  31. // @connect 72.10.206.73
  32. // @connect adairgis.integritygis.com
  33. // @connect agis.charlottecountyfl.gov
  34. // @connect ags.agdmaps.com
  35. // @connect ags.colliergov.net
  36. // @connect ags.kitsapgov.com
  37. // @connect ags10s1.dot.illinois.gov
  38. // @connect ags2.scgov.net
  39. // @connect ags2maps.srcity.org
  40. // @connect ags3.lincoln.ne.gov
  41. // @connect al.decatur.geopowered.com
  42. // @connect aldotgis.dot.state.al.us
  43. // @connect alleganygis.allconet.org
  44. // @connect alphagis.alpharetta.ga.us
  45. // @connect andrewgis.integritygis.com
  46. // @connect apnsgis1.apsu.edu
  47. // @connect app.mdt.mt.gov
  48. // @connect apps.douglas.co.us
  49. // @connect apps.fs.usda.gov
  50. // @connect apps.lcounty.com
  51. // @connect apps.willcogis.org
  52. // @connect arc2000.florenceco.org
  53. // @connect arcgis.atlantaregional.com
  54. // @connect arcgis.cityofcapegirardeau.org
  55. // @connect arcgis.co.beltrami.mn.us
  56. // @connect arcgis.lakecountyohio.gov
  57. // @connect arcgis.mobile311.com
  58. // @connect arcgis.sd.gov
  59. // @connect arcgis.sprocketcomm.com
  60. // @connect arcgis.tampagov.net
  61. // @connect arcgis.tuscaloosa-al.gov
  62. // @connect arcgis.waxahachie.com
  63. // @connect arcgis.wycokck.org
  64. // @connect arcgis2.williamsoncounty-tn.gov
  65. // @connect arcgis4.roktech.net
  66. // @connect arcgisce.co.valencia.nm.us
  67. // @connect arcgiswap01.ci.temple.tx.us
  68. // @connect arcgisweb.co.fort-bend.tx.us
  69. // @connect arcgisweb.countyofnewaygo.com
  70. // @connect arcmobile.co.albany.wy.us
  71. // @connect arcserv.co.washington.ar.us
  72. // @connect arcserve1.hornershifrin.com
  73. // @connect arcserver.madisoncountyky.us
  74. // @connect arcweb.hcad.org
  75. // @connect arcweb.ycpc.org
  76. // @connect ash.bernco.gov
  77. // @connect atchisongis.integritygis.com
  78. // @connect atlas.co.anson.nc.us
  79. // @connect atlas.geoportalmaps.com
  80. // @connect audraingis.integritygis.com
  81. // @connect batesgis.integritygis.com
  82. // @connect bcgis.baltimorecountymd.gov
  83. // @connect bcgis.broward.org
  84. // @connect bcweb-adapters.bcpa.net
  85. // @connect bentongis.integritygis.com
  86. // @connect bradleytn.geopowered.com
  87. // @connect broadbandgis.bakerprojects.com
  88. // @connect bryangis.bryan-county.org
  89. // @connect bsm.sfdpw.org
  90. // @connect buchanangis.integritygis.com
  91. // @connect c39gisserver.co.richland.nd.us
  92. // @connect cache.gis.lacounty.gov
  93. // @connect cagisonline.hamilton-co.org
  94. // @connect calvertgis.co.cal.md.us
  95. // @connect camdengis.integritygis.com
  96. // @connect cassweb3.co.cass.mn.us
  97. // @connect ccgisapps.charlestoncounty.org
  98. // @connect cdsd.co.teller.co.us
  99. // @connect cecilmaps.ccgov.org
  100. // @connect cloud.longviewtexas.gov
  101. // @connect cloud.sagis.org
  102. // @connect co.kent.de.us
  103. // @connect coagisweb.cabq.gov
  104. // @connect coopergis.integritygis.com
  105. // @connect copgisweb.ci.pearland.tx.us
  106. // @connect crgis.cedar-rapids.org
  107. // @connect data.wsdot.wa.gov
  108. // @connect data1.digitaldataservices.com
  109. // @connect dc-web.co.douglas.mn.us
  110. // @connect doniphangis.integritygis.com
  111. // @connect douglasgis.integritygis.com
  112. // @connect douglasil.com
  113. // @connect dtdapps.coloradodot.info
  114. // @connect dungis.dunwoodyga.gov
  115. // @connect egis.pinellascounty.org
  116. // @connect elb2.39dn.com
  117. // @connect emapsplus.com
  118. // @connect epv.ci.juneau.ak.us
  119. // @connect eservices.co.crook.or.us
  120. // @connect essex-gis.co.essex.ny.us
  121. // @connect ewebmap.ci.lubbock.tx.us
  122. // @connect fremontgis.com
  123. // @connect geaugarealink.co.geauga.oh.us
  124. // @connect gem.edcgov.us
  125. // @connect geo.forsythco.com
  126. // @connect geo.nhcgov.com
  127. // @connect geo.oit.ohio.gov
  128. // @connect geodata.hawaii.gov
  129. // @connect geodata.md.gov
  130. // @connect geodata.sarpy.com
  131. // @connect geodata.state.nj.us
  132. // @connect geodataportal.net
  133. // @connect geopowered.wilson.wilsontngis.com
  134. // @connect geoservices.co.polk.or.us
  135. // @connect geoweb.martin.fl.us
  136. // @connect gis-2.warrencountyny.gov
  137. // @connect gis-server.co.becker.mn.us
  138. // @connect gis-server.co.montezuma.co.us
  139. // @connect gis-web.co.union.nc.us
  140. // @connect gis-world3.aacounty.org
  141. // @connect gis.abilenetx.com
  142. // @connect gis.acgov.org
  143. // @connect gis.adamscounty.org
  144. // @connect gis.aecomonline.net
  145. // @connect gis.allencountyohio.com
  146. // @connect gis.arapahoegov.com
  147. // @connect gis.arkansas.gov
  148. // @connect gis.arlingtonva.us
  149. // @connect gis.ashland-ohio.com
  150. // @connect gis.auglaizecounty.org
  151. // @connect gis.azdot.gov
  152. // @connect gis.baltimorecity.gov
  153. // @connect gis.bentoncountyar.gov
  154. // @connect gis.berkeleycountysc.gov
  155. // @connect gis.bgky.org
  156. // @connect gis.bisclient.com
  157. // @connect gis.blairco.org
  158. // @connect gis.brevardcounty.us
  159. // @connect gis.brookhavenga.gov
  160. // @connect gis.broomfield.org
  161. // @connect gis.buncombecounty.org
  162. // @connect gis.burleighco.com
  163. // @connect gis.buttecounty.net
  164. // @connect gis.calhouncounty.org
  165. // @connect gis.canyonco.org
  166. // @connect gis.carboncounty.com
  167. // @connect gis.cayugacounty.us
  168. // @connect gis.cccounty.us
  169. // @connect gis.ccgisonline.com
  170. // @connect gis.ccpa.net
  171. // @connect gis.cherokeega.com
  172. // @connect gis.citruspa.org
  173. // @connect gis.cityofaikensc.gov
  174. // @connect gis.cityofboston.gov
  175. // @connect gis.clearfieldco.org
  176. // @connect gis.clearwatercounty.org
  177. // @connect gis.clevelandtn.gov
  178. // @connect gis.cmpdd.org
  179. // @connect gis.co.benton.or.us
  180. // @connect gis.co.berks.pa.us
  181. // @connect gis.co.big-stone.mn.us
  182. // @connect gis.co.carlton.mn.us
  183. // @connect gis.co.carver.mn.us
  184. // @connect gis.co.cumberland.nc.us
  185. // @connect gis.co.fillmore.mn.us
  186. // @connect gis.co.fulton.pa.us
  187. // @connect gis.co.guilford.nc.us
  188. // @connect gis.co.hubbard.mn.us
  189. // @connect gis.co.kandiyohi.mn.us
  190. // @connect gis.co.kendall.il.us
  191. // @connect gis.co.knox.il.us
  192. // @connect gis.co.lancaster.pa.us
  193. // @connect gis.co.mifflin.pa.us
  194. // @connect gis.co.mille-lacs.mn.us
  195. // @connect gis.co.monterey.ca.us
  196. // @connect gis.co.nezperce.id.us
  197. // @connect gis.co.polk.mn.us
  198. // @connect gis.co.pope.mn.us
  199. // @connect gis.co.roseau.mn.us
  200. // @connect gis.co.santa-cruz.ca.us
  201. // @connect gis.co.stearns.mn.us
  202. // @connect gis.co.tuolumne.ca.us
  203. // @connect gis.co.wadena.mn.us
  204. // @connect gis.co.waseca.mn.us
  205. // @connect gis.co.washington.ny.us
  206. // @connect gis.co.ym.mn.gov
  207. // @connect gis.coloradosprings.gov
  208. // @connect gis.cosb.us
  209. // @connect gis.countyofriverside.us
  210. // @connect gis.cowleycounty.org
  211. // @connect gis.cranstonri.org
  212. // @connect gis.crawfordcountypa.net
  213. // @connect gis.crookcounty.wy.gov
  214. // @connect gis.dauphincounty.org
  215. // @connect gis.dbqco.org
  216. // @connect gis.dentoncounty.com
  217. // @connect gis.dogis.org
  218. // @connect gis.dot.state.oh.us
  219. // @connect gis.douglascountyks.org
  220. // @connect gis.douglascountywa.net
  221. // @connect gis.dupageco.org
  222. // @connect gis.dutchessny.gov
  223. // @connect gis.eastgreenwichri.com
  224. // @connect gis.ebparks.org
  225. // @connect gis.elpasotexas.gov
  226. // @connect gis.emmetcounty.org
  227. // @connect gis.fnsb.us
  228. // @connect gis.franklincountypa.gov
  229. // @connect gis.fultoncountyga.gov
  230. // @connect gis.fultoncountyoh.com
  231. // @connect gis.fwb.org
  232. // @connect gis.gallatin.mt.gov
  233. // @connect gis.garfield-county.com
  234. // @connect gis.gastongov.com
  235. // @connect gis.gcppwa.net
  236. // @connect gis.gilacountyaz.gov
  237. // @connect gis.gocolumbiamo.com
  238. // @connect gis.goshencounty.org
  239. // @connect gis.gptx.org
  240. // @connect gis.greenegovernment.com
  241. // @connect gis.gscplanning.com
  242. // @connect gis.hcpafl.org
  243. // @connect gis.hendersonky.us
  244. // @connect gis.hennepin.us
  245. // @connect gis.in.gov
  246. // @connect gis.interdev.com
  247. // @connect gis.iowadot.gov
  248. // @connect gis.ircgov.com
  249. // @connect gis.itd.idaho.gov
  250. // @connect gis.johnson-county.com
  251. // @connect gis.johnsoncitytn.org
  252. // @connect gis.kanawhacountyassessor.com
  253. // @connect gis.kentcountymi.gov
  254. // @connect gis.lakecountyfl.gov
  255. // @connect gis.latah.id.us
  256. // @connect gis.leecountyil.com
  257. // @connect gis.leoc.net
  258. // @connect gis.libertycountyga.com
  259. // @connect gis.linncounty.org
  260. // @connect gis.losalamosnm.us
  261. // @connect gis.luzernecounty.org
  262. // @connect gis.lyco.org
  263. // @connect gis.maine.gov
  264. // @connect gis.marinpublic.com
  265. // @connect gis.marioncountyfl.org
  266. // @connect gis.massdot.state.ma.us
  267. // @connect gis.mcassessor.maricopa.gov
  268. // @connect gis.missoulacounty.us
  269. // @connect gis.mono.ca.gov
  270. // @connect gis.mytoddcounty.com
  271. // @connect gis.napa.ca.gov
  272. // @connect gis.nassaucountyny.gov
  273. // @connect gis.nccde.org
  274. // @connect gis.nevadadot.com
  275. // @connect gis.nevcounty.net
  276. // @connect gis.newedgeservices.com
  277. // @connect gis.niagaracounty.com
  278. // @connect gis.nola.gov
  279. // @connect gis.norrycopa.net
  280. // @connect gis.northamptoncounty.org
  281. // @connect gis.odot.state.or.us
  282. // @connect gis.ohiodnr.gov
  283. // @connect gis.okc.gov
  284. // @connect gis.orangecountync.gov
  285. // @connect gis.owensboro.org
  286. // @connect gis.pandai.com
  287. // @connect gis.parkcounty.org
  288. // @connect gis.peoriacounty.org
  289. // @connect gis.perryco.org
  290. // @connect gis.personcounty.net
  291. // @connect gis.pgatlas.com
  292. // @connect gis.phila.gov
  293. // @connect gis.pikepa.org
  294. // @connect gis.pittcountync.gov
  295. // @connect gis.plantation.org
  296. // @connect gis.polkcountyiowa.gov
  297. // @connect gis.pottcounty-ia.gov
  298. // @connect gis.putnam-fl.com
  299. // @connect gis.qac.org
  300. // @connect gis.rcgov.org
  301. // @connect gis.renvillecountymn.com
  302. // @connect gis.rileycountyks.gov
  303. // @connect gis.rrnm.gov
  304. // @connect gis.sandyspringsga.gov
  305. // @connect gis.sangis.org
  306. // @connect gis.santacruzcountyaz.gov
  307. // @connect gis.slocounty.ca.gov
  308. // @connect gis.snco.us
  309. // @connect gis.southkingstownri.com
  310. // @connect gis.spokanecounty.org
  311. // @connect gis.stancounty.com
  312. // @connect gis.summitcountyco.gov
  313. // @connect gis.tazewell.com
  314. // @connect gis.thomsonreuters.com
  315. // @connect gis.transportation.wv.gov
  316. // @connect gis.tularecounty.ca.gov
  317. // @connect gis.ulstercountyny.gov
  318. // @connect gis.ventura.org
  319. // @connect gis.weatherfordtx.gov
  320. // @connect gis.whitfieldcountyga.com
  321. // @connect gis.wilco.org
  322. // @connect gis.wiu.edu
  323. // @connect gis.worldviewsolutions.com
  324. // @connect gis.wyo.gov
  325. // @connect gis.yavapai.us
  326. // @connect gis.yumacountyaz.gov
  327. // @connect gis1.cookcountyil.gov
  328. // @connect gis1.georgetowncountysc.org
  329. // @connect gis1.hcpao.org
  330. // @connect gis10.dot.ga.gov
  331. // @connect gis11.services.ncdot.gov
  332. // @connect gis2.ashtabulacounty.us
  333. // @connect gis2.cambriacountypa.gov
  334. // @connect gis2.co.dakota.mn.us
  335. // @connect gis2.co.josephine.or.us
  336. // @connect gis2.erie.gov
  337. // @connect gis2.gisworkshop.com
  338. // @connect gis2.idaho.gov
  339. // @connect gis2.lawrenceks.org
  340. // @connect gis2.odessa-tx.gov
  341. // @connect gis2.orangeburgcounty.org
  342. // @connect gis2.richmondnc.com
  343. // @connect gis2.siouxfalls.org
  344. // @connect gis3.gisworkshop.com
  345. // @connect gis3.montgomerycountymd.gov
  346. // @connect gis4.dfwmaps.com
  347. // @connect gis4.montgomerycountymd.gov
  348. // @connect gisago.mcgi.state.mi.us
  349. // @connect gisapp.adcogov.org
  350. // @connect gisapp.mahoningcountyoh.gov
  351. // @connect gisdata.alleghenycounty.us
  352. // @connect gisdata.dot.ca.gov
  353. // @connect gisdata.kingcounty.gov
  354. // @connect gisdemo1.cdmsmith.com
  355. // @connect gisgate.co.clark.nv.us
  356. // @connect gismap.augustaga.gov
  357. // @connect gismap.cityofaspen.com
  358. // @connect gismap.co.marshall.mn.us
  359. // @connect gismap.co.norman.mn.us
  360. // @connect gismap.eaglecounty.us
  361. // @connect gismaps.cityofboise.org
  362. // @connect gismaps.co.sangamon.il.us
  363. // @connect gismaps.columbiapa.org
  364. // @connect gismaps.flower-mound.com
  365. // @connect gismaps.pinalcountyaz.gov
  366. // @connect gismaps.snoco.org
  367. // @connect gismaps.vita.virginia.gov
  368. // @connect gismaps.wichita.gov
  369. // @connect gismapserver.leegov.com
  370. // @connect gisp.co.genesee.ny.us
  371. // @connect gisprod10.co.fresno.ca.us
  372. // @connect gisprodops.chesco.org
  373. // @connect gisprpxy.itd.state.ma.us
  374. // @connect gispub.cityofmesquite.com
  375. // @connect gispublic.co.lake.ca.us
  376. // @connect gissd.sandag.org
  377. // @connect gisserver.christiancountymo.gov
  378. // @connect gisserver1.co.teton.id.us
  379. // @connect gisservicemt.gov
  380. // @connect gisservices.chathamnc.org
  381. // @connect gisservices.co.anoka.mn.us
  382. // @connect gisservices.dorchestercounty.net
  383. // @connect gisservices.douglasnv.us
  384. // @connect gisservices.its.ny.gov
  385. // @connect gisservices.oakgov.com
  386. // @connect gisservices2.suffolkcountyny.gov
  387. // @connect gissites2.centrecountypa.gov
  388. // @connect gistech.countyofkane.org
  389. // @connect gisweb.casscountynd.gov
  390. // @connect gisweb.ci.boca-raton.fl.us
  391. // @connect gisweb.co.aitkin.mn.us
  392. // @connect gisweb.co.hinds.ms.us
  393. // @connect gisweb.co.wilkin.mn.us
  394. // @connect gisweb.jeffcowa.us
  395. // @connect gisweb.miamidade.gov
  396. // @connect gisweb.port-orange.org
  397. // @connect gisweb2.azwater.gov
  398. // @connect giswww.westchestergov.com
  399. // @connect git.co.tioga.ny.us
  400. // @connect grandgis.com
  401. // @connect granitweb.sr.unh.edu
  402. // @connect gtg.smcg.co.saint-marys.md.us
  403. // @connect gweb01.co.olmsted.mn.us
  404. // @connect harpergis.integritygis.com
  405. // @connect harrisonms.geopowered.com
  406. // @connect haslet.halff.com
  407. // @connect helenamontanamaps.org
  408. // @connect hgis.hialeahfl.gov
  409. // @connect hidgis.co.hidalgo.tx.us
  410. // @connect ims.districtiii.org
  411. // @connect intervector.leoncountyfl.gov
  412. // @connect iowagis.integritygis.com
  413. // @connect ira.property-appraiser.org
  414. // @connect jcgis.jacksongov.org
  415. // @connect jeffcogis.jccal.org
  416. // @connect jessie.rexburg.org
  417. // @connect k3gis.com
  418. // @connect kcearth.kcgov.us
  419. // @connect kcgis.kcor.org
  420. // @connect kingscountygis.com
  421. // @connect kygisserver.ky.gov
  422. // @connect lafayettegis.integritygis.com
  423. // @connect lawrencegis.integritygis.com
  424. // @connect lcmaps.lanecounty.org
  425. // @connect leegis.leegov.com
  426. // @connect lgmap.wdm.iowa.gov
  427. // @connect lincolngis.integritygis.com
  428. // @connect linkgis.org
  429. // @connect lpcgis.laplata.co.us
  430. // @connect macongis.co.macon.il.us
  431. // @connect map-gis.paducahky.gov
  432. // @connect map.claycountymn.gov
  433. // @connect map.co.clearwater.mn.us
  434. // @connect map.co.merced.ca.us
  435. // @connect map.co.thurston.wa.us
  436. // @connect map.collincad.org
  437. // @connect map.coppelltx.gov
  438. // @connect map.livingstoncounty.us
  439. // @connect map.newberrycounty.net
  440. // @connect map.opkansas.org
  441. // @connect map.polkpa.org
  442. // @connect map.sccmo.org
  443. // @connect map.stclairco.com
  444. // @connect map7.incog.org
  445. // @connect mapcache.friscotexas.gov
  446. // @connect mapit.tarrantcounty.com
  447. // @connect mapitwest.fortworthtexas.gov
  448. // @connect mapp.co.cook.mn.us
  449. // @connect mapping.adamscounty.us
  450. // @connect mapping.cabellassessor.com
  451. // @connect mapping.huntingdoncounty.net
  452. // @connect mapping.modot.org
  453. // @connect mappingmonroe.monroecounty.gov
  454. // @connect maps.alexandriava.gov
  455. // @connect maps.berkeleywv.org
  456. // @connect maps.bonnercounty.us
  457. // @connect maps.boonecountyil.org
  458. // @connect maps.bouldercounty.org
  459. // @connect maps.bryantx.gov
  460. // @connect maps.butlercountyauditor.org
  461. // @connect maps.casperwy.gov
  462. // @connect maps.chautauquacounty.com
  463. // @connect maps.ci.liberty.mo.us
  464. // @connect maps.ci.sherman.tx.us
  465. // @connect maps.cityhs.net
  466. // @connect maps.cityofmobile.org
  467. // @connect maps.claycountygov.com
  468. // @connect maps.clermontauditor.org
  469. // @connect maps.clintoncountypa.com
  470. // @connect maps.co.blaine.id.us
  471. // @connect maps.co.bonneville.id.us
  472. // @connect maps.co.butler.pa.us
  473. // @connect maps.co.forsyth.nc.us
  474. // @connect maps.co.goodhue.mn.us
  475. // @connect maps.co.gov
  476. // @connect maps.co.grayson.tx.us
  477. // @connect maps.co.itasca.mn.us
  478. // @connect maps.co.jefferson.id.us
  479. // @connect maps.co.kern.ca.us
  480. // @connect maps.co.palm-beach.fl.us
  481. // @connect maps.co.pueblo.co.us
  482. // @connect maps.co.ramsey.mn.us
  483. // @connect maps.co.routt.co.us
  484. // @connect maps.co.shasta.ca.us
  485. // @connect maps.co.washington.mn.us
  486. // @connect maps.co.yellowstone.mt.gov
  487. // @connect maps.coj.net
  488. // @connect maps.crc.ga.gov
  489. // @connect maps.cstx.gov
  490. // @connect maps.dcad.org
  491. // @connect maps.dekalbcountyga.gov
  492. // @connect maps.deltacounty.com
  493. // @connect maps.deschutes.org
  494. // @connect maps.desotocountyms.gov
  495. // @connect maps.dmgov.org
  496. // @connect maps.donaanacounty.org
  497. // @connect maps.escpa.org
  498. // @connect maps.flathead.mt.gov
  499. // @connect maps.franklincountyauditor.com
  500. // @connect maps.frederickcountymd.gov
  501. // @connect maps.garlandtx.gov
  502. // @connect maps.garrettcounty.org
  503. // @connect maps.grundyco.org
  504. // @connect maps.highlandvillage.org
  505. // @connect maps.indiana.edu
  506. // @connect maps.kcmo.org
  507. // @connect maps.kpb.us
  508. // @connect maps.kytc.ky.gov
  509. // @connect maps.lagrange-ga.org
  510. // @connect maps.lakecountyil.gov
  511. // @connect maps.laramiecounty.com
  512. // @connect maps.lcwy.org
  513. // @connect maps.lex-co.com
  514. // @connect maps.lexingtonky.gov
  515. // @connect maps.lincolncountysd.org
  516. // @connect maps.maurycounty-tn.gov
  517. // @connect maps.mckinneytexas.org
  518. // @connect maps.meshekgis.com
  519. // @connect maps.miamigov.com
  520. // @connect maps.nashville.gov
  521. // @connect maps.nassauflpa.com
  522. // @connect maps.ocpafl.org
  523. // @connect maps.outdoornebraska.gov
  524. // @connect maps.palmcoastgov.com
  525. // @connect maps.parkco.us
  526. // @connect maps.pitkincounty.com
  527. // @connect maps.placer.ca.gov
  528. // @connect maps.planogis.org
  529. // @connect maps.raleighnc.gov
  530. // @connect maps.sanbag.ca.gov
  531. // @connect maps.sanmiguelcountyco.gov
  532. // @connect maps.santabarbaraca.gov
  533. // @connect maps.shelbyal.com
  534. // @connect maps.showmeboone.com
  535. // @connect maps.sjcounty.net
  536. // @connect maps.smcgov.org
  537. // @connect maps.springfieldmo.gov
  538. // @connect maps.stlouisco.com
  539. // @connect maps.sussexcountyde.gov
  540. // @connect maps.talbgov.org
  541. // @connect maps.udot.utah.gov
  542. // @connect maps.vcgov.org
  543. // @connect maps.vermont.gov
  544. // @connect maps.wakegov.com
  545. // @connect maps.washco-md.net
  546. // @connect maps1.larimer.org
  547. // @connect maps2.bgadd.org
  548. // @connect maps2.cattco.org
  549. // @connect maps2.dcgis.dc.gov
  550. // @connect maps2.timmons.com
  551. // @connect maps2.yorkcountygov.com
  552. // @connect maps3.murfreesborotn.gov
  553. // @connect mapsdev.hamiltontn.gov
  554. // @connect mapserver.co.calaveras.ca.us
  555. // @connect mapservices.gis.saccounty.net
  556. // @connect mapservices.legis.wisconsin.gov
  557. // @connect mapservices2.jeffco.us
  558. // @connect mapsonline.columbiacountyga.gov
  559. // @connect mcggis.mcgtn.org
  560. // @connect mcgis.mesacounty.us
  561. // @connect mcgis.mohavecounty.us
  562. // @connect mcgis4.monroecounty-fl.gov
  563. // @connect mcmap2.montrosecounty.net
  564. // @connect mcogis.co.marion.oh.us
  565. // @connect midland.newedgeservices.com
  566. // @connect millergis.integritygis.com
  567. // @connect mndotgis.dot.state.mn.us
  568. // @connect morgangis.integritygis.com
  569. // @connect msdisweb.missouri.edu
  570. // @connect mtbachelor.co.washington.or.us
  571. // @connect nbgis.newportbeachca.gov
  572. // @connect ndgishub.nd.gov
  573. // @connect oak.co.lake-of-the-woods.mn.us
  574. // @connect ocgis.orangecountygov.com
  575. // @connect oklahomacounty.geocortex.com
  576. // @connect oldhamgis.org
  577. // @connect oncorng.co.ontario.ny.us
  578. // @connect operationserver.ci.henderson.nc.us
  579. // @connect orfmaps.norfolk.gov
  580. // @connect pagis.org
  581. // @connect parcels.rsdigital.com
  582. // @connect pascoview.pascocountyfl.net
  583. // @connect phelpsgis.integritygis.com
  584. // @connect pimamaps.pima.gov
  585. // @connect polaris3g.mecklenburgcountync.gov
  586. // @connect prodmaps.ne.gov
  587. // @connect propaccess.traviscad.org
  588. // @connect propaccess.wadtx.com
  589. // @connect propertyrecords.montcopa.org
  590. // @connect propertyviewer.andersoncountysc.org
  591. // @connect psportal.harrisoncountywv.com
  592. // @connect publicmap.co.st-clair.il.us
  593. // @connect qagis1.sanantonio.gov
  594. // @connect rallsgis.integritygis.com
  595. // @connect rapcgis.rapc.info
  596. // @connect raygis.integritygis.com
  597. // @connect regis.solanocounty.com
  598. // @connect renogis3.renogov.org
  599. // @connect romefloyd.agdmaps.com
  600. // @connect rptsgisweb.oswegocounty.com
  601. // @connect sagiscloud.thempc.org
  602. // @connect scgis.siouxcounty.org
  603. // @connect secure.boonecountygis.com
  604. // @connect seminolearcgis.seminolecountyfl.gov
  605. // @connect server1.mapxpress.net
  606. // @connect server2.mapxpress.net
  607. // @connect services.arcgis.com
  608. // @connect services.ccgisc.org
  609. // @connect services.geoviewer8.com
  610. // @connect services.gis.ca.gov
  611. // @connect services.nconemap.gov
  612. // @connect services.putnamco.org
  613. // @connect services.wvgis.wvu.edu
  614. // @connect services1.arcgis.com
  615. // @connect services2.arcgis.com
  616. // @connect services2.bhamaps.com
  617. // @connect services2.integritygis.com
  618. // @connect services3.arcgis.com
  619. // @connect services5.arcgis.com
  620. // @connect services6.arcgis.com
  621. // @connect services7.arcgis.com
  622. // @connect services9.arcgis.com
  623. // @connect sjmap.org
  624. // @connect spatial.gishost.com
  625. // @connect spatial.jacksoncounty.org
  626. // @connect springsgis.coralsprings.org
  627. // @connect stlgis.stlouis-mo.gov
  628. // @connect summitmaps.summitoh.net
  629. // @connect svr4.sumtercountysc.org
  630. // @connect tigerweb.geo.census.gov
  631. // @connect tiptontn.geopowered.com
  632. // @connect tlcgisinter.leoncountyfl.gov
  633. // @connect tn.hardeman.geopowered.com
  634. // @connect tn.mcminn.geopowered.com
  635. // @connect tn.sumner.geopowered.com
  636. // @connect tnmap.tn.gov
  637. // @connect tsc-gis-ags101a.schneidercorp.com
  638. // @connect utility.arcgis.com
  639. // @connect vernongis.integritygis.com
  640. // @connect w04.co.delaware.pa.us
  641. // @connect wallawallagis.com
  642. // @connect wcgisweb.washoecounty.us
  643. // @connect wcsvrgis.washcopa.org
  644. // @connect web3.kcsgis.com
  645. // @connect weba.co.clayton.ga.us
  646. // @connect webdmz.starkcountyohio.gov
  647. // @connect webgis.bcgov.net
  648. // @connect webgis.bedfordcountyva.gov
  649. // @connect webgis.co.humboldt.ca.us
  650. // @connect webgis.okaloosafl.com
  651. // @connect webgis.providenceri.gov
  652. // @connect webmap.co.hood-river.or.us
  653. // @connect webmap.co.jackson.ms.us
  654. // @connect webmap.jeffparish.net
  655. // @connect webmap.trueautomation.com
  656. // @connect webmap1.co.warren.oh.us
  657. // @connect webmapssecure.ewashtenaw.org
  658. // @connect webserverholis.honolulugis.org
  659. // @connect wfs.ksdot.org
  660. // @connect ww1.bucoks.com
  661. // @connect www.1stdistrict.org
  662. // @connect www.adacountyassessor.org
  663. // @connect www.adamscountyarcserver.com
  664. // @connect www.ancgis.com
  665. // @connect www.bcad.org
  666. // @connect www.bcgis.com
  667. // @connect www.centralilmaps.com
  668. // @connect www.co.bingham.id.us
  669. // @connect www.co.chippewa.mn.us
  670. // @connect www.co.steele.mn.us
  671. // @connect www.cobbgis.org
  672. // @connect www.cvcog911.org
  673. // @connect www.dmcwebgis.com
  674. // @connect www.dot.state.ak.us
  675. // @connect www.efsedge.com
  676. // @connect www.finneycountygis.com
  677. // @connect www.gcgis.org
  678. // @connect www.gfgis.com
  679. // @connect www.gis.bocc.co.st-johns.fl.us
  680. // @connect www.gis.dcga.us
  681. // @connect www.gisonline.ms.gov
  682. // @connect www.greenwoodsc.gov
  683. // @connect www.hernandocountygis-fl.us
  684. // @connect www.horrycounty.org
  685. // @connect www.landmarkgeospatial.com
  686. // @connect www.laurenscountygis.org
  687. // @connect www.loraincountyauditor.com
  688. // @connect www.mcegisohio.org
  689. // @connect www.mcgisweb.org
  690. // @connect www.mchenrycountygis.org
  691. // @connect www.mercercountyohio.org
  692. // @connect www.midmogis.org
  693. // @connect www.monroegis.org
  694. // @connect www.mymanatee.org
  695. // @connect www.ncpub.org
  696. // @connect www.ocgis.com
  697. // @connect www.ottertailcounty.net
  698. // @connect www.paslc.org
  699. // @connect www.pdarcgissvr.pa.gov
  700. // @connect www.richlandcountyauditor.org
  701. // @connect www.saludacountysc.net
  702. // @connect www.sanduskycountygis.org
  703. // @connect www.sccgov.org
  704. // @connect www.sciotocountyengineer.org
  705. // @connect www.semogis.com
  706. // @connect www.smithcountymapsite.org
  707. // @connect www.tgisites.com
  708. // @connect www.unionco.org
  709. // @connect www.valorgis.com
  710. // @connect www.wcgis.us
  711. // @connect www.webgis.net
  712. // @connect www.wingis.org
  713. // @connect www2.pottcounty.org
  714. // @connect www3.multco.us
  715. // @connect www4.co.union.oh.us
  716. // @connect yolo-gis-prod.yolocounty.org
  717. // ==/UserScript==
  718.  
  719. /* global OL */
  720. /* global W */
  721. /* global GM_info */
  722. /* global WazeWrap */
  723. /* global _ */
  724. /* global $ */
  725.  
  726. (function() {
  727. // **************************************************************************************************************
  728. // IMPORTANT: Update this when releasing a new version of script that includes changes to the spreadsheet format
  729. // that may cause old code to break. This # should match the version listed in the spreadsheet
  730. // i.e. update them at the same time.
  731.  
  732. const LAYER_DEF_VERSION = '2018.04.27.001';
  733.  
  734. // **************************************************************************************************************
  735.  
  736. const SCRIPT_AUTHOR = 'MapOMatic'; // Used in tooltips to tell people who to report issues to. Update if a new author takes ownership of this script.
  737. const LAYER_INFO_URL = 'https://spreadsheets.google.com/feeds/list/1cEG3CvXSCI4TOZyMQTI50SQGbVhJ48Xip-jjWg4blWw/o7gusx3/public/values?alt=json';
  738. const LAYER_DEF_URL = 'https://spreadsheets.google.com/feeds/list/1cEG3CvXSCI4TOZyMQTI50SQGbVhJ48Xip-jjWg4blWw/oj7k5j6/public/values?alt=json';
  739. const PRIVATE_LAYERS = {'nc-henderson-sl-signs': ['the_cre8r','mapomatic']}; // case sensitive -- use all lower case
  740.  
  741. const DEFAULT_STYLE = {
  742. fillColor: '#000',
  743. pointRadius: 4,
  744. label : '${label}',
  745. strokeColor: '#ffa500',
  746. strokeOpacity: '0.95',
  747. strokeWidth: 1.5,
  748. fontColor: '#ffc520',
  749. fontSize: '13',
  750. labelOutlineColor: 'black',
  751. labelOutlineWidth: 3
  752. };
  753.  
  754. const LAYER_STYLES = {
  755. cities: {
  756. fillOpacity: 0.3,
  757. fillColor: '#f65',
  758. strokeColor: '#f65',
  759. fontColor: '#f62'
  760. },
  761. forests_parks: {
  762. fillOpacity: 0.4,
  763. fillColor: '#585',
  764. strokeColor: '#484',
  765. fontColor: '#8b8'
  766. },
  767. milemarkers: {
  768. strokeColor: '#fff',
  769. fontColor: '#fff',
  770. fontWeight: 'bold',
  771. fillOpacity: 0,
  772. labelYOffset: 10,
  773. pointRadius: 2,
  774. fontSize: 12
  775. },
  776. parcels: {
  777. fillOpacity: 0,
  778. fillColor: '#ffa500'
  779. },
  780. points: {
  781. strokeColor: '#000',
  782. fontColor: '#0ff',
  783. fillColor: '#0ff',
  784. labelYOffset: -10,
  785. labelAlign: 'ct'
  786. },
  787. post_offices: {
  788. strokeColor: '#000',
  789. fontColor: '#f84',
  790. fillColor: '#f84',
  791. fontWeight: 'bold',
  792. labelYOffset: -10,
  793. labelAlign: 'ct'
  794. },
  795. state_parcels: {
  796. fillOpacity: 0,
  797. strokeColor: '#e62',
  798. fillColor: '#e62',
  799. fontColor: '#e73'
  800. },
  801. state_points: {
  802. strokeColor: '#000',
  803. fontColor: '#3cf',
  804. fillColor: '#3cf',
  805. labelYOffset: -10,
  806. labelAlign: 'ct'
  807. },
  808. structures: {
  809. fillOpacity: 0,
  810. strokeColor: '#f7f',
  811. fontColor: '#f7f'
  812. }
  813. };
  814.  
  815. const ROAD_STYLE = new OL.Style(
  816. {
  817. pointRadius: 12,
  818. fillColor:'#369',
  819. pathLabel: '${label}',
  820. label:'',
  821. fontColor:'#faf',
  822. labelSelect: true,
  823. pathLabelYOffset:'${getOffset}',
  824. pathLabelCurve: '${getSmooth}',
  825. pathLabelReadable: '${getReadable}',
  826. labelAlign: '${getAlign}',
  827. labelOutlineWidth: 3,
  828. labelOutlineColor: '#000',
  829. strokeWidth:3,
  830. stroke:true,
  831. strokeColor:'#f0f',
  832. strokeOpacity: 0.4,
  833. fontWeight: 'bold',
  834. fontSize: 11
  835. }, {
  836. context: {
  837. getOffset: function() { return -(W.map.getZoom()+5); },
  838. getSmooth: function() { return ''; },
  839. getReadable: function() { return '1'; },
  840. getAlign: function() { return 'cb'; }
  841. }
  842. }
  843. );
  844.  
  845. let _regexReplace = {
  846. // Strip leading zeros or blank full label for any label starting with a non-digit or is a Zero Address, use with '' as replace.
  847. r0: /^(0+(\s.*)?|\D.*)/,
  848. // Strip Everything After Street Type to end of the string by use $1 and $2 capture groups, use with replace '$1$2'
  849. r1: /^(.* )(Ave(nue)?|Dr(ive)?|St(reet)?|C(our)?t|Cir(cle)?|Blvd|Boulevard|Pl(ace)?|Ln|Lane|Fwy|Freeway|R(oa)?d|Ter(r|race)?|Tr(ai)?l|Way|Rte \d+|Route \d+)\b.*/gi,
  850. // Strip SPACE 5 Digits from end of string, use with replace ''
  851. r2: /\s\d{5}$/,
  852. // Strip Everything after a "~", ",", ";" to the end of the string, use with replace ''
  853. r3: /(~|,|;|\s?\r\n).*$/,
  854. // Move the digits after the last space to before the rest of the string using, use with replace '$2 $1'
  855. r4: /^(.*)\s(\d+).*/,
  856. // Insert newline between digits (including "-") and everything after the digits, except (and before) a ",", use with replace '$1\n$2'
  857. r5: /^([-\d]+)\s+([^,]+).*/,
  858. // Insert newline between digits and everything after the digits, use with replace '$1\n$2'
  859. r6: /^(\d+)\s+(.*)/
  860. };
  861.  
  862. let _gisLayers = [];
  863.  
  864. let _layerRefinements = [
  865. {id: 'us-post-offices',
  866. labelHeaderFields: ['LOCALE_NAME']
  867. },
  868.  
  869. {id: 'ky-warren-co-wku-structures',
  870. labelHeaderFields: ['Bldg_Name']
  871. }
  872. ];
  873.  
  874. const STATES = {
  875. _states:[
  876. ['US (Country)','US',-1],['Alabama','AL',1],['Alaska','AK',2],['American Samoa','AS',60],['Arizona','AZ',4],['Arkansas','AR',5],['California','CA',6],['Colorado','CO',8],['Connecticut','CT',9],['Delaware','DE',10],['District of Columbia','DC',11],
  877. ['Florida','FL',12],['Georgia','GA',13],['Guam','GU',66],['Hawaii','HI',15],['Idaho','ID',16],['Illinois','IL',17],['Indiana','IN',18],['Iowa','IA',19],['Kansas','KS',20],
  878. ['Kentucky','KY',21],['Louisiana','LA',22],['Maine','ME',23],['Maryland','MD',24],['Massachusetts','MA',25],['Michigan','MI',26],['Minnesota','MN',27],['Mississippi','MS',28],['Missouri','MO',29],
  879. ['Montana','MT',30],['Nebraska','NE',31],['Nevada','NV',32],['New Hampshire','NH',33],['New Jersey','NJ',34],['New Mexico','NM',35],['New York','NY',36],['North Carolina','NC',37],['North Dakota','ND',38],
  880. ['Northern Mariana Islands','MP',69],['Ohio','OH',39],['Oklahoma','OK',40],['Oregon','OR',41],['Pennsylvania','PA',42],['Puerto Rico','PR',72],['Rhode Island','RI',44],['South Carolina','SC',45],
  881. ['South Dakota','SD',46],['Tennessee','TN',47],['Texas','TX',48],['Utah','UT',49],['Vermont','VT',50],['Virgin Islands','VI',78],['Virginia','VA',51],['Washington','WA',53],['West Virginia','WV',54],['Wisconsin','WI',55],['Wyoming','WY',56]
  882. ],
  883. toAbbr: function(fullName) { return this._states.find(a => a[0] === fullName)[1]; },
  884. toFullName: function(abbr) { return this._states.find(a => a[1] === abbr)[0]; },
  885. toFullNameArray: function() { return this._states.map(a => a[0]); },
  886. toAbbrArray: function() { return this._states.map(a => a[1]); },
  887. fromId: function(id) { return this._states.find(a => a[2] === id); }
  888. };
  889. const DEFAULT_VISIBLE_AT_ZOOM = 6;
  890. const SETTINGS_STORE_NAME = 'wme_gis_layers_fl';
  891. const COUNTIES_URL = 'https://tigerweb.geo.census.gov/arcgis/rest/services/Census2010/State_County/MapServer/1/';
  892. const ALERT_UPDATE = false;
  893. const SCRIPT_VERSION = GM_info.script.version;
  894. const SCRIPT_VERSION_CHANGES = [
  895. GM_info.script.name + '\nv' + SCRIPT_VERSION + '\n\nWhat\'s New\n------------------------------\n',
  896. '\n- Update for new WME layers menu.'
  897. ].join('');
  898. let _mapLayer = null;
  899. let _roadLayer = null;
  900. let _settings = {};
  901. let _ignoreFetch = false;
  902. let _lastToken = {};
  903.  
  904. const DEBUG = true;
  905. function log(message) { console.log('GIS Layers:', message); }
  906. function logError(message) { console.error('GIS Layers:', message); }
  907. function logDebug(message) { if (DEBUG) console.debug('GIS Layers:', message); }
  908. function logWarning(message) { console.warn('GIS Layers:', message); }
  909.  
  910. function loadSettingsFromStorage() {
  911. let loadedSettings = $.parseJSON(localStorage.getItem(SETTINGS_STORE_NAME));
  912. let defaultSettings = {
  913. lastVersion: null,
  914. visibleLayers: [],
  915. onlyShowApplicableLayers: false,
  916. selectedStates: [],
  917. enabled: true,
  918. fillParcels: false,
  919. addrLabelDisplay: 'all'
  920. };
  921. _settings = loadedSettings ? loadedSettings : defaultSettings;
  922. for (let prop in defaultSettings) {
  923. if (!_settings.hasOwnProperty(prop)) {
  924. _settings[prop] = defaultSettings[prop];
  925. }
  926. }
  927. }
  928.  
  929. function saveSettingsToStorage() {
  930. if (localStorage) {
  931. _settings.lastVersion = SCRIPT_VERSION;
  932. localStorage.setItem(SETTINGS_STORE_NAME, JSON.stringify(_settings));
  933. log('Settings saved');
  934. }
  935. }
  936.  
  937. function getUrl(extent, gisLayer) {
  938. if (gisLayer.spatialReference) {
  939. let proj = new OL.Projection('EPSG:' + gisLayer.spatialReference);
  940. extent.transform(W.map.getProjection(), proj);
  941. }
  942. let geometry = { xmin:extent.left, ymin:extent.bottom, xmax:extent.right, ymax:extent.top, spatialReference: {wkid: gisLayer.spatialReference ? gisLayer.spatialReference : 102100, latestWkid: gisLayer.spatialReference ? gisLayer.spatialReference : 3857} };
  943. let geometryStr = JSON.stringify(geometry);
  944. let url = gisLayer.url + '/query?geometry=' + encodeURIComponent(geometryStr);
  945. let fields = gisLayer.labelFields;
  946. if (gisLayer.labelHeaderFields) {
  947. fields = fields.concat(gisLayer.labelHeaderFields);
  948. }
  949. if (gisLayer.distinctFields) {
  950. fields = fields.concat(gisLayer.distinctFields);
  951. }
  952. url += gisLayer.token ? '&token=' + gisLayer.token : '';
  953. url += '&outFields=' + encodeURIComponent(fields.join(','));
  954. url += '&returnGeometry=true';
  955. url += '&spatialRel=esriSpatialRelIntersects&geometryType=esriGeometryEnvelope&inSR=' + (gisLayer.spatialReference ? gisLayer.spatialReference : '102100') + '&outSR=3857&f=json';
  956. if (gisLayer.where) {
  957. url += '&where=' + encodeURIComponent(gisLayer.where);
  958. }
  959. logDebug('Request URL: ' + url);
  960. return url;
  961. }
  962.  
  963. function getCountiesUrl(extent) {
  964. let geometry = { xmin:extent.left, ymin:extent.bottom, xmax:extent.right, ymax:extent.top, spatialReference: {wkid: 102100, latestWkid: 3857} };
  965. let url = COUNTIES_URL + '/query?geometry=' + encodeURIComponent(JSON.stringify(geometry));
  966. return url + '&outFields=BASENAME%2CSTATE&returnGeometry=false&spatialRel=esriSpatialRelIntersects&geometryType=esriGeometryEnvelope&inSR=102100&outSR=3857&f=json';
  967. }
  968.  
  969. let _countiesInExtent = [];
  970. let _statesInExtent = [];
  971.  
  972. function getFetchableLayers(getInvisible) {
  973. return _gisLayers.filter(gisLayer => {
  974. let isValidUrl = gisLayer.url && gisLayer.url.trim().length > 0;
  975. let isVisible = (getInvisible || _settings.visibleLayers.indexOf(gisLayer.id) > -1) && _settings.selectedStates.indexOf(gisLayer.state) > -1;
  976. let isInState = gisLayer.state === 'US' || _statesInExtent.indexOf(STATES.toFullName(gisLayer.state)) > -1;
  977. // Be sure to use hasOwnProperty when checking this, since 0 is a valid value.
  978. let isValidZoom = getInvisible || W.map.getZoom() >= (gisLayer.hasOwnProperty('visibleAtZoom') ? gisLayer.visibleAtZoom : DEFAULT_VISIBLE_AT_ZOOM);
  979. return isValidUrl && isInState && isVisible && isValidZoom;
  980. });
  981. }
  982.  
  983. function filterLayerCheckboxes() {
  984. let applicableLayers = getFetchableLayers(true).filter(layer => {
  985. let hasCounties = layer.hasOwnProperty('counties');
  986. return (hasCounties && layer.counties.some(county => _countiesInExtent.indexOf(county.toLowerCase()) > -1)) || !hasCounties;
  987. });
  988. var statesToHide = STATES.toAbbrArray();
  989.  
  990. _gisLayers.forEach(gisLayer => {
  991. let id = '#gis-layer-' + gisLayer.id + '-container';
  992. if(!_settings.onlyShowApplicableLayers || applicableLayers.indexOf(gisLayer) > -1){
  993. $(id).show();
  994. $('#gis-layers-for-' + gisLayer.state).show();
  995. let idx = statesToHide.indexOf(gisLayer.state);
  996. if (idx > -1) statesToHide.splice(idx, 1);
  997. } else {
  998. $(id).hide();
  999. }
  1000. });
  1001. if (_settings.onlyShowApplicableLayers) {
  1002. statesToHide.forEach(st => $('#gis-layers-for-' + st).hide());
  1003. }
  1004. }
  1005.  
  1006. const ROAD_ABBR = [[/\bAVENUE$/,'AVE'], [/\bCIRCLE$/,'CIR'], [/\bCOURT$/,'CT'], [/\bDRIVE$/,'DR'], [/\bLANE$/,'LN'], [/\bPARK$/,'PK'], [/\bPLACE$/,'PL'], [/\bROAD$/,'RD'], [/\bSTREET$/,'ST'], [/\bTERRACE$/,'TER']];
  1007. function processFeatures(data, token, gisLayer) {
  1008. let features = [];
  1009. if (data.skipIt) {
  1010. // do nothing
  1011. } else if (data.error) {
  1012. logError('Error in layer "' + gisLayer.name + '": ' + data.error.message);
  1013. } else {
  1014. let items = data.features;
  1015. if (!token.cancel) {
  1016. let error = false;
  1017. let distinctValues = [];
  1018. items.forEach(item => {
  1019. let skipIt = false;
  1020. if (!token.cancel && !error) {
  1021. let feature;
  1022. let featureGeometry;
  1023. let area;
  1024. if (gisLayer.distinctFields) {
  1025. if (distinctValues.some( v => gisLayer.distinctFields.every(fld => v[fld] === item.attributes[fld]) )) {
  1026. skipIt = true;
  1027. } else {
  1028. let dist = {};
  1029. gisLayer.distinctFields.forEach(fld => dist[fld] = item.attributes[fld]);
  1030. distinctValues.push(dist);
  1031. }
  1032. }
  1033. if (!skipIt) {
  1034. let layerOffset = gisLayer.layerOffset ? gisLayer.layerOffset : {x: 0, y: 0};
  1035. // Special handling for this layer, because it doesn't have a geometry property. Coordinates are stored in the attributes.
  1036. if (gisLayer.id === 'nc-richmond-co-pts') {
  1037. let pt = new OL.Geometry.Point(item.attributes.XCOOR, item.attributes.YCOOR);
  1038. pt.transform(W.map.displayProjection, W.map.projection);
  1039. item.geometry = pt;
  1040. }
  1041. if (item.geometry) {
  1042. if (item.geometry.x) {
  1043. featureGeometry = new OL.Geometry.Point(item.geometry.x + layerOffset.x, item.geometry.y + layerOffset.y);
  1044. } else if (item.geometry.points) {
  1045. // @TODO Fix for multiple points instead of just grabbing first.
  1046. featureGeometry = new OL.Geometry.Point(item.geometry.points[0][0] + layerOffset.x, item.geometry.points[0][1] + layerOffset.y);
  1047. } else if (item.geometry.rings) {
  1048. let rings = [];
  1049. item.geometry.rings.forEach(function(ringIn) {
  1050. let pnts= [];
  1051. for(let i=0;i<ringIn.length;i++){
  1052. pnts.push(new OL.Geometry.Point(ringIn[i][0] + layerOffset.x, ringIn[i][1] + layerOffset.y));
  1053. }
  1054. rings.push(new OL.Geometry.LinearRing(pnts));
  1055. });
  1056. featureGeometry = new OL.Geometry.Polygon(rings);
  1057. if (gisLayer.areaToPoint) {
  1058. featureGeometry = featureGeometry.getCentroid();
  1059. } else {
  1060. area = featureGeometry.getArea();
  1061. }
  1062. } else if (data.geometryType === 'esriGeometryPolyline') {
  1063. let pointList = [];
  1064. item.geometry.paths.forEach(function(path){
  1065. path.forEach(point => pointList.push(new OL.Geometry.Point(point[0] + layerOffset.x, point[1] + layerOffset.y)));
  1066. });
  1067. featureGeometry = new OL.Geometry.LineString(pointList);
  1068. featureGeometry.skipDupeCheck = true;
  1069. } else {
  1070. logDebug('Unexpected feature type in layer: ' + JSON.stringify(item));
  1071. logError('Error: Unexpected feature type in layer "' + gisLayer.name + '"');
  1072. error = true;
  1073. }
  1074. if (!error) {
  1075. let hasVisibleAtZoom = gisLayer.hasOwnProperty('visibleAtZoom');
  1076. let hasLabelsVisibleAtZoom = gisLayer.hasOwnProperty('labelsVisibleAtZoom');
  1077. let displayLabelsAtZoom = hasLabelsVisibleAtZoom ? gisLayer.labelsVisibleAtZoom : (hasVisibleAtZoom ? gisLayer.visibleAtZoom : DEFAULT_VISIBLE_AT_ZOOM) + 1;
  1078. let label = '';
  1079. if (gisLayer.labelHeaderFields) {
  1080. label = gisLayer.labelHeaderFields.map(fieldName => item.attributes[fieldName]).join(' ').trim() + '\n';
  1081. }
  1082. if (W.map.getZoom() >= displayLabelsAtZoom || area >= 5000) {
  1083. label += gisLayer.labelFields.map(fieldName => item.attributes[fieldName]).join(' ').trim();
  1084. if (gisLayer.processLabel) label = gisLayer.processLabel(label, item.attributes).trim();
  1085. }
  1086. if (label && [LAYER_STYLES.points, LAYER_STYLES.parcels, LAYER_STYLES.state_points, LAYER_STYLES.state_parcels].indexOf(gisLayer.style) > -1) {
  1087. if (_settings.addrLabelDisplay === 'hn') {
  1088. let m = label.match(/^\d+/);
  1089. label = m ? m[0] : '';
  1090. } else if (_settings.addrLabelDisplay === 'street') {
  1091. let m = label.match(/^(?:\d+\s)?(.*)/);
  1092. label = m ? m[1].trim() : '';
  1093. }
  1094. }
  1095. let attributes = {
  1096. layerID: gisLayer.id,
  1097. label: label
  1098. };
  1099. feature = new OL.Feature.Vector(featureGeometry,attributes);
  1100. features.push(feature);
  1101. }
  1102. }
  1103. }
  1104. }
  1105. });
  1106. }
  1107. }
  1108. if (!token.cancel) {
  1109. // Check for duplicate geometries.
  1110. for (let i=0; i<features.length; i++) {
  1111. let f1 = features[i];
  1112. if (!f1.geometry.skipDupeCheck) {
  1113. let c1 = f1.geometry.getCentroid();
  1114. let labels = [f1.attributes.label];
  1115. for (let j=i+1; j<features.length; j++) {
  1116. let f2 = features[j];
  1117. if (!f2.geometry.skipDupeCheck && f2.geometry.getCentroid().distanceTo(c1) < 1) {
  1118. features.splice(j,1);
  1119. labels.push(f2.attributes.label);
  1120. j--;
  1121. }
  1122. }
  1123. labels = _.unique(labels);
  1124. if (labels.length > 1) {
  1125. labels.forEach((label, idx) => {
  1126. label = label.replace(/\n/g,' ').replace(/\s{2,}/,' ').replace(/\bUNIT\s.{1,5}$/i,'').trim();
  1127. ROAD_ABBR.forEach(abbr => label = label.replace(abbr[0], abbr[1]));
  1128. labels[idx] = label;
  1129. });
  1130. labels = _.unique(labels);
  1131. labels.sort();
  1132. if (labels.length > 12) {
  1133. let len = labels.length;
  1134. labels = labels.slice(0,10);
  1135. labels.push('(' + (len - 10) + ' more...)');
  1136. }
  1137. f1.attributes.label = _.unique(labels).join('\n');
  1138. } else {
  1139. let label = f1.attributes.label;
  1140. ROAD_ABBR.forEach(abbr => label = label.replace(abbr[0], abbr[1]));
  1141. f1.attributes.label = label;
  1142. }
  1143. }
  1144. }
  1145.  
  1146. let layer = gisLayer.isRoadLayer ? _roadLayer : _mapLayer;
  1147. layer.removeFeatures(layer.getFeaturesByAttribute('layerID', gisLayer.id));
  1148. layer.addFeatures(features);
  1149.  
  1150. if (features.length) {
  1151. $('label[for="gis-layer-' + gisLayer.id + '"]').css({color:'#00a009'});
  1152. }
  1153. }
  1154. } // END processFeatures()
  1155.  
  1156. function fetchFeatures() {
  1157. if (_ignoreFetch) return;
  1158. _lastToken.cancel = true;
  1159. _lastToken = {cancel: false, features: [], layersProcessed: 0};
  1160. $('.gis-state-layer-label').css({'color':'#777'});
  1161.  
  1162. let _layersCleared = false;
  1163.  
  1164. //if (layersToFetch.length) {
  1165. let extent = W.map.getExtent();
  1166. GM_xmlhttpRequest({
  1167. url: getCountiesUrl(extent),
  1168. method: 'GET',
  1169. onload: function(res) {
  1170. if (res.status < 400) {
  1171. let data = $.parseJSON(res.responseText);
  1172. if (data.error) {
  1173. logError('Error in US Census counties data: ' + data.error.message);
  1174. } else {
  1175. _countiesInExtent = data.features.map(feature => feature.attributes.BASENAME.toLowerCase());
  1176. logDebug('US Census counties: ' + _countiesInExtent.join(', '));
  1177. _statesInExtent = _.unique(data.features.map(feature => STATES.fromId(parseInt(feature.attributes.STATE))[0]));
  1178.  
  1179. let layersToFetch;
  1180. if (!_layersCleared) {
  1181. _layersCleared = true;
  1182. layersToFetch = getFetchableLayers();
  1183.  
  1184. // Remove features of any layers that won't be mapped.
  1185. _gisLayers.forEach(gisLayer => {
  1186. if (layersToFetch.indexOf(gisLayer) === -1) {
  1187. _mapLayer.removeFeatures(_mapLayer.getFeaturesByAttribute('layerID', gisLayer.id));
  1188. _roadLayer.removeFeatures(_roadLayer.getFeaturesByAttribute('layerID', gisLayer.id));
  1189. }
  1190. });
  1191. }
  1192.  
  1193. layersToFetch = layersToFetch.filter( layer => !layer.hasOwnProperty('counties') || layer.counties.some(county => _countiesInExtent.indexOf(county.toLowerCase()) > -1) );
  1194. filterLayerCheckboxes();
  1195. logDebug('Fetching ' + layersToFetch.length + ' layers...');
  1196. logDebug(layersToFetch);
  1197. layersToFetch.forEach(gisLayer => {
  1198. let url = getUrl(extent, gisLayer);
  1199. GM_xmlhttpRequest({
  1200. url: url,
  1201. context: _lastToken,
  1202. method: 'GET',
  1203. onload: function(res) {
  1204. if (res.status < 400) { // Handle stupid issue where http 4## is considered success //
  1205. processFeatures($.parseJSON(res.responseText), res.context, gisLayer);
  1206. } else {
  1207. logDebug('HTTP request error: ' + JSON.stringify(res));
  1208. logError('Could not fetch layer "' + gisLayer.id + '". Request returned ' + res.status);
  1209. }},
  1210. onerror: function(res) {
  1211. logDebug('xmlhttpRequest error:' + JSON.stringify(res));
  1212. logError('Could not fetch layer "' + gisLayer.id + '". An error was thrown.');
  1213. }
  1214. });
  1215. });
  1216. }
  1217. } else {
  1218. logDebug('HTTP request error: ' + JSON.stringify(res));
  1219. logError('Could not fetch counties from US Census site. Request returned ' + res.status);
  1220. }
  1221. },
  1222. onerror: function(res) {
  1223. logDebug('xmlhttpRequest error:' + JSON.stringify(res));
  1224. logError('Could not fetch counties from US Census site. An error was thrown.');
  1225. }
  1226. });
  1227. //} else {
  1228. // filterLayerCheckboxes();
  1229. //}
  1230. }
  1231.  
  1232. function showScriptInfoAlert() {
  1233. /* Check version and alert on update */
  1234. if (ALERT_UPDATE && SCRIPT_VERSION !== _settings.lastVersion) {
  1235. alert(SCRIPT_VERSION_CHANGES);
  1236. }
  1237. }
  1238.  
  1239. function setEnabled(value) {
  1240. _settings.enabled = value;
  1241. saveSettingsToStorage();
  1242. _mapLayer.setVisibility(value);
  1243. _roadLayer.setVisibility(value);
  1244. let color = value ? '#00bd00' : '#ccc';
  1245. $('span#gis-layers-power-btn').css({color:color});
  1246. if (value) fetchFeatures();
  1247. $('#layer-switcher-item_gis_layers').prop('checked',value);
  1248. }
  1249.  
  1250. function onLayerToggleChanged(checked, layerID) {
  1251. let idx = _settings.visibleLayers.indexOf(layerID);
  1252. if (checked) {
  1253. if (idx === -1) _settings.visibleLayers.push(layerID);
  1254. } else {
  1255. if (idx > -1) _settings.visibleLayers.splice(idx, 1);
  1256. }
  1257. if (!_ignoreFetch) {
  1258. saveSettingsToStorage();
  1259. fetchFeatures();
  1260. }
  1261. }
  1262.  
  1263. function onOnlyShowApplicableLayersChanged(checked) {
  1264. _settings.onlyShowApplicableLayers = checked;
  1265. saveSettingsToStorage();
  1266. fetchFeatures();
  1267. }
  1268.  
  1269. function onStateCheckChanged(checked, st) {
  1270. let idx = _settings.selectedStates.indexOf(st);
  1271. if (checked) {
  1272. if (idx === -1) _settings.selectedStates.push(st);
  1273. } else {
  1274. if (idx > -1) _settings.selectedStates.splice(idx, 1);
  1275. }
  1276. if (!_ignoreFetch) {
  1277. saveSettingsToStorage();
  1278. initLayersTab();
  1279. fetchFeatures();
  1280. }
  1281. }
  1282.  
  1283. function onLayerCheckboxChanged(checked) {
  1284. setEnabled(checked);
  1285. }
  1286.  
  1287. function setFillParcels(doFill) {
  1288. [LAYER_STYLES.parcels, LAYER_STYLES.state_parcels].forEach(style => {
  1289. style.fillOpacity = doFill ? 0.2 : 0;
  1290. });
  1291. }
  1292.  
  1293. function onFillParcelsCheckedChanged(checked) {
  1294. setFillParcels(checked);
  1295. _settings.fillParcels = checked;
  1296. saveSettingsToStorage();
  1297. fetchFeatures();
  1298. }
  1299.  
  1300. function onMapMove() {
  1301. if (_settings.enabled) fetchFeatures();
  1302. }
  1303.  
  1304. function initLayer(){
  1305. let rules = _gisLayers.map(gisLayer => {
  1306. return new OL.Rule({
  1307. filter: new OL.Filter.Comparison({
  1308. type: OL.Filter.Comparison.EQUAL_TO,
  1309. property: 'layerID',
  1310. value: gisLayer.id
  1311. }),
  1312. symbolizer: gisLayer.style
  1313. });
  1314. });
  1315.  
  1316. setFillParcels(_settings.fillParcels);
  1317.  
  1318. let style = new OL.Style(DEFAULT_STYLE, { rules: rules } );
  1319.  
  1320. _mapLayer = new OL.Layer.Vector('GIS Layers - Default', {
  1321. uniqueName: 'wmeGISLayersDefault',
  1322. styleMap: new OL.StyleMap(style)
  1323. });
  1324.  
  1325. _roadLayer = new OL.Layer.Vector('GIS Layers - Roads', {
  1326. uniqueName: 'wmeGISLayersRoads',
  1327. styleMap: new OL.StyleMap(ROAD_STYLE)
  1328. });
  1329.  
  1330. _mapLayer.setVisibility(_settings.enabled);
  1331. _roadLayer.setVisibility(_settings.enabled);
  1332.  
  1333. W.map.addLayer(_roadLayer);
  1334. W.map.addLayer(_mapLayer);
  1335.  
  1336. } // END InitLayer
  1337.  
  1338. function initLayersTab() {
  1339. let user = W.loginManager.user.userName.toLowerCase();
  1340. let states = _.uniq(_gisLayers.map(l => l.state)).filter(st => _settings.selectedStates.indexOf(st) > -1);
  1341. $('#panel-gis-state-layers').empty();
  1342. $('#panel-gis-state-layers').append(
  1343. $('<div>', {class: 'controls-container'}).css({'padding-top':'2px'}).append(
  1344. $('<input>', {type:'checkbox', id:'only-show-applicable-gis-layers'}).change(function() { onOnlyShowApplicableLayersChanged($(this).is(':checked')); }).prop('checked', _settings.onlyShowApplicableLayers),
  1345. $('<label>', {for:'only-show-applicable-gis-layers'}).css({'white-space':'pre-line'}).text('Only show applicable layers')
  1346. ),
  1347. $('.gis-layers-state-checkbox:checked').length === 0 ? $('<div>').text('Turn on layer categories in the Settings tab.') : states.map(st => {
  1348. return $('<fieldset>', {id:'gis-layers-for-' + st, style:'border:1px solid silver;padding:8px;border-radius:4px;-webkit-padding-before: 0;'}).append(
  1349. $('<legend>', {style:'margin-bottom:0px;border-bottom-style:none;width:auto;'}).append($('<i>', {class:'fa fa-fw fa-chevron-down', style:'cursor: pointer;font-size: 12px;margin-right: 4px'}).click(function() {
  1350. $(this).toggleClass('fa fa-fw fa-chevron-down');
  1351. $(this).toggleClass('fa fa-fw fa-chevron-right');
  1352. $(`#${st}_body`).toggleClass('collapse');
  1353. }), $('<span>', {style:'font-size:14px;font-weight:600;text-transform: uppercase;'}).text(STATES.toFullName(st))),
  1354. $('<div>', {id:`${st}_body`}).append(
  1355. $('<div>').css({'font-size':'11px'}).append(
  1356. $('<span>').append(
  1357. 'Select ',
  1358. $('<a>', {href:'#'}).text('All').click(function(){
  1359. _ignoreFetch = true;
  1360. $(this).closest('fieldset').find('input').prop('checked', false).trigger('click');
  1361. _ignoreFetch = false;
  1362. saveSettingsToStorage();
  1363. fetchFeatures();
  1364. }),
  1365. ' / ',
  1366. $('<a>', {href:'#'}).text('None').click(function(){
  1367. _ignoreFetch = true;
  1368. $(this).closest('fieldset').find('input').prop('checked', true).trigger('click');
  1369. _ignoreFetch = false;
  1370. saveSettingsToStorage();
  1371. fetchFeatures();
  1372. })
  1373. )
  1374. ),
  1375. $('<div>', {class:'controls-container', style:'padding-top:0px;'}).append(
  1376. _gisLayers.filter(l => (l.state === st && (!PRIVATE_LAYERS.hasOwnProperty(l.id) || PRIVATE_LAYERS[l.id].indexOf(user) > -1))).map(gisLayer => {
  1377. let id = 'gis-layer-' + gisLayer.id;
  1378. return $('<div>', {class: 'controls-container', id: id+'-container'}).css({'padding-top':'2px', 'display':'block'}).append(
  1379. $('<input>', {type:'checkbox', id:id}).change(function() { onLayerToggleChanged($(this).is(':checked'), gisLayer.id); }).prop('checked', _settings.visibleLayers.indexOf(gisLayer.id) > -1),
  1380. $('<label>', {for:id, class:'gis-state-layer-label'}).css({'white-space':'pre-line'}).text(gisLayer.name)
  1381. );
  1382. })
  1383. )
  1384. )
  1385. );
  1386. })
  1387. );
  1388. }
  1389.  
  1390. function initSettingsTab() {
  1391. let states = _.uniq(_gisLayers.map(l => l.state));
  1392. let createRadioBtn = (name, value, text, checked) => {
  1393. let id = `${name}-${value}`;
  1394. return [$('<input>', {type:'radio', id:id, name:name, value:value}).prop('checked',checked),$('<label>', {for:id}).text(text).css({paddingLeft:'15px', marginRight:'4px'})];
  1395. };
  1396. $('#panel-gis-layers-settings').append(
  1397. $('<fieldset>', {style:'border:1px solid silver;padding:8px;border-radius:4px;-webkit-padding-before: 0;'}).append(
  1398. $('<legend>', {style:'margin-bottom:0px;border-bottom-style:none;width:auto;'}).append($('<span>', {style:'font-size:14px;font-weight:600;text-transform: uppercase;'}).text('Labels')),
  1399. $('<div>', {id:'labelSettings'}).append(
  1400. $('<div>', {class: 'controls-container'}).css({'padding-top':'2px'}).append(
  1401. $('<label>',{style:'font-weight:normal;'}).text('Addresses:'),
  1402. createRadioBtn('gisAddrDisplay', 'hn', 'HN', _settings.addrLabelDisplay === 'hn'),
  1403. createRadioBtn('gisAddrDisplay', 'street', 'Street', _settings.addrLabelDisplay === 'street'),
  1404. createRadioBtn('gisAddrDisplay', 'all', 'Both', _settings.addrLabelDisplay === 'all'),
  1405. $('<i>', {class:'waze-tooltip', id:'gisAddrDisplayInfo', 'data-toggle':'tooltip', style:'margin-left:8px; font-size:12px', 'data-placement':'bottom',
  1406. 'title':`This may not work properly for all layers. Please report issues to ${SCRIPT_AUTHOR}.`}).tooltip()
  1407. )
  1408. )
  1409. ),
  1410. $('<fieldset>', {style:'border:1px solid silver;padding:8px;border-radius:4px;-webkit-padding-before: 0;'}).append(
  1411. $('<legend>', {style:'margin-bottom:0px;border-bottom-style:none;width:auto;'}).append($('<span>', {style:'font-size:14px;font-weight:600;text-transform: uppercase;'}).text('Layer Categories')),
  1412. $('<div>', {id:'states_body'}).append(
  1413. $('<div>').css({'font-size':'11px'}).append(
  1414. $('<span>').append(
  1415. 'Select ',
  1416. $('<a>', {href:'#'}).text('All').click(function(){
  1417. _ignoreFetch = true;
  1418. $(this).closest('fieldset').find('input').prop('checked', false).trigger('click');
  1419. _ignoreFetch = false;
  1420. saveSettingsToStorage();
  1421. initLayersTab();
  1422. fetchFeatures();
  1423. }),
  1424. ' / ',
  1425. $('<a>', {href:'#'}).text('None').click(function(){
  1426. _ignoreFetch = true;
  1427. $(this).closest('fieldset').find('input').prop('checked', true).trigger('click');
  1428. _ignoreFetch = false;
  1429. saveSettingsToStorage();
  1430. initLayersTab();
  1431. fetchFeatures();
  1432. })
  1433. )
  1434. ),
  1435. $('<div>', {class:'controls-container', style:'padding-top:0px;'}).append(
  1436. states.map(st => {
  1437. let fullName = STATES.toFullName(st);
  1438. let id = 'gis-layer-enable-state-' + st;
  1439. return $('<div>', {class: 'controls-container'}).css({'padding-top':'2px','display':'block'}).append(
  1440. $('<input>', {type:'checkbox', id:id, class:'gis-layers-state-checkbox'}).change(function() { onStateCheckChanged($(this).is(':checked'), st); }).prop('checked', _settings.selectedStates.indexOf(st) > -1),
  1441. $('<label>', {for:id}).css({'white-space':'pre-line'}).text(fullName)
  1442. );
  1443. })
  1444. )
  1445. )
  1446. )
  1447. );
  1448. $('#panel-gis-layers-settings').append(
  1449. $('<fieldset>', {style:'border:1px solid silver;padding:8px;border-radius:4px;-webkit-padding-before: 0;'}).append(
  1450. $('<legend>', {style:'margin-bottom:0px;border-bottom-style:none;width:auto;'}).append($('<span>', {style:'font-size:14px;font-weight:600;text-transform: uppercase;'}).text('Appearance')),
  1451. // $('<div>', {class:'controls-container', style:'padding-top:0px;'}).append(
  1452. $('<div>', {class: 'controls-container'}).css({'padding-top':'2px'}).append(
  1453. $('<input>', {type:'checkbox', id:'fill-parcels'}).change(function() { onFillParcelsCheckedChanged($(this).is(':checked')); }).prop('checked', _settings.fillParcels),
  1454. $('<label>', {for:'fill-parcels'}).css({'white-space':'pre-line'}).text('Fill parcels')
  1455. )
  1456. // )
  1457. )
  1458. );
  1459. $('input[name=gisAddrDisplay]').on('change', function() {
  1460. _settings.addrLabelDisplay = $(this).val();
  1461. saveSettingsToStorage();
  1462. fetchFeatures();
  1463. });
  1464. }
  1465.  
  1466. function initTab() {
  1467. initSettingsTab();
  1468. initLayersTab();
  1469. if (!$('#gis-layers-power-btn').length) {
  1470. let color = _settings.enabled ? '#00bd00' : '#ccc';
  1471. $('a[href="#sidepanel-gis-l"]').prepend(
  1472. $('<span>', {class:'fa fa-power-off', id:'gis-layers-power-btn', style:'margin-right: 5px;cursor: pointer;color: ' + color + ';font-size: 13px;', title:'Toggle GIS Layers'}).click(function(evt) {
  1473. evt.stopPropagation();
  1474. setEnabled(!_settings.enabled);
  1475. })
  1476. );
  1477. }
  1478. }
  1479.  
  1480. function initGui() {
  1481. initLayer();
  1482.  
  1483. let content = $('<div>').append(
  1484. $('<span>', {style:'font-size:14px;font-weight:600'}).text('GIS Layers'),
  1485. $('<span>', {style:'font-size:11px;margin-left:10px;color:#aaa;'}).text(GM_info.script.version),
  1486. '<ul class="nav nav-tabs">' +
  1487. '<li class="active"><a data-toggle="tab" href="#panel-gis-state-layers" aria-expanded="true">Layers</a></li>' +
  1488. '<li><a data-toggle="tab" href="#panel-gis-layers-settings" aria-expanded="true">Settings</a></li>' +
  1489. '</ul>',
  1490. $('<div>', {class:'tab-content',style:'padding:8px;padding-top:2px'}).append(
  1491. $('<div>', {class:'tab-pane active', id:'panel-gis-state-layers'}),
  1492. $('<div>', {class:'tab-pane', id:'panel-gis-layers-settings'})
  1493. )
  1494. ).html();
  1495.  
  1496. new WazeWrap.Interface.Tab('GIS-L', content, initTab, null);
  1497. WazeWrap.Interface.AddLayerCheckbox('Display', 'GIS Layers', _settings.enabled, onLayerCheckboxChanged);
  1498. W.map.events.register('moveend',null,onMapMove);
  1499. showScriptInfoAlert();
  1500. }
  1501.  
  1502. function loadSpreadsheetAsync() {
  1503. return new Promise((resolve, reject) => {
  1504. $.get({
  1505. url: LAYER_DEF_URL,
  1506. success: function(data) {
  1507. // Critical fields that must be present in the spreadsheet, or script cannot process the data correctly.
  1508. // If any of these are still null after processing the fields entry, there's a problem.
  1509. const EXPECTED_FIELD_NAMES = ['state','name','id','counties','url','where','labelFields','processLabel','style','visibleAtZoom','labelsVisibleAtZoom','enabled'];
  1510. let ssFieldNames;
  1511. let result = {error:null};
  1512. let checkFieldNames = fldName => ssFieldNames.indexOf(fldName) > -1;
  1513.  
  1514. for(let entryIdx = 0; entryIdx < data.feed.entry.length && !result.error; entryIdx++) {
  1515. let cellValue = data.feed.entry[entryIdx].title.$t;
  1516. if (entryIdx === 0) {
  1517. // The minimum script version that the spreadsheet supports.
  1518. if (SCRIPT_VERSION < cellValue) {
  1519. result.error = 'Script must be updated to at least version ' + cellValue + ' before layer definitions can be loaded.';
  1520. }
  1521. } else if (entryIdx === 1) {
  1522. // Process field names
  1523. ssFieldNames = cellValue.split('|').map(fldName => fldName.trim());
  1524. if (ssFieldNames.length < EXPECTED_FIELD_NAMES.length) {
  1525. result.error = 'Expected ' + EXPECTED_FIELD_NAMES.length + ' columns in layer definition data. Spreadsheet returned ' + ssFieldNames.length + '.';
  1526. } else if (!EXPECTED_FIELD_NAMES.every(fldName => checkFieldNames(fldName))) {
  1527. result.error = 'Script expected to see the following column names in the layer definition spreadsheet:\n' + EXPECTED_FIELD_NAMES.join(', ') + '\nBut the spreadsheet returned these:\n' + ssFieldNames.join(', ');
  1528. }
  1529. } else {
  1530. let values = cellValue.split('|');
  1531. if (values[ssFieldNames.indexOf('enabled')]) {
  1532. let layerDef = {};
  1533. ssFieldNames.forEach((fldName, fldIdx) => {
  1534. let value = values[fldIdx];
  1535. if (value.toString().length > 0) {
  1536. if (fldName === 'counties' || fldName === 'labelFields') {
  1537. value = value.split(',').map(item => item.trim());
  1538. } else if (fldName === 'processLabel') {
  1539. try {
  1540. value = eval('(function(label, fieldValues){' + value + '})');
  1541. } catch (ex) {
  1542. logError('Error loading label processing function for layer "' + layerDef.id + '".');
  1543. logDebug(ex);
  1544. }
  1545. } else if (fldName === 'style') {
  1546. layerDef.isRoadLayer = value === 'roads';
  1547. if (LAYER_STYLES.hasOwnProperty(value)) {
  1548. value = LAYER_STYLES[value];
  1549. }
  1550. // If layer is not defined, allow the value to be set as-is because it could be a custom style.
  1551. // *** THIS NEEDS TO BE TESTED ***
  1552. }
  1553. layerDef[fldName] = value;
  1554. } else if (fldName === 'labelFields') {
  1555. layerDef[fldName] = [''];
  1556. }
  1557. });
  1558. if (layerDef.enabled && ['0','false','no','n'].indexOf(layerDef.enabled.toString().trim().toLowerCase()) === -1) {
  1559. _gisLayers.push(layerDef);
  1560. }
  1561. }
  1562. }
  1563. }
  1564. resolve(result);
  1565. },
  1566. error: function() {
  1567. reject({message: 'An error occurred while loading the GIS layer definition spreadsheet.'});
  1568. }
  1569. });
  1570. });
  1571. }
  1572.  
  1573. function init() {
  1574. installPathFollowingLabels();
  1575. let t0 = performance.now();
  1576. loadSpreadsheetAsync().then(result => {
  1577. if (result.error) {
  1578. logError(result.error);
  1579. return;
  1580. }
  1581. _layerRefinements.forEach(layerRefinement => {
  1582. let layerDef = _gisLayers.find(layerDef => layerDef.id === layerRefinement.id);
  1583. if (layerDef) {
  1584. Object.keys(layerRefinement).forEach((fldName) => {
  1585. let value = layerRefinement[fldName];
  1586. if (fldName !== 'id' && layerDef.hasOwnProperty(fldName)) {
  1587. logDebug('The "' + fldName + '" property of layer "' + layerDef.id + '" has a value hardcoded in the script, and also defined in the spreadsheet. The spreadsheet value takes precedence.');
  1588. } else {
  1589. if (value) layerDef[fldName] = value;
  1590. }
  1591. });
  1592. } else {
  1593. logDebug('Refined layer "' + layerRefinement.id + '" does not have a corresponding layer defined in the spreadsheet. It can probably be removed from the script.');
  1594. }
  1595. });
  1596. logDebug('Loaded ' + _gisLayers.length + ' layer definitions in ' + Math.round(performance.now() - t0) + ' ms.');
  1597. loadSettingsFromStorage();
  1598. initGui();
  1599. fetchFeatures();
  1600. log('Initialized.');
  1601. }).catch(err => {
  1602. let msg;
  1603. if (err && err.message) {
  1604. msg = err.message;
  1605. } else {
  1606. msg = err;
  1607. }
  1608. logError(msg);
  1609. });
  1610. }
  1611.  
  1612. function bootstrap() {
  1613. if (W && W.loginManager && W.map && W.loginManager.user && W.model && W.model.states && W.model.states.getObjectArray().length) {
  1614. log('Initializing...');
  1615. init();
  1616. } else {
  1617. log('Bootstrap failed. Trying again...');
  1618. setTimeout(function () {
  1619. bootstrap();
  1620. }, 1000);
  1621. }
  1622. }
  1623.  
  1624. bootstrap();
  1625.  
  1626. function installPathFollowingLabels() {
  1627. // Copyright (c) 2015 by Jean-Marc.Viglino [at]ign.fr
  1628. // Dual-licensed under the CeCILL-B Licence (http://www.cecill.info/)
  1629. // and the Beerware license (http://en.wikipedia.org/wiki/Beerware),
  1630. // feel free to use and abuse it in your projects (the code, not the beer ;-).
  1631. //
  1632. //* Overwrite the SVG function to allow text along a path
  1633. //* setStyle function
  1634. //*
  1635. //* Add new options to the Openlayers.Style
  1636.  
  1637. // pathLabel: {String} Label to draw on the path
  1638. // pathLabelXOffset: {String} Offset along the line to start drawing text in pixel or %, default: "50%"
  1639. // pathLabelYOffset: {Number} Distance of the line to draw the text
  1640. // pathLabelCurve: {String} Smooth the line the label is drawn on (empty string for no)
  1641. // pathLabelReadable: {String} Make the label readable (empty string for no)
  1642.  
  1643. // * Extra standard values : all label and text values
  1644.  
  1645.  
  1646. // *
  1647. // * Method: removeChildById
  1648. // * Remove child in a node.
  1649. // *
  1650.  
  1651. function removeChildById(node,id) {
  1652. if (node.querySelector) {
  1653. var c = node.querySelector('#'+id);
  1654. if (c) node.removeChild(c);
  1655. return;
  1656. }
  1657. // For old browsers
  1658. var c = node.childNodes;
  1659. if (c) for (var i=0; i<c.length; i++) {
  1660. if (c[i].id === id) {
  1661. node.removeChild(c[i]);
  1662. return;
  1663. }
  1664. }
  1665. }
  1666.  
  1667.  
  1668. // *
  1669. // * Method: setStyle
  1670. // * Use to set all the style attributes to a SVG node.
  1671. // *
  1672. // * Takes care to adjust stroke width and point radius to be
  1673. // * resolution-relative
  1674. // *
  1675. // * Parameters:
  1676. // * node - {SVGDomElement} An SVG element to decorate
  1677. // * style - {Object}
  1678. // * options - {Object} Currently supported options include
  1679. // * 'isFilled' {Boolean} and
  1680. // * 'isStroked' {Boolean}
  1681.  
  1682. var setStyle = OL.Renderer.SVG.prototype.setStyle;
  1683. OL.Renderer.SVG.LABEL_STARTOFFSET = { 'l':'0%', 'r':'100%', 'm':'50%' };
  1684.  
  1685. OL.Renderer.SVG.prototype.pathText = function (node, style, suffix) {
  1686. var label = this.nodeFactory(null, 'text');
  1687. label.setAttribute('id',node._featureId+'_'+suffix);
  1688. if (style.fontColor) label.setAttributeNS(null, 'fill', style.fontColor);
  1689. if (style.fontStrokeColor) label.setAttributeNS(null, 'stroke', style.fontStrokeColor);
  1690. if (style.fontStrokeWidth) label.setAttributeNS(null, 'stroke-width', style.fontStrokeWidth);
  1691. if (style.fontOpacity) label.setAttributeNS(null, 'opacity', style.fontOpacity);
  1692. if (style.fontFamily) label.setAttributeNS(null, 'font-family', style.fontFamily);
  1693. if (style.fontSize) label.setAttributeNS(null, 'font-size', style.fontSize);
  1694. if (style.fontWeight) label.setAttributeNS(null, 'font-weight', style.fontWeight);
  1695. if (style.fontStyle) label.setAttributeNS(null, 'font-style', style.fontStyle);
  1696. if (style.labelSelect === true) {
  1697. label.setAttributeNS(null, 'pointer-events', 'visible');
  1698. label._featureId = node._featureId;
  1699. } else {
  1700. label.setAttributeNS(null, 'pointer-events', 'none');
  1701. }
  1702.  
  1703. function getpath (pathStr, readeable) {
  1704. var npath = pathStr.split(',');
  1705. var pts = [];
  1706. if (!readeable || Number(npath[0]) - Number(npath[npath.length-2]) < 0) {
  1707. while (npath.length) pts.push ( { x:Number(npath.shift()), y:Number(npath.shift()) } );
  1708. } else {
  1709. while (npath.length) pts.unshift ( { x:Number(npath.shift()), y:Number(npath.shift()) } );
  1710. }
  1711. return pts;
  1712. }
  1713.  
  1714. var path = this.nodeFactory(null, 'path');
  1715. var tpid = node._featureId+'_t'+suffix;
  1716. var tpath = node.getAttribute('points');
  1717. if (style.pathLabelCurve) {
  1718. var pts = getpath (tpath, style.pathLabelReadable);
  1719. var p = pts[0].x+' '+pts[0].y;
  1720. var dx, dy, s1, s2;
  1721. dx = (pts[0].x-pts[1].x)/4;
  1722. dy = (pts[0].y-pts[1].y)/4;
  1723. for (var i=1; i<pts.length-1; i++) {
  1724. p += ' C '+(pts[i-1].x-dx)+' '+(pts[i-1].y-dy);
  1725. dx = (pts[i-1].x-pts[i+1].x)/4;
  1726. dy = (pts[i-1].y-pts[i+1].y)/4;
  1727. s1 = Math.sqrt( Math.pow(pts[i-1].x-pts[i].x,2)+ Math.pow(pts[i-1].y-pts[i].y,2) );
  1728. s2 = Math.sqrt( Math.pow(pts[i+1].x-pts[i].x,2)+ Math.pow(pts[i+1].y-pts[i].y,2) );
  1729. p += ' '+(pts[i].x+s1*dx/s2)+' '+(pts[i].y+s1*dy/s2);
  1730. dx *= s2/s1;
  1731. dy *= s2/s1;
  1732. p += ' '+pts[i].x+' '+pts[i].y;
  1733. }
  1734. p += ' C '+(pts[i-1].x-dx)+' '+(pts[i-1].y-dy);
  1735. dx = (pts[i-1].x-pts[i].x )/4;
  1736. dy = (pts[i-1].y-pts[i].y )/4;
  1737. p += ' '+(pts[i].x+dx)+' '+(pts[i].y+dy);
  1738. p += ' '+pts[i].x+' '+pts[i].y;
  1739.  
  1740. path.setAttribute('d','M '+p);
  1741. } else {
  1742. if (style.pathLabelReadable) {
  1743. var pts = getpath (tpath, style.pathLabelReadable);
  1744. var p='';
  1745. for (var i=0; i<pts.length; i++) p += ' '+pts[i].x+' '+pts[i].y;
  1746. path.setAttribute('d','M '+p);
  1747. } else path.setAttribute('d','M '+tpath);
  1748. }
  1749. path.setAttribute('id',tpid);
  1750.  
  1751. var defs = this.createDefs();
  1752. removeChildById (defs, tpid);
  1753. defs.appendChild(path);
  1754.  
  1755. var textPath = this.nodeFactory(null, 'textPath');
  1756. textPath.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', '#'+tpid);
  1757. var align = style.labelAlign || OL.Renderer.defaultSymbolizer.labelAlign;
  1758. label.setAttributeNS(null, 'text-anchor', OL.Renderer.SVG.LABEL_ALIGN[align[0]] || 'middle');
  1759. textPath.setAttribute('startOffset', style.pathLabelXOffset || OL.Renderer.SVG.LABEL_STARTOFFSET[align[0]] || '50%');
  1760. label.setAttributeNS(null, 'dominant-baseline', OL.Renderer.SVG.LABEL_ALIGN[align[1]] || 'central');
  1761. if (style.pathLabelYOffset) label.setAttribute('dy', style.pathLabelYOffset);
  1762. //textPath.setAttribute('method','stretch');
  1763. //textPath.setAttribute('spacing','auto');
  1764.  
  1765. textPath.textContent = style.pathLabel;
  1766. label.appendChild(textPath);
  1767.  
  1768. removeChildById (this.textRoot, node._featureId+'_'+suffix);
  1769. this.textRoot.appendChild(label);
  1770. };
  1771.  
  1772. OL.Renderer.SVG.prototype.setStyle = function(node, style, options) {
  1773. if (node._geometryClass === 'OpenLayers.Geometry.LineString' && style.pathLabel) {
  1774. if (node._geometryClass === 'OpenLayers.Geometry.LineString' && style.pathLabel) {
  1775. var drawOutline = (!!style.labelOutlineWidth);
  1776. // First draw text in halo color and size and overlay the
  1777. // normal text afterwards
  1778. if (drawOutline) {
  1779. var outlineStyle = OL.Util.extend({}, style);
  1780. outlineStyle.fontColor = outlineStyle.labelOutlineColor;
  1781. outlineStyle.fontStrokeColor = outlineStyle.labelOutlineColor;
  1782. outlineStyle.fontStrokeWidth = style.labelOutlineWidth;
  1783. if (style.labelOutlineOpacity) outlineStyle.fontOpacity = style.labelOutlineOpacity;
  1784. delete outlineStyle.labelOutlineWidth;
  1785. this.pathText(node, outlineStyle, 'txtpath0');
  1786. }
  1787. this.pathText(node, style, 'txtpath');
  1788. setStyle.apply(this,arguments);
  1789. }
  1790. } else setStyle.apply(this,arguments);
  1791. return node;
  1792. };
  1793.  
  1794. // *
  1795. // * Method: drawGeometry
  1796. // * Remove the textpath if no geometry is drawn.
  1797. // *
  1798. // * Parameters:
  1799. // * geometry - {<OpenLayers.Geometry>}
  1800. // * style - {Object}
  1801. // * featureId - {String}
  1802. // *
  1803. // * Returns:
  1804. // * {Boolean} true if the geometry has been drawn completely; null if
  1805. // * incomplete; false otherwise
  1806.  
  1807. var drawGeometry = OL.Renderer.SVG.prototype.drawGeometry;
  1808. OL.Renderer.SVG.prototype.drawGeometry = function(geometry, style, id) {
  1809. var rendered = drawGeometry.apply(this,arguments);
  1810. if (rendered === false) {
  1811. removeChildById(this.textRoot, id+'_txtpath');
  1812. removeChildById(this.textRoot, id+'_txtpath0');
  1813. }
  1814. return rendered;
  1815. };
  1816.  
  1817. // *
  1818. // * Method: eraseGeometry
  1819. // * Erase a geometry from the renderer. In the case of a multi-geometry,
  1820. // * we cycle through and recurse on ourselves. Otherwise, we look for a
  1821. // * node with the geometry.id, destroy its geometry, and remove it from
  1822. // * the DOM.
  1823. // *
  1824. // * Parameters:
  1825. // * geometry - {<OpenLayers.Geometry>}
  1826. // * featureId - {String}
  1827.  
  1828. var eraseGeometry = OL.Renderer.SVG.prototype.eraseGeometry;
  1829. OL.Renderer.SVG.prototype.eraseGeometry = function(geometry, featureId) {
  1830. eraseGeometry.apply(this,arguments);
  1831. removeChildById(this.textRoot, featureId+'_txtpath');
  1832. removeChildById(this.textRoot, featureId+'_txtpath0');
  1833. };
  1834.  
  1835. }
  1836. })();