WME GIS Layers

Adds GIS layers in WME

当前为 2019-01-25 提交的版本,查看 最新版本

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