KmlDataSource.js 134 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230
  1. import ArcType from '../Core/ArcType.js';
  2. import AssociativeArray from '../Core/AssociativeArray.js';
  3. import BoundingRectangle from '../Core/BoundingRectangle.js';
  4. import Cartesian2 from '../Core/Cartesian2.js';
  5. import Cartesian3 from '../Core/Cartesian3.js';
  6. import Cartographic from '../Core/Cartographic.js';
  7. import ClockRange from '../Core/ClockRange.js';
  8. import ClockStep from '../Core/ClockStep.js';
  9. import clone from '../Core/clone.js';
  10. import Color from '../Core/Color.js';
  11. import createGuid from '../Core/createGuid.js';
  12. import Credit from '../Core/Credit.js';
  13. import defaultValue from '../Core/defaultValue.js';
  14. import defined from '../Core/defined.js';
  15. import defineProperties from '../Core/defineProperties.js';
  16. import DeveloperError from '../Core/DeveloperError.js';
  17. import Ellipsoid from '../Core/Ellipsoid.js';
  18. import Event from '../Core/Event.js';
  19. import getExtensionFromUri from '../Core/getExtensionFromUri.js';
  20. import getFilenameFromUri from '../Core/getFilenameFromUri.js';
  21. import getTimestamp from '../Core/getTimestamp.js';
  22. import HeadingPitchRange from '../Core/HeadingPitchRange.js';
  23. import HeadingPitchRoll from '../Core/HeadingPitchRoll.js';
  24. import Iso8601 from '../Core/Iso8601.js';
  25. import JulianDate from '../Core/JulianDate.js';
  26. import CesiumMath from '../Core/Math.js';
  27. import NearFarScalar from '../Core/NearFarScalar.js';
  28. import objectToQuery from '../Core/objectToQuery.js';
  29. import oneTimeWarning from '../Core/oneTimeWarning.js';
  30. import PinBuilder from '../Core/PinBuilder.js';
  31. import PolygonHierarchy from '../Core/PolygonHierarchy.js';
  32. import queryToObject from '../Core/queryToObject.js';
  33. import Rectangle from '../Core/Rectangle.js';
  34. import Resource from '../Core/Resource.js';
  35. import RuntimeError from '../Core/RuntimeError.js';
  36. import TimeInterval from '../Core/TimeInterval.js';
  37. import TimeIntervalCollection from '../Core/TimeIntervalCollection.js';
  38. import HeightReference from '../Scene/HeightReference.js';
  39. import HorizontalOrigin from '../Scene/HorizontalOrigin.js';
  40. import LabelStyle from '../Scene/LabelStyle.js';
  41. import SceneMode from '../Scene/SceneMode.js';
  42. import Autolinker from '../ThirdParty/Autolinker.js';
  43. import Uri from '../ThirdParty/Uri.js';
  44. import when from '../ThirdParty/when.js';
  45. import zip from '../ThirdParty/zip.js';
  46. import BillboardGraphics from './BillboardGraphics.js';
  47. import CompositePositionProperty from './CompositePositionProperty.js';
  48. import DataSource from './DataSource.js';
  49. import DataSourceClock from './DataSourceClock.js';
  50. import Entity from './Entity.js';
  51. import EntityCluster from './EntityCluster.js';
  52. import EntityCollection from './EntityCollection.js';
  53. import KmlCamera from './KmlCamera.js';
  54. import KmlLookAt from './KmlLookAt.js';
  55. import KmlTour from './KmlTour.js';
  56. import KmlTourFlyTo from './KmlTourFlyTo.js';
  57. import KmlTourWait from './KmlTourWait.js';
  58. import LabelGraphics from './LabelGraphics.js';
  59. import PathGraphics from './PathGraphics.js';
  60. import PolygonGraphics from './PolygonGraphics.js';
  61. import PolylineGraphics from './PolylineGraphics.js';
  62. import PositionPropertyArray from './PositionPropertyArray.js';
  63. import RectangleGraphics from './RectangleGraphics.js';
  64. import ReferenceProperty from './ReferenceProperty.js';
  65. import SampledPositionProperty from './SampledPositionProperty.js';
  66. import ScaledPositionProperty from './ScaledPositionProperty.js';
  67. import TimeIntervalCollectionProperty from './TimeIntervalCollectionProperty.js';
  68. import WallGraphics from './WallGraphics.js';
  69. //This is by no means an exhaustive list of MIME types.
  70. //The purpose of this list is to be able to accurately identify content embedded
  71. //in KMZ files. Eventually, we can make this configurable by the end user so they can add
  72. //there own content types if they have KMZ files that require it.
  73. var MimeTypes = {
  74. avi : 'video/x-msvideo',
  75. bmp : 'image/bmp',
  76. bz2 : 'application/x-bzip2',
  77. chm : 'application/vnd.ms-htmlhelp',
  78. css : 'text/css',
  79. csv : 'text/csv',
  80. doc : 'application/msword',
  81. dvi : 'application/x-dvi',
  82. eps : 'application/postscript',
  83. flv : 'video/x-flv',
  84. gif : 'image/gif',
  85. gz : 'application/x-gzip',
  86. htm : 'text/html',
  87. html : 'text/html',
  88. ico : 'image/vnd.microsoft.icon',
  89. jnlp : 'application/x-java-jnlp-file',
  90. jpeg : 'image/jpeg',
  91. jpg : 'image/jpeg',
  92. m3u : 'audio/x-mpegurl',
  93. m4v : 'video/mp4',
  94. mathml : 'application/mathml+xml',
  95. mid : 'audio/midi',
  96. midi : 'audio/midi',
  97. mov : 'video/quicktime',
  98. mp3 : 'audio/mpeg',
  99. mp4 : 'video/mp4',
  100. mp4v : 'video/mp4',
  101. mpeg : 'video/mpeg',
  102. mpg : 'video/mpeg',
  103. odp : 'application/vnd.oasis.opendocument.presentation',
  104. ods : 'application/vnd.oasis.opendocument.spreadsheet',
  105. odt : 'application/vnd.oasis.opendocument.text',
  106. ogg : 'application/ogg',
  107. pdf : 'application/pdf',
  108. png : 'image/png',
  109. pps : 'application/vnd.ms-powerpoint',
  110. ppt : 'application/vnd.ms-powerpoint',
  111. ps : 'application/postscript',
  112. qt : 'video/quicktime',
  113. rdf : 'application/rdf+xml',
  114. rss : 'application/rss+xml',
  115. rtf : 'application/rtf',
  116. svg : 'image/svg+xml',
  117. swf : 'application/x-shockwave-flash',
  118. text : 'text/plain',
  119. tif : 'image/tiff',
  120. tiff : 'image/tiff',
  121. txt : 'text/plain',
  122. wav : 'audio/x-wav',
  123. wma : 'audio/x-ms-wma',
  124. wmv : 'video/x-ms-wmv',
  125. xml : 'application/xml',
  126. zip : 'application/zip',
  127. detectFromFilename : function(filename) {
  128. var ext = filename.toLowerCase();
  129. ext = getExtensionFromUri(ext);
  130. return MimeTypes[ext];
  131. }
  132. };
  133. var parser;
  134. if (typeof DOMParser !== 'undefined') {
  135. parser = new DOMParser();
  136. }
  137. var autolinker = new Autolinker({
  138. stripPrefix : false,
  139. email : false,
  140. replaceFn : function(match) {
  141. if (!match.protocolUrlMatch) {
  142. //Prevent matching of non-explicit urls.
  143. //i.e. foo.id won't match but http://foo.id will
  144. return false;
  145. }
  146. }
  147. });
  148. var BILLBOARD_SIZE = 32;
  149. var BILLBOARD_NEAR_DISTANCE = 2414016;
  150. var BILLBOARD_NEAR_RATIO = 1.0;
  151. var BILLBOARD_FAR_DISTANCE = 1.6093e+7;
  152. var BILLBOARD_FAR_RATIO = 0.1;
  153. var kmlNamespaces = [null, undefined, 'http://www.opengis.net/kml/2.2', 'http://earth.google.com/kml/2.2', 'http://earth.google.com/kml/2.1', 'http://earth.google.com/kml/2.0'];
  154. var gxNamespaces = ['http://www.google.com/kml/ext/2.2'];
  155. var atomNamespaces = ['http://www.w3.org/2005/Atom'];
  156. var namespaces = {
  157. kml : kmlNamespaces,
  158. gx : gxNamespaces,
  159. atom : atomNamespaces,
  160. kmlgx : kmlNamespaces.concat(gxNamespaces)
  161. };
  162. // Ensure Specs/Data/KML/unsupported.kml is kept up to date with these supported types
  163. var featureTypes = {
  164. Document : processDocument,
  165. Folder : processFolder,
  166. Placemark : processPlacemark,
  167. NetworkLink : processNetworkLink,
  168. GroundOverlay : processGroundOverlay,
  169. PhotoOverlay : processUnsupportedFeature,
  170. ScreenOverlay : processUnsupportedFeature,
  171. Tour : processTour
  172. };
  173. function DeferredLoading(dataSource) {
  174. this._dataSource = dataSource;
  175. this._deferred = when.defer();
  176. this._stack = [];
  177. this._promises = [];
  178. this._timeoutSet = false;
  179. this._used = false;
  180. this._started = 0;
  181. this._timeThreshold = 1000; // Initial load is 1 second
  182. }
  183. defineProperties(DeferredLoading.prototype, {
  184. dataSource : {
  185. get : function() {
  186. return this._dataSource;
  187. }
  188. }
  189. });
  190. DeferredLoading.prototype.addNodes = function(nodes, processingData) {
  191. this._stack.push({
  192. nodes: nodes,
  193. index: 0,
  194. processingData: processingData
  195. });
  196. this._used = true;
  197. };
  198. DeferredLoading.prototype.addPromise = function(promise) {
  199. this._promises.push(promise);
  200. };
  201. DeferredLoading.prototype.wait = function() {
  202. // Case where we had a non-document/folder as the root
  203. var deferred = this._deferred;
  204. if (!this._used) {
  205. deferred.resolve();
  206. }
  207. return when.join(deferred.promise, when.all(this._promises));
  208. };
  209. DeferredLoading.prototype.process = function() {
  210. var isFirstCall = (this._stack.length === 1);
  211. if (isFirstCall) {
  212. this._started = KmlDataSource._getTimestamp();
  213. }
  214. return this._process(isFirstCall);
  215. };
  216. DeferredLoading.prototype._giveUpTime = function() {
  217. if (this._timeoutSet) {
  218. // Timeout was already set so just return
  219. return;
  220. }
  221. this._timeoutSet = true;
  222. this._timeThreshold = 50; // After the first load lower threshold to 0.5 seconds
  223. var that = this;
  224. setTimeout(function() {
  225. that._timeoutSet = false;
  226. that._started = KmlDataSource._getTimestamp();
  227. that._process(true);
  228. }, 0);
  229. };
  230. DeferredLoading.prototype._nextNode = function() {
  231. var stack = this._stack;
  232. var top = stack[stack.length-1];
  233. var index = top.index;
  234. var nodes = top.nodes;
  235. if (index === nodes.length) {
  236. return;
  237. }
  238. ++top.index;
  239. return nodes[index];
  240. };
  241. DeferredLoading.prototype._pop = function() {
  242. var stack = this._stack;
  243. stack.pop();
  244. // Return false if we are done
  245. if (stack.length === 0) {
  246. this._deferred.resolve();
  247. return false;
  248. }
  249. return true;
  250. };
  251. DeferredLoading.prototype._process = function(isFirstCall) {
  252. var dataSource = this.dataSource;
  253. var processingData = this._stack[this._stack.length-1].processingData;
  254. var child = this._nextNode();
  255. while(defined(child)) {
  256. var featureProcessor = featureTypes[child.localName];
  257. if(defined(featureProcessor) &&
  258. ((namespaces.kml.indexOf(child.namespaceURI) !== -1) || (namespaces.gx.indexOf(child.namespaceURI) !== -1))) {
  259. featureProcessor(dataSource, child, processingData, this);
  260. // Give up time and continue loading later
  261. if (this._timeoutSet || (KmlDataSource._getTimestamp() > (this._started + this._timeThreshold))) {
  262. this._giveUpTime();
  263. return;
  264. }
  265. }
  266. child = this._nextNode();
  267. }
  268. // If we are a recursive call from a subfolder, just return so the parent folder can continue processing
  269. // If we aren't then make another call to processNodes because there is stuff still left in the queue
  270. if (this._pop() && isFirstCall) {
  271. this._process(true);
  272. }
  273. };
  274. function isZipFile(blob) {
  275. var magicBlob = blob.slice(0, Math.min(4, blob.size));
  276. var deferred = when.defer();
  277. var reader = new FileReader();
  278. reader.addEventListener('load', function() {
  279. deferred.resolve(new DataView(reader.result).getUint32(0, false) === 0x504b0304);
  280. });
  281. reader.addEventListener('error', function() {
  282. deferred.reject(reader.error);
  283. });
  284. reader.readAsArrayBuffer(magicBlob);
  285. return deferred.promise;
  286. }
  287. function readBlobAsText(blob) {
  288. var deferred = when.defer();
  289. var reader = new FileReader();
  290. reader.addEventListener('load', function() {
  291. deferred.resolve(reader.result);
  292. });
  293. reader.addEventListener('error', function() {
  294. deferred.reject(reader.error);
  295. });
  296. reader.readAsText(blob);
  297. return deferred.promise;
  298. }
  299. function insertNamespaces(text) {
  300. var namespaceMap = {
  301. xsi : 'http://www.w3.org/2001/XMLSchema-instance'
  302. };
  303. var firstPart, lastPart, reg, declaration;
  304. for (var key in namespaceMap) {
  305. if (namespaceMap.hasOwnProperty(key)) {
  306. reg = RegExp('[< ]' + key + ':');
  307. declaration = 'xmlns:' + key + '=';
  308. if (reg.test(text) && text.indexOf(declaration) === -1) {
  309. if (!defined(firstPart)) {
  310. firstPart = text.substr(0, text.indexOf('<kml') + 4);
  311. lastPart = text.substr(firstPart.length);
  312. }
  313. firstPart += ' ' + declaration + '"' + namespaceMap[key] + '"';
  314. }
  315. }
  316. }
  317. if (defined(firstPart)) {
  318. text = firstPart + lastPart;
  319. }
  320. return text;
  321. }
  322. function removeDuplicateNamespaces(text) {
  323. var index = text.indexOf('xmlns:');
  324. var endDeclaration = text.indexOf('>', index);
  325. var namespace, startIndex, endIndex;
  326. while ((index !== -1) && (index < endDeclaration)) {
  327. namespace = text.slice(index, text.indexOf('\"', index));
  328. startIndex = index;
  329. index = text.indexOf(namespace, index + 1);
  330. if (index !== -1) {
  331. endIndex = text.indexOf('\"', (text.indexOf('\"', index) + 1));
  332. text = text.slice(0, index -1) + text.slice(endIndex + 1, text.length);
  333. index = text.indexOf('xmlns:', startIndex - 1);
  334. } else {
  335. index = text.indexOf('xmlns:', startIndex + 1);
  336. }
  337. }
  338. return text;
  339. }
  340. function loadXmlFromZip(entry, uriResolver, deferred) {
  341. entry.getData(new zip.TextWriter(), function(text) {
  342. text = insertNamespaces(text);
  343. text = removeDuplicateNamespaces(text);
  344. uriResolver.kml = parser.parseFromString(text, 'application/xml');
  345. deferred.resolve();
  346. });
  347. }
  348. function loadDataUriFromZip(entry, uriResolver, deferred) {
  349. var mimeType = defaultValue(MimeTypes.detectFromFilename(entry.filename), 'application/octet-stream');
  350. entry.getData(new zip.Data64URIWriter(mimeType), function(dataUri) {
  351. uriResolver[entry.filename] = dataUri;
  352. deferred.resolve();
  353. });
  354. }
  355. function embedDataUris(div, elementType, attributeName, uriResolver) {
  356. var keys = uriResolver.keys;
  357. var baseUri = new Uri('.');
  358. var elements = div.querySelectorAll(elementType);
  359. for (var i = 0; i < elements.length; i++) {
  360. var element = elements[i];
  361. var value = element.getAttribute(attributeName);
  362. var uri = new Uri(value).resolve(baseUri).toString();
  363. var index = keys.indexOf(uri);
  364. if (index !== -1) {
  365. var key = keys[index];
  366. element.setAttribute(attributeName, uriResolver[key]);
  367. if (elementType === 'a' && element.getAttribute('download') === null) {
  368. element.setAttribute('download', key);
  369. }
  370. }
  371. }
  372. }
  373. function applyBasePath(div, elementType, attributeName, sourceResource) {
  374. var elements = div.querySelectorAll(elementType);
  375. for (var i = 0; i < elements.length; i++) {
  376. var element = elements[i];
  377. var value = element.getAttribute(attributeName);
  378. var resource = resolveHref(value, sourceResource);
  379. element.setAttribute(attributeName, resource.url);
  380. }
  381. }
  382. // an optional context is passed to allow for some malformed kmls (those with multiple geometries with same ids) to still parse
  383. // correctly, as they do in Google Earth.
  384. function createEntity(node, entityCollection, context) {
  385. var id = queryStringAttribute(node, 'id');
  386. id = defined(id) && id.length !== 0 ? id : createGuid();
  387. if (defined(context)) {
  388. id = context + id;
  389. }
  390. // If we have a duplicate ID just generate one.
  391. // This isn't valid KML but Google Earth handles this case.
  392. var entity = entityCollection.getById(id);
  393. if (defined(entity)) {
  394. id = createGuid();
  395. if (defined(context)) {
  396. id = context + id;
  397. }
  398. }
  399. entity = entityCollection.add(new Entity({id : id}));
  400. if (!defined(entity.kml)) {
  401. entity.addProperty('kml');
  402. entity.kml = new KmlFeatureData();
  403. }
  404. return entity;
  405. }
  406. function isExtrudable(altitudeMode, gxAltitudeMode) {
  407. return altitudeMode === 'absolute' || altitudeMode === 'relativeToGround' || gxAltitudeMode === 'relativeToSeaFloor';
  408. }
  409. function readCoordinate(value, ellipsoid) {
  410. //Google Earth treats empty or missing coordinates as 0.
  411. if (!defined(value)) {
  412. return Cartesian3.fromDegrees(0, 0, 0, ellipsoid);
  413. }
  414. var digits = value.match(/[^\s,\n]+/g);
  415. if (!defined(digits)) {
  416. return Cartesian3.fromDegrees(0, 0, 0, ellipsoid);
  417. }
  418. var longitude = parseFloat(digits[0]);
  419. var latitude = parseFloat(digits[1]);
  420. var height = parseFloat(digits[2]);
  421. longitude = isNaN(longitude) ? 0.0 : longitude;
  422. latitude = isNaN(latitude) ? 0.0 : latitude;
  423. height = isNaN(height) ? 0.0 : height;
  424. return Cartesian3.fromDegrees(longitude, latitude, height, ellipsoid);
  425. }
  426. function readCoordinates(element, ellipsoid) {
  427. if (!defined(element)) {
  428. return undefined;
  429. }
  430. var tuples = element.textContent.match(/[^\s\n]+/g);
  431. if (!defined(tuples)) {
  432. return undefined;
  433. }
  434. var length = tuples.length;
  435. var result = new Array(length);
  436. var resultIndex = 0;
  437. for (var i = 0; i < length; i++) {
  438. result[resultIndex++] = readCoordinate(tuples[i], ellipsoid);
  439. }
  440. return result;
  441. }
  442. function queryNumericAttribute(node, attributeName) {
  443. if (!defined(node)) {
  444. return undefined;
  445. }
  446. var value = node.getAttribute(attributeName);
  447. if (value !== null) {
  448. var result = parseFloat(value);
  449. return !isNaN(result) ? result : undefined;
  450. }
  451. return undefined;
  452. }
  453. function queryStringAttribute(node, attributeName) {
  454. if (!defined(node)) {
  455. return undefined;
  456. }
  457. var value = node.getAttribute(attributeName);
  458. return value !== null ? value : undefined;
  459. }
  460. function queryFirstNode(node, tagName, namespace) {
  461. if (!defined(node)) {
  462. return undefined;
  463. }
  464. var childNodes = node.childNodes;
  465. var length = childNodes.length;
  466. for (var q = 0; q < length; q++) {
  467. var child = childNodes[q];
  468. if (child.localName === tagName && namespace.indexOf(child.namespaceURI) !== -1) {
  469. return child;
  470. }
  471. }
  472. return undefined;
  473. }
  474. function queryNodes(node, tagName, namespace) {
  475. if (!defined(node)) {
  476. return undefined;
  477. }
  478. var result = [];
  479. var childNodes = node.getElementsByTagNameNS('*', tagName);
  480. var length = childNodes.length;
  481. for (var q = 0; q < length; q++) {
  482. var child = childNodes[q];
  483. if (child.localName === tagName && namespace.indexOf(child.namespaceURI) !== -1) {
  484. result.push(child);
  485. }
  486. }
  487. return result;
  488. }
  489. function queryChildNodes(node, tagName, namespace) {
  490. if (!defined(node)) {
  491. return [];
  492. }
  493. var result = [];
  494. var childNodes = node.childNodes;
  495. var length = childNodes.length;
  496. for (var q = 0; q < length; q++) {
  497. var child = childNodes[q];
  498. if (child.localName === tagName && namespace.indexOf(child.namespaceURI) !== -1) {
  499. result.push(child);
  500. }
  501. }
  502. return result;
  503. }
  504. function queryNumericValue(node, tagName, namespace) {
  505. var resultNode = queryFirstNode(node, tagName, namespace);
  506. if (defined(resultNode)) {
  507. var result = parseFloat(resultNode.textContent);
  508. return !isNaN(result) ? result : undefined;
  509. }
  510. return undefined;
  511. }
  512. function queryStringValue(node, tagName, namespace) {
  513. var result = queryFirstNode(node, tagName, namespace);
  514. if (defined(result)) {
  515. return result.textContent.trim();
  516. }
  517. return undefined;
  518. }
  519. function queryBooleanValue(node, tagName, namespace) {
  520. var result = queryFirstNode(node, tagName, namespace);
  521. if (defined(result)) {
  522. var value = result.textContent.trim();
  523. return value === '1' || /^true$/i.test(value);
  524. }
  525. return undefined;
  526. }
  527. function resolveHref(href, sourceResource, uriResolver) {
  528. if (!defined(href)) {
  529. return undefined;
  530. }
  531. var resource;
  532. if (defined(uriResolver)) {
  533. var blob = uriResolver[href];
  534. if (defined(blob)) {
  535. resource = new Resource({
  536. url: blob
  537. });
  538. } else {
  539. // Needed for multiple levels of KML files in a KMZ
  540. var baseUri = new Uri(sourceResource.getUrlComponent());
  541. var uri = new Uri(href);
  542. blob = uriResolver[uri.resolve(baseUri)];
  543. if (defined(blob)) {
  544. resource = new Resource({
  545. url: blob
  546. });
  547. }
  548. }
  549. }
  550. if (!defined(resource)) {
  551. resource = sourceResource.getDerivedResource({
  552. url: href
  553. });
  554. }
  555. return resource;
  556. }
  557. var colorOptions = {
  558. maximumRed : undefined,
  559. red : undefined,
  560. maximumGreen : undefined,
  561. green : undefined,
  562. maximumBlue : undefined,
  563. blue : undefined
  564. };
  565. function parseColorString(value, isRandom) {
  566. if (!defined(value) || /^\s*$/gm.test(value)) {
  567. return undefined;
  568. }
  569. if (value[0] === '#') {
  570. value = value.substring(1);
  571. }
  572. var alpha = parseInt(value.substring(0, 2), 16) / 255.0;
  573. var blue = parseInt(value.substring(2, 4), 16) / 255.0;
  574. var green = parseInt(value.substring(4, 6), 16) / 255.0;
  575. var red = parseInt(value.substring(6, 8), 16) / 255.0;
  576. if (!isRandom) {
  577. return new Color(red, green, blue, alpha);
  578. }
  579. if (red > 0) {
  580. colorOptions.maximumRed = red;
  581. colorOptions.red = undefined;
  582. } else {
  583. colorOptions.maximumRed = undefined;
  584. colorOptions.red = 0;
  585. }
  586. if (green > 0) {
  587. colorOptions.maximumGreen = green;
  588. colorOptions.green = undefined;
  589. } else {
  590. colorOptions.maximumGreen = undefined;
  591. colorOptions.green = 0;
  592. }
  593. if (blue > 0) {
  594. colorOptions.maximumBlue = blue;
  595. colorOptions.blue = undefined;
  596. } else {
  597. colorOptions.maximumBlue = undefined;
  598. colorOptions.blue = 0;
  599. }
  600. colorOptions.alpha = alpha;
  601. return Color.fromRandom(colorOptions);
  602. }
  603. function queryColorValue(node, tagName, namespace) {
  604. var value = queryStringValue(node, tagName, namespace);
  605. if (!defined(value)) {
  606. return undefined;
  607. }
  608. return parseColorString(value, queryStringValue(node, 'colorMode', namespace) === 'random');
  609. }
  610. function processTimeStamp(featureNode) {
  611. var node = queryFirstNode(featureNode, 'TimeStamp', namespaces.kmlgx);
  612. var whenString = queryStringValue(node, 'when', namespaces.kmlgx);
  613. if (!defined(node) || !defined(whenString) || whenString.length === 0) {
  614. return undefined;
  615. }
  616. //According to the KML spec, a TimeStamp represents a "single moment in time"
  617. //However, since Cesium animates much differently than Google Earth, that doesn't
  618. //Make much sense here. Instead, we use the TimeStamp as the moment the feature
  619. //comes into existence. This works much better and gives a similar feel to
  620. //GE's experience.
  621. var when = JulianDate.fromIso8601(whenString);
  622. var result = new TimeIntervalCollection();
  623. result.addInterval(new TimeInterval({
  624. start : when,
  625. stop : Iso8601.MAXIMUM_VALUE
  626. }));
  627. return result;
  628. }
  629. function processTimeSpan(featureNode) {
  630. var node = queryFirstNode(featureNode, 'TimeSpan', namespaces.kmlgx);
  631. if (!defined(node)) {
  632. return undefined;
  633. }
  634. var result;
  635. var beginNode = queryFirstNode(node, 'begin', namespaces.kmlgx);
  636. var beginDate = defined(beginNode) ? JulianDate.fromIso8601(beginNode.textContent) : undefined;
  637. var endNode = queryFirstNode(node, 'end', namespaces.kmlgx);
  638. var endDate = defined(endNode) ? JulianDate.fromIso8601(endNode.textContent) : undefined;
  639. if (defined(beginDate) && defined(endDate)) {
  640. if (JulianDate.lessThan(endDate, beginDate)) {
  641. var tmp = beginDate;
  642. beginDate = endDate;
  643. endDate = tmp;
  644. }
  645. result = new TimeIntervalCollection();
  646. result.addInterval(new TimeInterval({
  647. start : beginDate,
  648. stop : endDate
  649. }));
  650. } else if (defined(beginDate)) {
  651. result = new TimeIntervalCollection();
  652. result.addInterval(new TimeInterval({
  653. start : beginDate,
  654. stop : Iso8601.MAXIMUM_VALUE
  655. }));
  656. } else if (defined(endDate)) {
  657. result = new TimeIntervalCollection();
  658. result.addInterval(new TimeInterval({
  659. start : Iso8601.MINIMUM_VALUE,
  660. stop : endDate
  661. }));
  662. }
  663. return result;
  664. }
  665. function createDefaultBillboard() {
  666. var billboard = new BillboardGraphics();
  667. billboard.width = BILLBOARD_SIZE;
  668. billboard.height = BILLBOARD_SIZE;
  669. billboard.scaleByDistance = new NearFarScalar(BILLBOARD_NEAR_DISTANCE, BILLBOARD_NEAR_RATIO, BILLBOARD_FAR_DISTANCE, BILLBOARD_FAR_RATIO);
  670. billboard.pixelOffsetScaleByDistance = new NearFarScalar(BILLBOARD_NEAR_DISTANCE, BILLBOARD_NEAR_RATIO, BILLBOARD_FAR_DISTANCE, BILLBOARD_FAR_RATIO);
  671. return billboard;
  672. }
  673. function createDefaultPolygon() {
  674. var polygon = new PolygonGraphics();
  675. polygon.outline = true;
  676. polygon.outlineColor = Color.WHITE;
  677. return polygon;
  678. }
  679. function createDefaultLabel() {
  680. var label = new LabelGraphics();
  681. label.translucencyByDistance = new NearFarScalar(3000000, 1.0, 5000000, 0.0);
  682. label.pixelOffset = new Cartesian2(17, 0);
  683. label.horizontalOrigin = HorizontalOrigin.LEFT;
  684. label.font = '16px sans-serif';
  685. label.style = LabelStyle.FILL_AND_OUTLINE;
  686. return label;
  687. }
  688. function getIconHref(iconNode, dataSource, sourceResource, uriResolver, canRefresh) {
  689. var href = queryStringValue(iconNode, 'href', namespaces.kml);
  690. if (!defined(href) || (href.length === 0)) {
  691. return undefined;
  692. }
  693. if (href.indexOf('root://icons/palette-') === 0) {
  694. var palette = href.charAt(21);
  695. // Get the icon number
  696. var x = defaultValue(queryNumericValue(iconNode, 'x', namespaces.gx), 0);
  697. var y = defaultValue(queryNumericValue(iconNode, 'y', namespaces.gx), 0);
  698. x = Math.min(x / 32, 7);
  699. y = 7 - Math.min(y / 32, 7);
  700. var iconNum = (8 * y) + x;
  701. href = 'https://maps.google.com/mapfiles/kml/pal' + palette + '/icon' + iconNum + '.png';
  702. }
  703. var hrefResource = resolveHref(href, sourceResource, uriResolver);
  704. if (canRefresh) {
  705. var refreshMode = queryStringValue(iconNode, 'refreshMode', namespaces.kml);
  706. var viewRefreshMode = queryStringValue(iconNode, 'viewRefreshMode', namespaces.kml);
  707. if (refreshMode === 'onInterval' || refreshMode === 'onExpire') {
  708. oneTimeWarning('kml-refreshMode-' + refreshMode, 'KML - Unsupported Icon refreshMode: ' + refreshMode);
  709. } else if (viewRefreshMode === 'onStop' || viewRefreshMode === 'onRegion') {
  710. oneTimeWarning('kml-refreshMode-' + viewRefreshMode, 'KML - Unsupported Icon viewRefreshMode: ' + viewRefreshMode);
  711. }
  712. var viewBoundScale = defaultValue(queryStringValue(iconNode, 'viewBoundScale', namespaces.kml), 1.0);
  713. var defaultViewFormat = (viewRefreshMode === 'onStop') ? 'BBOX=[bboxWest],[bboxSouth],[bboxEast],[bboxNorth]' : '';
  714. var viewFormat = defaultValue(queryStringValue(iconNode, 'viewFormat', namespaces.kml), defaultViewFormat);
  715. var httpQuery = queryStringValue(iconNode, 'httpQuery', namespaces.kml);
  716. if (defined(viewFormat)) {
  717. hrefResource.setQueryParameters(queryToObject(cleanupString(viewFormat)));
  718. }
  719. if (defined(httpQuery)) {
  720. hrefResource.setQueryParameters(queryToObject(cleanupString(httpQuery)));
  721. }
  722. var ellipsoid = dataSource._ellipsoid;
  723. processNetworkLinkQueryString(hrefResource, dataSource._camera, dataSource._canvas, viewBoundScale, dataSource._lastCameraView.bbox, ellipsoid);
  724. return hrefResource;
  725. }
  726. return hrefResource;
  727. }
  728. function processBillboardIcon(dataSource, node, targetEntity, sourceResource, uriResolver) {
  729. var scale = queryNumericValue(node, 'scale', namespaces.kml);
  730. var heading = queryNumericValue(node, 'heading', namespaces.kml);
  731. var color = queryColorValue(node, 'color', namespaces.kml);
  732. var iconNode = queryFirstNode(node, 'Icon', namespaces.kml);
  733. var icon = getIconHref(iconNode, dataSource, sourceResource, uriResolver, false);
  734. // If icon tags are present but blank, we do not want to show an icon
  735. if (defined(iconNode) && !defined(icon)) {
  736. icon = false;
  737. }
  738. var x = queryNumericValue(iconNode, 'x', namespaces.gx);
  739. var y = queryNumericValue(iconNode, 'y', namespaces.gx);
  740. var w = queryNumericValue(iconNode, 'w', namespaces.gx);
  741. var h = queryNumericValue(iconNode, 'h', namespaces.gx);
  742. var hotSpotNode = queryFirstNode(node, 'hotSpot', namespaces.kml);
  743. var hotSpotX = queryNumericAttribute(hotSpotNode, 'x');
  744. var hotSpotY = queryNumericAttribute(hotSpotNode, 'y');
  745. var hotSpotXUnit = queryStringAttribute(hotSpotNode, 'xunits');
  746. var hotSpotYUnit = queryStringAttribute(hotSpotNode, 'yunits');
  747. var billboard = targetEntity.billboard;
  748. if (!defined(billboard)) {
  749. billboard = createDefaultBillboard();
  750. targetEntity.billboard = billboard;
  751. }
  752. billboard.image = icon;
  753. billboard.scale = scale;
  754. billboard.color = color;
  755. if (defined(x) || defined(y) || defined(w) || defined(h)) {
  756. billboard.imageSubRegion = new BoundingRectangle(x, y, w, h);
  757. }
  758. //GE treats a heading of zero as no heading
  759. //You can still point north using a 360 degree angle (or any multiple of 360)
  760. if (defined(heading) && heading !== 0) {
  761. billboard.rotation = CesiumMath.toRadians(-heading);
  762. billboard.alignedAxis = Cartesian3.UNIT_Z;
  763. }
  764. //Hotpot is the KML equivalent of pixel offset
  765. //The hotspot origin is the lower left, but we leave
  766. //our billboard origin at the center and simply
  767. //modify the pixel offset to take this into account
  768. scale = defaultValue(scale, 1.0);
  769. var xOffset;
  770. var yOffset;
  771. if (defined(hotSpotX)) {
  772. if (hotSpotXUnit === 'pixels') {
  773. xOffset = -hotSpotX * scale;
  774. } else if (hotSpotXUnit === 'insetPixels') {
  775. xOffset = (hotSpotX - BILLBOARD_SIZE) * scale;
  776. } else if (hotSpotXUnit === 'fraction') {
  777. xOffset = -hotSpotX * BILLBOARD_SIZE * scale;
  778. }
  779. xOffset += BILLBOARD_SIZE * 0.5 * scale;
  780. }
  781. if (defined(hotSpotY)) {
  782. if (hotSpotYUnit === 'pixels') {
  783. yOffset = hotSpotY * scale;
  784. } else if (hotSpotYUnit === 'insetPixels') {
  785. yOffset = (-hotSpotY + BILLBOARD_SIZE) * scale;
  786. } else if (hotSpotYUnit === 'fraction') {
  787. yOffset = hotSpotY * BILLBOARD_SIZE * scale;
  788. }
  789. yOffset -= BILLBOARD_SIZE * 0.5 * scale;
  790. }
  791. if (defined(xOffset) || defined(yOffset)) {
  792. billboard.pixelOffset = new Cartesian2(xOffset, yOffset);
  793. }
  794. }
  795. function applyStyle(dataSource, styleNode, targetEntity, sourceResource, uriResolver) {
  796. for (var i = 0, len = styleNode.childNodes.length; i < len; i++) {
  797. var node = styleNode.childNodes.item(i);
  798. if (node.localName === 'IconStyle') {
  799. processBillboardIcon(dataSource, node, targetEntity, sourceResource, uriResolver);
  800. } else if (node.localName === 'LabelStyle') {
  801. var label = targetEntity.label;
  802. if (!defined(label)) {
  803. label = createDefaultLabel();
  804. targetEntity.label = label;
  805. }
  806. label.scale = defaultValue(queryNumericValue(node, 'scale', namespaces.kml), label.scale);
  807. label.fillColor = defaultValue(queryColorValue(node, 'color', namespaces.kml), label.fillColor);
  808. label.text = targetEntity.name;
  809. } else if (node.localName === 'LineStyle') {
  810. var polyline = targetEntity.polyline;
  811. if (!defined(polyline)) {
  812. polyline = new PolylineGraphics();
  813. targetEntity.polyline = polyline;
  814. }
  815. polyline.width = queryNumericValue(node, 'width', namespaces.kml);
  816. polyline.material = queryColorValue(node, 'color', namespaces.kml);
  817. if (defined(queryColorValue(node, 'outerColor', namespaces.gx))) {
  818. oneTimeWarning('kml-gx:outerColor', 'KML - gx:outerColor is not supported in a LineStyle');
  819. }
  820. if (defined(queryNumericValue(node, 'outerWidth', namespaces.gx))) {
  821. oneTimeWarning('kml-gx:outerWidth', 'KML - gx:outerWidth is not supported in a LineStyle');
  822. }
  823. if (defined(queryNumericValue(node, 'physicalWidth', namespaces.gx))) {
  824. oneTimeWarning('kml-gx:physicalWidth', 'KML - gx:physicalWidth is not supported in a LineStyle');
  825. }
  826. if (defined(queryBooleanValue(node, 'labelVisibility', namespaces.gx))) {
  827. oneTimeWarning('kml-gx:labelVisibility', 'KML - gx:labelVisibility is not supported in a LineStyle');
  828. }
  829. } else if (node.localName === 'PolyStyle') {
  830. var polygon = targetEntity.polygon;
  831. if (!defined(polygon)) {
  832. polygon = createDefaultPolygon();
  833. targetEntity.polygon = polygon;
  834. }
  835. polygon.material = defaultValue(queryColorValue(node, 'color', namespaces.kml), polygon.material);
  836. polygon.fill = defaultValue(queryBooleanValue(node, 'fill', namespaces.kml), polygon.fill);
  837. polygon.outline = defaultValue(queryBooleanValue(node, 'outline', namespaces.kml), polygon.outline);
  838. } else if (node.localName === 'BalloonStyle') {
  839. var bgColor = defaultValue(parseColorString(queryStringValue(node, 'bgColor', namespaces.kml)), Color.WHITE);
  840. var textColor = defaultValue(parseColorString(queryStringValue(node, 'textColor', namespaces.kml)), Color.BLACK);
  841. var text = queryStringValue(node, 'text', namespaces.kml);
  842. //This is purely an internal property used in style processing,
  843. //it never ends up on the final entity.
  844. targetEntity.addProperty('balloonStyle');
  845. targetEntity.balloonStyle = {
  846. bgColor : bgColor,
  847. textColor : textColor,
  848. text : text
  849. };
  850. } else if (node.localName === 'ListStyle') {
  851. var listItemType = queryStringValue(node, 'listItemType', namespaces.kml);
  852. if (listItemType === 'radioFolder' || listItemType === 'checkOffOnly') {
  853. oneTimeWarning('kml-listStyle-' + listItemType, 'KML - Unsupported ListStyle with listItemType: ' + listItemType);
  854. }
  855. }
  856. }
  857. }
  858. //Processes and merges any inline styles for the provided node into the provided entity.
  859. function computeFinalStyle(dataSource, placeMark, styleCollection, sourceResource, uriResolver) {
  860. var result = new Entity();
  861. var styleEntity;
  862. //Google earth seems to always use the last inline Style/StyleMap only
  863. var styleIndex = -1;
  864. var childNodes = placeMark.childNodes;
  865. var length = childNodes.length;
  866. for (var q = 0; q < length; q++) {
  867. var child = childNodes[q];
  868. if (child.localName === 'Style' || child.localName === 'StyleMap') {
  869. styleIndex = q;
  870. }
  871. }
  872. if (styleIndex !== -1) {
  873. var inlineStyleNode = childNodes[styleIndex];
  874. if (inlineStyleNode.localName === 'Style') {
  875. applyStyle(dataSource, inlineStyleNode, result, sourceResource, uriResolver);
  876. } else { // StyleMap
  877. var pairs = queryChildNodes(inlineStyleNode, 'Pair', namespaces.kml);
  878. for (var p = 0; p < pairs.length; p++) {
  879. var pair = pairs[p];
  880. var key = queryStringValue(pair, 'key', namespaces.kml);
  881. if (key === 'normal') {
  882. var styleUrl = queryStringValue(pair, 'styleUrl', namespaces.kml);
  883. if (defined(styleUrl)) {
  884. styleEntity = styleCollection.getById(styleUrl);
  885. if (!defined(styleEntity)) {
  886. styleEntity = styleCollection.getById('#' + styleUrl);
  887. }
  888. if (defined(styleEntity)) {
  889. result.merge(styleEntity);
  890. }
  891. } else {
  892. var node = queryFirstNode(pair, 'Style', namespaces.kml);
  893. applyStyle(dataSource, node, result, sourceResource, uriResolver);
  894. }
  895. } else {
  896. oneTimeWarning('kml-styleMap-' + key, 'KML - Unsupported StyleMap key: ' + key);
  897. }
  898. }
  899. }
  900. }
  901. //Google earth seems to always use the first external style only.
  902. var externalStyle = queryStringValue(placeMark, 'styleUrl', namespaces.kml);
  903. if (defined(externalStyle)) {
  904. var id = externalStyle;
  905. if (externalStyle[0] !== '#' && externalStyle.indexOf('#') !== -1) {
  906. var tokens = externalStyle.split('#');
  907. var uri = tokens[0];
  908. var resource = sourceResource.getDerivedResource({
  909. url: uri
  910. });
  911. id = resource.getUrlComponent() + '#' + tokens[1];
  912. }
  913. styleEntity = styleCollection.getById(id);
  914. if (!defined(styleEntity)) {
  915. styleEntity = styleCollection.getById('#' + id);
  916. }
  917. if (defined(styleEntity)) {
  918. result.merge(styleEntity);
  919. }
  920. }
  921. return result;
  922. }
  923. //Asynchronously processes an external style file.
  924. function processExternalStyles(dataSource, resource, styleCollection) {
  925. return resource.fetchXML().then(function(styleKml) {
  926. return processStyles(dataSource, styleKml, styleCollection, resource, true);
  927. });
  928. }
  929. //Processes all shared and external styles and stores
  930. //their id into the provided styleCollection.
  931. //Returns an array of promises that will resolve when
  932. //each style is loaded.
  933. function processStyles(dataSource, kml, styleCollection, sourceResource, isExternal, uriResolver) {
  934. var i;
  935. var id;
  936. var styleEntity;
  937. var node;
  938. var styleNodes = queryNodes(kml, 'Style', namespaces.kml);
  939. if (defined(styleNodes)) {
  940. var styleNodesLength = styleNodes.length;
  941. for (i = 0; i < styleNodesLength; i++) {
  942. node = styleNodes[i];
  943. id = queryStringAttribute(node, 'id');
  944. if (defined(id)) {
  945. id = '#' + id;
  946. if (isExternal && defined(sourceResource)) {
  947. id = sourceResource.getUrlComponent() + id;
  948. }
  949. if (!defined(styleCollection.getById(id))) {
  950. styleEntity = new Entity({
  951. id : id
  952. });
  953. styleCollection.add(styleEntity);
  954. applyStyle(dataSource, node, styleEntity, sourceResource, uriResolver);
  955. }
  956. }
  957. }
  958. }
  959. var styleMaps = queryNodes(kml, 'StyleMap', namespaces.kml);
  960. if (defined(styleMaps)) {
  961. var styleMapsLength = styleMaps.length;
  962. for (i = 0; i < styleMapsLength; i++) {
  963. var styleMap = styleMaps[i];
  964. id = queryStringAttribute(styleMap, 'id');
  965. if (defined(id)) {
  966. var pairs = queryChildNodes(styleMap, 'Pair', namespaces.kml);
  967. for (var p = 0; p < pairs.length; p++) {
  968. var pair = pairs[p];
  969. var key = queryStringValue(pair, 'key', namespaces.kml);
  970. if (key === 'normal') {
  971. id = '#' + id;
  972. if (isExternal && defined(sourceResource)) {
  973. id = sourceResource.getUrlComponent() + id;
  974. }
  975. if (!defined(styleCollection.getById(id))) {
  976. styleEntity = styleCollection.getOrCreateEntity(id);
  977. var styleUrl = queryStringValue(pair, 'styleUrl', namespaces.kml);
  978. if (defined(styleUrl)) {
  979. if (styleUrl[0] !== '#') {
  980. styleUrl = '#' + styleUrl;
  981. }
  982. if (isExternal && defined(sourceResource)) {
  983. styleUrl = sourceResource.getUrlComponent() + styleUrl;
  984. }
  985. var base = styleCollection.getById(styleUrl);
  986. if (defined(base)) {
  987. styleEntity.merge(base);
  988. }
  989. } else {
  990. node = queryFirstNode(pair, 'Style', namespaces.kml);
  991. applyStyle(dataSource, node, styleEntity, sourceResource, uriResolver);
  992. }
  993. }
  994. } else {
  995. oneTimeWarning('kml-styleMap-' + key, 'KML - Unsupported StyleMap key: ' + key);
  996. }
  997. }
  998. }
  999. }
  1000. }
  1001. var promises = [];
  1002. var styleUrlNodes = kml.getElementsByTagName('styleUrl');
  1003. var styleUrlNodesLength = styleUrlNodes.length;
  1004. for (i = 0; i < styleUrlNodesLength; i++) {
  1005. var styleReference = styleUrlNodes[i].textContent;
  1006. if (styleReference[0] !== '#') {
  1007. //According to the spec, all local styles should start with a #
  1008. //and everything else is an external style that has a # seperating
  1009. //the URL of the document and the style. However, Google Earth
  1010. //also accepts styleUrls without a # as meaning a local style.
  1011. var tokens = styleReference.split('#');
  1012. if (tokens.length === 2) {
  1013. var uri = tokens[0];
  1014. var resource = sourceResource.getDerivedResource({
  1015. url: uri
  1016. });
  1017. promises.push(processExternalStyles(dataSource, resource, styleCollection));
  1018. }
  1019. }
  1020. }
  1021. return promises;
  1022. }
  1023. function createDropLine(entityCollection, entity, styleEntity) {
  1024. var entityPosition = new ReferenceProperty(entityCollection, entity.id, ['position']);
  1025. var surfacePosition = new ScaledPositionProperty(entity.position);
  1026. entity.polyline = defined(styleEntity.polyline) ? styleEntity.polyline.clone() : new PolylineGraphics();
  1027. entity.polyline.positions = new PositionPropertyArray([entityPosition, surfacePosition]);
  1028. }
  1029. function heightReferenceFromAltitudeMode(altitudeMode, gxAltitudeMode) {
  1030. if (!defined(altitudeMode) && !defined(gxAltitudeMode) || altitudeMode === 'clampToGround') {
  1031. return HeightReference.CLAMP_TO_GROUND;
  1032. }
  1033. if (altitudeMode === 'relativeToGround') {
  1034. return HeightReference.RELATIVE_TO_GROUND;
  1035. }
  1036. if (altitudeMode === 'absolute') {
  1037. return HeightReference.NONE;
  1038. }
  1039. if (gxAltitudeMode === 'clampToSeaFloor') {
  1040. oneTimeWarning('kml-gx:altitudeMode-clampToSeaFloor', 'KML - <gx:altitudeMode>:clampToSeaFloor is currently not supported, using <kml:altitudeMode>:clampToGround.');
  1041. return HeightReference.CLAMP_TO_GROUND;
  1042. }
  1043. if (gxAltitudeMode === 'relativeToSeaFloor') {
  1044. oneTimeWarning('kml-gx:altitudeMode-relativeToSeaFloor', 'KML - <gx:altitudeMode>:relativeToSeaFloor is currently not supported, using <kml:altitudeMode>:relativeToGround.');
  1045. return HeightReference.RELATIVE_TO_GROUND;
  1046. }
  1047. if (defined(altitudeMode)) {
  1048. oneTimeWarning('kml-altitudeMode-unknown', 'KML - Unknown <kml:altitudeMode>:' + altitudeMode + ', using <kml:altitudeMode>:CLAMP_TO_GROUND.');
  1049. } else {
  1050. oneTimeWarning('kml-gx:altitudeMode-unknown', 'KML - Unknown <gx:altitudeMode>:' + gxAltitudeMode + ', using <kml:altitudeMode>:CLAMP_TO_GROUND.');
  1051. }
  1052. // Clamp to ground is the default
  1053. return HeightReference.CLAMP_TO_GROUND;
  1054. }
  1055. function createPositionPropertyFromAltitudeMode(property, altitudeMode, gxAltitudeMode) {
  1056. if (gxAltitudeMode === 'relativeToSeaFloor' || altitudeMode === 'absolute' || altitudeMode === 'relativeToGround') {
  1057. //Just return the ellipsoid referenced property until we support MSL
  1058. return property;
  1059. }
  1060. if ((defined(altitudeMode) && altitudeMode !== 'clampToGround') || //
  1061. (defined(gxAltitudeMode) && gxAltitudeMode !== 'clampToSeaFloor')) {
  1062. oneTimeWarning('kml-altitudeMode-unknown', 'KML - Unknown altitudeMode: ' + defaultValue(altitudeMode, gxAltitudeMode));
  1063. }
  1064. // Clamp to ground is the default
  1065. return new ScaledPositionProperty(property);
  1066. }
  1067. function createPositionPropertyArrayFromAltitudeMode(properties, altitudeMode, gxAltitudeMode, ellipsoid) {
  1068. if (!defined(properties)) {
  1069. return undefined;
  1070. }
  1071. if (gxAltitudeMode === 'relativeToSeaFloor' || altitudeMode === 'absolute' || altitudeMode === 'relativeToGround') {
  1072. //Just return the ellipsoid referenced property until we support MSL
  1073. return properties;
  1074. }
  1075. if ((defined(altitudeMode) && altitudeMode !== 'clampToGround') || //
  1076. (defined(gxAltitudeMode) && gxAltitudeMode !== 'clampToSeaFloor')) {
  1077. oneTimeWarning('kml-altitudeMode-unknown', 'KML - Unknown altitudeMode: ' + defaultValue(altitudeMode, gxAltitudeMode));
  1078. }
  1079. // Clamp to ground is the default
  1080. var propertiesLength = properties.length;
  1081. for (var i = 0; i < propertiesLength; i++) {
  1082. var property = properties[i];
  1083. ellipsoid.scaleToGeodeticSurface(property, property);
  1084. }
  1085. return properties;
  1086. }
  1087. function processPositionGraphics(dataSource, entity, styleEntity, heightReference) {
  1088. var label = entity.label;
  1089. if (!defined(label)) {
  1090. label = defined(styleEntity.label) ? styleEntity.label.clone() : createDefaultLabel();
  1091. entity.label = label;
  1092. }
  1093. label.text = entity.name;
  1094. var billboard = entity.billboard;
  1095. if (!defined(billboard)) {
  1096. billboard = defined(styleEntity.billboard) ? styleEntity.billboard.clone() : createDefaultBillboard();
  1097. entity.billboard = billboard;
  1098. }
  1099. if (!defined(billboard.image)) {
  1100. billboard.image = dataSource._pinBuilder.fromColor(Color.YELLOW, 64);
  1101. // If there were empty <Icon> tags in the KML, then billboard.image was set to false above
  1102. // However, in this case, the false value would have been converted to a property afterwards
  1103. // Thus, we check if billboard.image is defined with value of false
  1104. } else if (!billboard.image.getValue()) {
  1105. billboard.image = undefined;
  1106. }
  1107. var scale = 1.0;
  1108. if (defined(billboard.scale)) {
  1109. scale = billboard.scale.getValue();
  1110. if (scale !== 0) {
  1111. label.pixelOffset = new Cartesian2((scale * 16) + 1, 0);
  1112. } else {
  1113. //Minor tweaks to better match Google Earth.
  1114. label.pixelOffset = undefined;
  1115. label.horizontalOrigin = undefined;
  1116. }
  1117. }
  1118. if (defined(heightReference) && dataSource._clampToGround) {
  1119. billboard.heightReference = heightReference;
  1120. label.heightReference = heightReference;
  1121. }
  1122. }
  1123. function processPathGraphics(entity, styleEntity) {
  1124. var path = entity.path;
  1125. if (!defined(path)) {
  1126. path = new PathGraphics();
  1127. path.leadTime = 0;
  1128. entity.path = path;
  1129. }
  1130. var polyline = styleEntity.polyline;
  1131. if (defined(polyline)) {
  1132. path.material = polyline.material;
  1133. path.width = polyline.width;
  1134. }
  1135. }
  1136. function processPoint(dataSource, entityCollection, geometryNode, entity, styleEntity) {
  1137. var coordinatesString = queryStringValue(geometryNode, 'coordinates', namespaces.kml);
  1138. var altitudeMode = queryStringValue(geometryNode, 'altitudeMode', namespaces.kml);
  1139. var gxAltitudeMode = queryStringValue(geometryNode, 'altitudeMode', namespaces.gx);
  1140. var extrude = queryBooleanValue(geometryNode, 'extrude', namespaces.kml);
  1141. var ellipsoid = dataSource._ellipsoid;
  1142. var position = readCoordinate(coordinatesString, ellipsoid);
  1143. entity.position = position;
  1144. processPositionGraphics(dataSource, entity, styleEntity, heightReferenceFromAltitudeMode(altitudeMode, gxAltitudeMode));
  1145. if (extrude && isExtrudable(altitudeMode, gxAltitudeMode)) {
  1146. createDropLine(entityCollection, entity, styleEntity);
  1147. }
  1148. return true;
  1149. }
  1150. function processLineStringOrLinearRing(dataSource, entityCollection, geometryNode, entity, styleEntity) {
  1151. var coordinatesNode = queryFirstNode(geometryNode, 'coordinates', namespaces.kml);
  1152. var altitudeMode = queryStringValue(geometryNode, 'altitudeMode', namespaces.kml);
  1153. var gxAltitudeMode = queryStringValue(geometryNode, 'altitudeMode', namespaces.gx);
  1154. var extrude = queryBooleanValue(geometryNode, 'extrude', namespaces.kml);
  1155. var tessellate = queryBooleanValue(geometryNode, 'tessellate', namespaces.kml);
  1156. var canExtrude = isExtrudable(altitudeMode, gxAltitudeMode);
  1157. var zIndex = queryNumericValue(geometryNode, 'drawOrder', namespaces.gx);
  1158. var ellipsoid = dataSource._ellipsoid;
  1159. var coordinates = readCoordinates(coordinatesNode, ellipsoid);
  1160. var polyline = styleEntity.polyline;
  1161. if (canExtrude && extrude) {
  1162. var wall = new WallGraphics();
  1163. entity.wall = wall;
  1164. wall.positions = coordinates;
  1165. var polygon = styleEntity.polygon;
  1166. if (defined(polygon)) {
  1167. wall.fill = polygon.fill;
  1168. wall.material = polygon.material;
  1169. }
  1170. //Always outline walls so they show up in 2D.
  1171. wall.outline = true;
  1172. if (defined(polyline)) {
  1173. wall.outlineColor = defined(polyline.material) ? polyline.material.color : Color.WHITE;
  1174. wall.outlineWidth = polyline.width;
  1175. } else if (defined(polygon)) {
  1176. wall.outlineColor = defined(polygon.material) ? polygon.material.color : Color.WHITE;
  1177. }
  1178. } else if (dataSource._clampToGround && !canExtrude && tessellate) {
  1179. var polylineGraphics = new PolylineGraphics();
  1180. polylineGraphics.clampToGround = true;
  1181. entity.polyline = polylineGraphics;
  1182. polylineGraphics.positions = coordinates;
  1183. if (defined(polyline)) {
  1184. polylineGraphics.material = defined(polyline.material) ? polyline.material.color.getValue(Iso8601.MINIMUM_VALUE) : Color.WHITE;
  1185. polylineGraphics.width = defaultValue(polyline.width, 1.0);
  1186. } else {
  1187. polylineGraphics.material = Color.WHITE;
  1188. polylineGraphics.width = 1.0;
  1189. }
  1190. polylineGraphics.zIndex = zIndex;
  1191. } else {
  1192. if (defined(zIndex)) {
  1193. oneTimeWarning('kml-gx:drawOrder', 'KML - gx:drawOrder is not supported in LineStrings when clampToGround is false');
  1194. }
  1195. polyline = defined(polyline) ? polyline.clone() : new PolylineGraphics();
  1196. entity.polyline = polyline;
  1197. polyline.positions = createPositionPropertyArrayFromAltitudeMode(coordinates, altitudeMode, gxAltitudeMode, ellipsoid);
  1198. if (!tessellate || canExtrude) {
  1199. polyline.arcType = ArcType.NONE;
  1200. }
  1201. }
  1202. return true;
  1203. }
  1204. function processPolygon(dataSource, entityCollection, geometryNode, entity, styleEntity) {
  1205. var outerBoundaryIsNode = queryFirstNode(geometryNode, 'outerBoundaryIs', namespaces.kml);
  1206. var linearRingNode = queryFirstNode(outerBoundaryIsNode, 'LinearRing', namespaces.kml);
  1207. var coordinatesNode = queryFirstNode(linearRingNode, 'coordinates', namespaces.kml);
  1208. var ellipsoid = dataSource._ellipsoid;
  1209. var coordinates = readCoordinates(coordinatesNode, ellipsoid);
  1210. var extrude = queryBooleanValue(geometryNode, 'extrude', namespaces.kml);
  1211. var altitudeMode = queryStringValue(geometryNode, 'altitudeMode', namespaces.kml);
  1212. var gxAltitudeMode = queryStringValue(geometryNode, 'altitudeMode', namespaces.gx);
  1213. var canExtrude = isExtrudable(altitudeMode, gxAltitudeMode);
  1214. var polygon = defined(styleEntity.polygon) ? styleEntity.polygon.clone() : createDefaultPolygon();
  1215. var polyline = styleEntity.polyline;
  1216. if (defined(polyline)) {
  1217. polygon.outlineColor = defined(polyline.material) ? polyline.material.color : Color.WHITE;
  1218. polygon.outlineWidth = polyline.width;
  1219. }
  1220. entity.polygon = polygon;
  1221. if (canExtrude) {
  1222. polygon.perPositionHeight = true;
  1223. polygon.extrudedHeight = extrude ? 0 : undefined;
  1224. } else if (!dataSource._clampToGround) {
  1225. polygon.height = 0;
  1226. }
  1227. if (defined(coordinates)) {
  1228. var hierarchy = new PolygonHierarchy(coordinates);
  1229. var innerBoundaryIsNodes = queryChildNodes(geometryNode, 'innerBoundaryIs', namespaces.kml);
  1230. for (var j = 0; j < innerBoundaryIsNodes.length; j++) {
  1231. linearRingNode = queryChildNodes(innerBoundaryIsNodes[j], 'LinearRing', namespaces.kml);
  1232. for (var k = 0; k < linearRingNode.length; k++) {
  1233. coordinatesNode = queryFirstNode(linearRingNode[k], 'coordinates', namespaces.kml);
  1234. coordinates = readCoordinates(coordinatesNode, ellipsoid);
  1235. if (defined(coordinates)) {
  1236. hierarchy.holes.push(new PolygonHierarchy(coordinates));
  1237. }
  1238. }
  1239. }
  1240. polygon.hierarchy = hierarchy;
  1241. }
  1242. return true;
  1243. }
  1244. function processTrack(dataSource, entityCollection, geometryNode, entity, styleEntity) {
  1245. var altitudeMode = queryStringValue(geometryNode, 'altitudeMode', namespaces.kml);
  1246. var gxAltitudeMode = queryStringValue(geometryNode, 'altitudeMode', namespaces.gx);
  1247. var coordNodes = queryChildNodes(geometryNode, 'coord', namespaces.gx);
  1248. var angleNodes = queryChildNodes(geometryNode, 'angles', namespaces.gx);
  1249. var timeNodes = queryChildNodes(geometryNode, 'when', namespaces.kml);
  1250. var extrude = queryBooleanValue(geometryNode, 'extrude', namespaces.kml);
  1251. var canExtrude = isExtrudable(altitudeMode, gxAltitudeMode);
  1252. var ellipsoid = dataSource._ellipsoid;
  1253. if (angleNodes.length > 0) {
  1254. oneTimeWarning('kml-gx:angles', 'KML - gx:angles are not supported in gx:Tracks');
  1255. }
  1256. var length = Math.min(coordNodes.length, timeNodes.length);
  1257. var coordinates = [];
  1258. var times = [];
  1259. for (var i = 0; i < length; i++) {
  1260. var position = readCoordinate(coordNodes[i].textContent, ellipsoid);
  1261. coordinates.push(position);
  1262. times.push(JulianDate.fromIso8601(timeNodes[i].textContent));
  1263. }
  1264. var property = new SampledPositionProperty();
  1265. property.addSamples(times, coordinates);
  1266. entity.position = property;
  1267. processPositionGraphics(dataSource, entity, styleEntity, heightReferenceFromAltitudeMode(altitudeMode, gxAltitudeMode));
  1268. processPathGraphics(entity, styleEntity);
  1269. entity.availability = new TimeIntervalCollection();
  1270. if (timeNodes.length > 0) {
  1271. entity.availability.addInterval(new TimeInterval({
  1272. start : times[0],
  1273. stop : times[times.length - 1]
  1274. }));
  1275. }
  1276. if (canExtrude && extrude) {
  1277. createDropLine(entityCollection, entity, styleEntity);
  1278. }
  1279. return true;
  1280. }
  1281. function addToMultiTrack(times, positions, composite, availability, dropShowProperty, extrude, altitudeMode, gxAltitudeMode, includeEndPoints) {
  1282. var start = times[0];
  1283. var stop = times[times.length - 1];
  1284. var data = new SampledPositionProperty();
  1285. data.addSamples(times, positions);
  1286. composite.intervals.addInterval(new TimeInterval({
  1287. start : start,
  1288. stop : stop,
  1289. isStartIncluded : includeEndPoints,
  1290. isStopIncluded : includeEndPoints,
  1291. data : createPositionPropertyFromAltitudeMode(data, altitudeMode, gxAltitudeMode)
  1292. }));
  1293. availability.addInterval(new TimeInterval({
  1294. start : start,
  1295. stop : stop,
  1296. isStartIncluded : includeEndPoints,
  1297. isStopIncluded : includeEndPoints
  1298. }));
  1299. dropShowProperty.intervals.addInterval(new TimeInterval({
  1300. start : start,
  1301. stop : stop,
  1302. isStartIncluded : includeEndPoints,
  1303. isStopIncluded : includeEndPoints,
  1304. data : extrude
  1305. }));
  1306. }
  1307. function processMultiTrack(dataSource, entityCollection, geometryNode, entity, styleEntity) {
  1308. // Multitrack options do not work in GE as detailed in the spec,
  1309. // rather than altitudeMode being at the MultiTrack level,
  1310. // GE just defers all settings to the underlying track.
  1311. var interpolate = queryBooleanValue(geometryNode, 'interpolate', namespaces.gx);
  1312. var trackNodes = queryChildNodes(geometryNode, 'Track', namespaces.gx);
  1313. var times;
  1314. var lastStop;
  1315. var lastStopPosition;
  1316. var needDropLine = false;
  1317. var dropShowProperty = new TimeIntervalCollectionProperty();
  1318. var availability = new TimeIntervalCollection();
  1319. var composite = new CompositePositionProperty();
  1320. var ellipsoid = dataSource._ellipsoid;
  1321. for (var i = 0, len = trackNodes.length; i < len; i++) {
  1322. var trackNode = trackNodes[i];
  1323. var timeNodes = queryChildNodes(trackNode, 'when', namespaces.kml);
  1324. var coordNodes = queryChildNodes(trackNode, 'coord', namespaces.gx);
  1325. var altitudeMode = queryStringValue(trackNode, 'altitudeMode', namespaces.kml);
  1326. var gxAltitudeMode = queryStringValue(trackNode, 'altitudeMode', namespaces.gx);
  1327. var canExtrude = isExtrudable(altitudeMode, gxAltitudeMode);
  1328. var extrude = queryBooleanValue(trackNode, 'extrude', namespaces.kml);
  1329. var length = Math.min(coordNodes.length, timeNodes.length);
  1330. var positions = [];
  1331. times = [];
  1332. for (var x = 0; x < length; x++) {
  1333. var position = readCoordinate(coordNodes[x].textContent, ellipsoid);
  1334. positions.push(position);
  1335. times.push(JulianDate.fromIso8601(timeNodes[x].textContent));
  1336. }
  1337. if (interpolate) {
  1338. //If we are interpolating, then we need to fill in the end of
  1339. //the last track and the beginning of this one with a sampled
  1340. //property. From testing in Google Earth, this property
  1341. //is never extruded and always absolute.
  1342. if (defined(lastStop)) {
  1343. addToMultiTrack([lastStop, times[0]], [lastStopPosition, positions[0]], composite, availability, dropShowProperty, false, 'absolute', undefined, false);
  1344. }
  1345. lastStop = times[length - 1];
  1346. lastStopPosition = positions[positions.length - 1];
  1347. }
  1348. addToMultiTrack(times, positions, composite, availability, dropShowProperty, canExtrude && extrude, altitudeMode, gxAltitudeMode, true);
  1349. needDropLine = needDropLine || (canExtrude && extrude);
  1350. }
  1351. entity.availability = availability;
  1352. entity.position = composite;
  1353. processPositionGraphics(dataSource, entity, styleEntity);
  1354. processPathGraphics(entity, styleEntity);
  1355. if (needDropLine) {
  1356. createDropLine(entityCollection, entity, styleEntity);
  1357. entity.polyline.show = dropShowProperty;
  1358. }
  1359. return true;
  1360. }
  1361. var geometryTypes = {
  1362. Point : processPoint,
  1363. LineString : processLineStringOrLinearRing,
  1364. LinearRing : processLineStringOrLinearRing,
  1365. Polygon : processPolygon,
  1366. Track : processTrack,
  1367. MultiTrack : processMultiTrack,
  1368. MultiGeometry : processMultiGeometry,
  1369. Model : processUnsupportedGeometry
  1370. };
  1371. function processMultiGeometry(dataSource, entityCollection, geometryNode, entity, styleEntity, context) {
  1372. var childNodes = geometryNode.childNodes;
  1373. var hasGeometry = false;
  1374. for (var i = 0, len = childNodes.length; i < len; i++) {
  1375. var childNode = childNodes.item(i);
  1376. var geometryProcessor = geometryTypes[childNode.localName];
  1377. if (defined(geometryProcessor)) {
  1378. var childEntity = createEntity(childNode, entityCollection, context);
  1379. childEntity.parent = entity;
  1380. childEntity.name = entity.name;
  1381. childEntity.availability = entity.availability;
  1382. childEntity.description = entity.description;
  1383. childEntity.kml = entity.kml;
  1384. if (geometryProcessor(dataSource, entityCollection, childNode, childEntity, styleEntity)) {
  1385. hasGeometry = true;
  1386. }
  1387. }
  1388. }
  1389. return hasGeometry;
  1390. }
  1391. function processUnsupportedGeometry(dataSource, entityCollection, geometryNode, entity, styleEntity) {
  1392. oneTimeWarning('kml-unsupportedGeometry', 'KML - Unsupported geometry: ' + geometryNode.localName);
  1393. return false;
  1394. }
  1395. function processExtendedData(node, entity) {
  1396. var extendedDataNode = queryFirstNode(node, 'ExtendedData', namespaces.kml);
  1397. if (!defined(extendedDataNode)) {
  1398. return undefined;
  1399. }
  1400. if (defined(queryFirstNode(extendedDataNode, 'SchemaData', namespaces.kml))) {
  1401. oneTimeWarning('kml-schemaData', 'KML - SchemaData is unsupported');
  1402. }
  1403. if (defined(queryStringAttribute(extendedDataNode, 'xmlns:prefix'))) {
  1404. oneTimeWarning('kml-extendedData', 'KML - ExtendedData with xmlns:prefix is unsupported');
  1405. }
  1406. var result = {};
  1407. var dataNodes = queryChildNodes(extendedDataNode, 'Data', namespaces.kml);
  1408. if (defined(dataNodes)) {
  1409. var length = dataNodes.length;
  1410. for (var i = 0; i < length; i++) {
  1411. var dataNode = dataNodes[i];
  1412. var name = queryStringAttribute(dataNode, 'name');
  1413. if (defined(name)) {
  1414. result[name] = {
  1415. displayName : queryStringValue(dataNode, 'displayName', namespaces.kml),
  1416. value : queryStringValue(dataNode, 'value', namespaces.kml)
  1417. };
  1418. }
  1419. }
  1420. }
  1421. entity.kml.extendedData = result;
  1422. }
  1423. var scratchDiv;
  1424. if (typeof document !== 'undefined') {
  1425. scratchDiv = document.createElement('div');
  1426. }
  1427. function processDescription(node, entity, styleEntity, uriResolver, sourceResource) {
  1428. var i;
  1429. var key;
  1430. var keys;
  1431. var kmlData = entity.kml;
  1432. var extendedData = kmlData.extendedData;
  1433. var description = queryStringValue(node, 'description', namespaces.kml);
  1434. var balloonStyle = defaultValue(entity.balloonStyle, styleEntity.balloonStyle);
  1435. var background = Color.WHITE;
  1436. var foreground = Color.BLACK;
  1437. var text = description;
  1438. if (defined(balloonStyle)) {
  1439. background = defaultValue(balloonStyle.bgColor, Color.WHITE);
  1440. foreground = defaultValue(balloonStyle.textColor, Color.BLACK);
  1441. text = defaultValue(balloonStyle.text, description);
  1442. }
  1443. var value;
  1444. if (defined(text)) {
  1445. text = text.replace('$[name]', defaultValue(entity.name, ''));
  1446. text = text.replace('$[description]', defaultValue(description, ''));
  1447. text = text.replace('$[address]', defaultValue(kmlData.address, ''));
  1448. text = text.replace('$[Snippet]', defaultValue(kmlData.snippet, ''));
  1449. text = text.replace('$[id]', entity.id);
  1450. //While not explicitly defined by the OGC spec, in Google Earth
  1451. //The appearance of geDirections adds the directions to/from links
  1452. //We simply replace this string with nothing.
  1453. text = text.replace('$[geDirections]', '');
  1454. if (defined(extendedData)) {
  1455. var matches = text.match(/\$\[.+?\]/g);
  1456. if (matches !== null) {
  1457. for (i = 0; i < matches.length; i++) {
  1458. var token = matches[i];
  1459. var propertyName = token.substr(2, token.length - 3);
  1460. var isDisplayName = /\/displayName$/.test(propertyName);
  1461. propertyName = propertyName.replace(/\/displayName$/, '');
  1462. value = extendedData[propertyName];
  1463. if (defined(value)) {
  1464. value = isDisplayName ? value.displayName : value.value;
  1465. }
  1466. if (defined(value)) {
  1467. text = text.replace(token, defaultValue(value, ''));
  1468. }
  1469. }
  1470. }
  1471. }
  1472. } else if (defined(extendedData)) {
  1473. //If no description exists, build a table out of the extended data
  1474. keys = Object.keys(extendedData);
  1475. if (keys.length > 0) {
  1476. text = '<table class="cesium-infoBox-defaultTable cesium-infoBox-defaultTable-lighter"><tbody>';
  1477. for (i = 0; i < keys.length; i++) {
  1478. key = keys[i];
  1479. value = extendedData[key];
  1480. text += '<tr><th>' + defaultValue(value.displayName, key) + '</th><td>' + defaultValue(value.value, '') + '</td></tr>';
  1481. }
  1482. text += '</tbody></table>';
  1483. }
  1484. }
  1485. if (!defined(text)) {
  1486. //No description
  1487. return;
  1488. }
  1489. //Turns non-explicit links into clickable links.
  1490. text = autolinker.link(text);
  1491. //Use a temporary div to manipulate the links
  1492. //so that they open in a new window.
  1493. scratchDiv.innerHTML = text;
  1494. var links = scratchDiv.querySelectorAll('a');
  1495. for (i = 0; i < links.length; i++) {
  1496. links[i].setAttribute('target', '_blank');
  1497. }
  1498. //Rewrite any KMZ embedded urls
  1499. if (defined(uriResolver) && uriResolver.keys.length > 1) {
  1500. embedDataUris(scratchDiv, 'a', 'href', uriResolver);
  1501. embedDataUris(scratchDiv, 'img', 'src', uriResolver);
  1502. }
  1503. //Make relative urls absolute using the sourceResource
  1504. applyBasePath(scratchDiv, 'a', 'href', sourceResource);
  1505. applyBasePath(scratchDiv, 'img', 'src', sourceResource);
  1506. var tmp = '<div class="cesium-infoBox-description-lighter" style="';
  1507. tmp += 'overflow:auto;';
  1508. tmp += 'word-wrap:break-word;';
  1509. tmp += 'background-color:' + background.toCssColorString() + ';';
  1510. tmp += 'color:' + foreground.toCssColorString() + ';';
  1511. tmp += '">';
  1512. tmp += scratchDiv.innerHTML + '</div>';
  1513. scratchDiv.innerHTML = '';
  1514. //Set the final HTML as the description.
  1515. entity.description = tmp;
  1516. }
  1517. function processFeature(dataSource, featureNode, processingData) {
  1518. var entityCollection = processingData.entityCollection;
  1519. var parent = processingData.parentEntity;
  1520. var sourceResource = processingData.sourceResource;
  1521. var uriResolver = processingData.uriResolver;
  1522. var entity = createEntity(featureNode, entityCollection, processingData.context);
  1523. var kmlData = entity.kml;
  1524. var styleEntity = computeFinalStyle(dataSource, featureNode, processingData.styleCollection, sourceResource, uriResolver);
  1525. var name = queryStringValue(featureNode, 'name', namespaces.kml);
  1526. entity.name = name;
  1527. entity.parent = parent;
  1528. var availability = processTimeSpan(featureNode);
  1529. if (!defined(availability)) {
  1530. availability = processTimeStamp(featureNode);
  1531. }
  1532. entity.availability = availability;
  1533. mergeAvailabilityWithParent(entity);
  1534. // Per KML spec "A Feature is visible only if it and all its ancestors are visible."
  1535. function ancestryIsVisible(parentEntity) {
  1536. if (!parentEntity) {
  1537. return true;
  1538. }
  1539. return parentEntity.show && ancestryIsVisible(parentEntity.parent);
  1540. }
  1541. var visibility = queryBooleanValue(featureNode, 'visibility', namespaces.kml);
  1542. entity.show = ancestryIsVisible(parent) && defaultValue(visibility, true);
  1543. //var open = queryBooleanValue(featureNode, 'open', namespaces.kml);
  1544. var authorNode = queryFirstNode(featureNode, 'author', namespaces.atom);
  1545. var author = kmlData.author;
  1546. author.name = queryStringValue(authorNode, 'name', namespaces.atom);
  1547. author.uri = queryStringValue(authorNode, 'uri', namespaces.atom);
  1548. author.email = queryStringValue(authorNode, 'email', namespaces.atom);
  1549. var linkNode = queryFirstNode(featureNode, 'link', namespaces.atom);
  1550. var link = kmlData.link;
  1551. link.href = queryStringAttribute(linkNode, 'href');
  1552. link.hreflang = queryStringAttribute(linkNode, 'hreflang');
  1553. link.rel = queryStringAttribute(linkNode, 'rel');
  1554. link.type = queryStringAttribute(linkNode, 'type');
  1555. link.title = queryStringAttribute(linkNode, 'title');
  1556. link.length = queryStringAttribute(linkNode, 'length');
  1557. kmlData.address = queryStringValue(featureNode, 'address', namespaces.kml);
  1558. kmlData.phoneNumber = queryStringValue(featureNode, 'phoneNumber', namespaces.kml);
  1559. kmlData.snippet = queryStringValue(featureNode, 'Snippet', namespaces.kml);
  1560. processExtendedData(featureNode, entity);
  1561. processDescription(featureNode, entity, styleEntity, uriResolver, sourceResource);
  1562. var ellipsoid = dataSource._ellipsoid;
  1563. processLookAt(featureNode, entity, ellipsoid);
  1564. processCamera(featureNode, entity, ellipsoid);
  1565. if (defined(queryFirstNode(featureNode, 'Region', namespaces.kml))) {
  1566. oneTimeWarning('kml-region', 'KML - Placemark Regions are unsupported');
  1567. }
  1568. return {
  1569. entity : entity,
  1570. styleEntity : styleEntity
  1571. };
  1572. }
  1573. function processDocument(dataSource, node, processingData, deferredLoading) {
  1574. deferredLoading.addNodes(node.childNodes, processingData);
  1575. deferredLoading.process();
  1576. }
  1577. function processFolder(dataSource, node, processingData, deferredLoading) {
  1578. var r = processFeature(dataSource, node, processingData);
  1579. var newProcessingData = clone(processingData);
  1580. newProcessingData.parentEntity = r.entity;
  1581. processDocument(dataSource, node, newProcessingData, deferredLoading);
  1582. }
  1583. function processPlacemark(dataSource, placemark, processingData, deferredLoading) {
  1584. var r = processFeature(dataSource, placemark, processingData);
  1585. var entity = r.entity;
  1586. var styleEntity = r.styleEntity;
  1587. var hasGeometry = false;
  1588. var childNodes = placemark.childNodes;
  1589. for (var i = 0, len = childNodes.length; i < len && !hasGeometry; i++) {
  1590. var childNode = childNodes.item(i);
  1591. var geometryProcessor = geometryTypes[childNode.localName];
  1592. if (defined(geometryProcessor)) {
  1593. // pass the placemark entity id as a context for case of defining multiple child entities together to handle case
  1594. // where some malformed kmls reuse the same id across placemarks, which works in GE, but is not technically to spec.
  1595. geometryProcessor(dataSource, processingData.entityCollection, childNode, entity, styleEntity, entity.id);
  1596. hasGeometry = true;
  1597. }
  1598. }
  1599. if (!hasGeometry) {
  1600. entity.merge(styleEntity);
  1601. processPositionGraphics(dataSource, entity, styleEntity);
  1602. }
  1603. }
  1604. var playlistNodeProcessors = {
  1605. FlyTo: processTourFlyTo,
  1606. Wait: processTourWait,
  1607. SoundCue: processTourUnsupportedNode,
  1608. AnimatedUpdate: processTourUnsupportedNode,
  1609. TourControl: processTourUnsupportedNode
  1610. };
  1611. function processTour(dataSource, node, processingData, deferredLoading) {
  1612. var name = queryStringValue(node, 'name', namespaces.kml);
  1613. var id = queryStringAttribute(node, 'id');
  1614. var tour = new KmlTour(name, id);
  1615. var playlistNode = queryFirstNode(node, 'Playlist', namespaces.gx);
  1616. if(playlistNode) {
  1617. var ellipsoid = dataSource._ellipsoid;
  1618. var childNodes = playlistNode.childNodes;
  1619. for(var i = 0; i < childNodes.length; i++) {
  1620. var entryNode = childNodes[i];
  1621. if (entryNode.localName) {
  1622. var playlistNodeProcessor = playlistNodeProcessors[entryNode.localName];
  1623. if (playlistNodeProcessor) {
  1624. playlistNodeProcessor(tour, entryNode, ellipsoid);
  1625. }
  1626. else {
  1627. console.log('Unknown KML Tour playlist entry type ' + entryNode.localName);
  1628. }
  1629. }
  1630. }
  1631. }
  1632. if (!defined(dataSource.kmlTours)) {
  1633. dataSource.kmlTours = [];
  1634. }
  1635. dataSource.kmlTours.push(tour);
  1636. }
  1637. function processTourUnsupportedNode(tour, entryNode) {
  1638. oneTimeWarning('KML Tour unsupported node ' + entryNode.localName);
  1639. }
  1640. function processTourWait(tour, entryNode) {
  1641. var duration = queryNumericValue(entryNode, 'duration', namespaces.gx);
  1642. tour.addPlaylistEntry(new KmlTourWait(duration));
  1643. }
  1644. function processTourFlyTo(tour, entryNode, ellipsoid) {
  1645. var duration = queryNumericValue(entryNode, 'duration', namespaces.gx);
  1646. var flyToMode = queryStringValue(entryNode, 'flyToMode', namespaces.gx);
  1647. var t = {kml: {}};
  1648. processLookAt(entryNode, t, ellipsoid);
  1649. processCamera(entryNode, t, ellipsoid);
  1650. var view = t.kml.lookAt || t.kml.camera;
  1651. var flyto = new KmlTourFlyTo(duration, flyToMode, view);
  1652. tour.addPlaylistEntry(flyto);
  1653. }
  1654. function processCamera(featureNode, entity, ellipsoid) {
  1655. var camera = queryFirstNode(featureNode, 'Camera', namespaces.kml);
  1656. if(defined(camera)) {
  1657. var lon = defaultValue(queryNumericValue(camera, 'longitude', namespaces.kml), 0.0);
  1658. var lat = defaultValue(queryNumericValue(camera, 'latitude', namespaces.kml), 0.0);
  1659. var altitude = defaultValue(queryNumericValue(camera, 'altitude', namespaces.kml), 0.0);
  1660. var heading = defaultValue(queryNumericValue(camera, 'heading', namespaces.kml), 0.0);
  1661. var tilt = defaultValue(queryNumericValue(camera, 'tilt', namespaces.kml), 0.0);
  1662. var roll = defaultValue(queryNumericValue(camera, 'roll', namespaces.kml), 0.0);
  1663. var position = Cartesian3.fromDegrees(lon, lat, altitude, ellipsoid);
  1664. var hpr = HeadingPitchRoll.fromDegrees(heading, tilt - 90.0, roll);
  1665. entity.kml.camera = new KmlCamera(position, hpr);
  1666. }
  1667. }
  1668. function processLookAt(featureNode, entity, ellipsoid) {
  1669. var lookAt = queryFirstNode(featureNode, 'LookAt', namespaces.kml);
  1670. if(defined(lookAt)) {
  1671. var lon = defaultValue(queryNumericValue(lookAt, 'longitude', namespaces.kml), 0.0);
  1672. var lat = defaultValue(queryNumericValue(lookAt, 'latitude', namespaces.kml), 0.0);
  1673. var altitude = defaultValue(queryNumericValue(lookAt, 'altitude', namespaces.kml), 0.0);
  1674. var heading = queryNumericValue(lookAt, 'heading', namespaces.kml);
  1675. var tilt = queryNumericValue(lookAt, 'tilt', namespaces.kml);
  1676. var range = defaultValue(queryNumericValue(lookAt, 'range', namespaces.kml), 0.0);
  1677. tilt = CesiumMath.toRadians(defaultValue(tilt, 0.0));
  1678. heading = CesiumMath.toRadians(defaultValue(heading, 0.0));
  1679. var hpr = new HeadingPitchRange(heading, tilt - CesiumMath.PI_OVER_TWO, range);
  1680. var viewPoint = Cartesian3.fromDegrees(lon, lat, altitude, ellipsoid);
  1681. entity.kml.lookAt = new KmlLookAt(viewPoint, hpr);
  1682. }
  1683. }
  1684. function processGroundOverlay(dataSource, groundOverlay, processingData, deferredLoading) {
  1685. var r = processFeature(dataSource, groundOverlay, processingData);
  1686. var entity = r.entity;
  1687. var geometry;
  1688. var isLatLonQuad = false;
  1689. var ellipsoid = dataSource._ellipsoid;
  1690. var positions = readCoordinates(queryFirstNode(groundOverlay, 'LatLonQuad', namespaces.gx), ellipsoid);
  1691. var zIndex = queryNumericValue(groundOverlay, 'drawOrder', namespaces.kml);
  1692. if (defined(positions)) {
  1693. geometry = createDefaultPolygon();
  1694. geometry.hierarchy = new PolygonHierarchy(positions);
  1695. geometry.zIndex = zIndex;
  1696. entity.polygon = geometry;
  1697. isLatLonQuad = true;
  1698. } else {
  1699. geometry = new RectangleGraphics();
  1700. geometry.zIndex = zIndex;
  1701. entity.rectangle = geometry;
  1702. var latLonBox = queryFirstNode(groundOverlay, 'LatLonBox', namespaces.kml);
  1703. if (defined(latLonBox)) {
  1704. var west = queryNumericValue(latLonBox, 'west', namespaces.kml);
  1705. var south = queryNumericValue(latLonBox, 'south', namespaces.kml);
  1706. var east = queryNumericValue(latLonBox, 'east', namespaces.kml);
  1707. var north = queryNumericValue(latLonBox, 'north', namespaces.kml);
  1708. if (defined(west)) {
  1709. west = CesiumMath.negativePiToPi(CesiumMath.toRadians(west));
  1710. }
  1711. if (defined(south)) {
  1712. south = CesiumMath.clampToLatitudeRange(CesiumMath.toRadians(south));
  1713. }
  1714. if (defined(east)) {
  1715. east = CesiumMath.negativePiToPi(CesiumMath.toRadians(east));
  1716. }
  1717. if (defined(north)) {
  1718. north = CesiumMath.clampToLatitudeRange(CesiumMath.toRadians(north));
  1719. }
  1720. geometry.coordinates = new Rectangle(west, south, east, north);
  1721. var rotation = queryNumericValue(latLonBox, 'rotation', namespaces.kml);
  1722. if (defined(rotation)) {
  1723. var rotationRadians = CesiumMath.toRadians(rotation);
  1724. geometry.rotation = rotationRadians;
  1725. geometry.stRotation = rotationRadians;
  1726. }
  1727. }
  1728. }
  1729. var iconNode = queryFirstNode(groundOverlay, 'Icon', namespaces.kml);
  1730. var href = getIconHref(iconNode, dataSource, processingData.sourceResource, processingData.uriResolver, true);
  1731. if (defined(href)) {
  1732. if (isLatLonQuad) {
  1733. oneTimeWarning('kml-gx:LatLonQuad', 'KML - gx:LatLonQuad Icon does not support texture projection.');
  1734. }
  1735. var x = queryNumericValue(iconNode, 'x', namespaces.gx);
  1736. var y = queryNumericValue(iconNode, 'y', namespaces.gx);
  1737. var w = queryNumericValue(iconNode, 'w', namespaces.gx);
  1738. var h = queryNumericValue(iconNode, 'h', namespaces.gx);
  1739. if (defined(x) || defined(y) || defined(w) || defined(h)) {
  1740. oneTimeWarning('kml-groundOverlay-xywh', 'KML - gx:x, gx:y, gx:w, gx:h aren\'t supported for GroundOverlays');
  1741. }
  1742. geometry.material = href;
  1743. geometry.material.color = queryColorValue(groundOverlay, 'color', namespaces.kml);
  1744. geometry.material.transparent = true;
  1745. } else {
  1746. geometry.material = queryColorValue(groundOverlay, 'color', namespaces.kml);
  1747. }
  1748. var altitudeMode = queryStringValue(groundOverlay, 'altitudeMode', namespaces.kml);
  1749. if (defined(altitudeMode)) {
  1750. if (altitudeMode === 'absolute') {
  1751. //Use height above ellipsoid until we support MSL.
  1752. geometry.height = queryNumericValue(groundOverlay, 'altitude', namespaces.kml);
  1753. geometry.zIndex = undefined;
  1754. } else if (altitudeMode !== 'clampToGround') {
  1755. oneTimeWarning('kml-altitudeMode-unknown', 'KML - Unknown altitudeMode: ' + altitudeMode);
  1756. }
  1757. // else just use the default of 0 until we support 'clampToGround'
  1758. } else {
  1759. altitudeMode = queryStringValue(groundOverlay, 'altitudeMode', namespaces.gx);
  1760. if (altitudeMode === 'relativeToSeaFloor') {
  1761. oneTimeWarning('kml-altitudeMode-relativeToSeaFloor', 'KML - altitudeMode relativeToSeaFloor is currently not supported, treating as absolute.');
  1762. geometry.height = queryNumericValue(groundOverlay, 'altitude', namespaces.kml);
  1763. geometry.zIndex = undefined;
  1764. } else if (altitudeMode === 'clampToSeaFloor') {
  1765. oneTimeWarning('kml-altitudeMode-clampToSeaFloor', 'KML - altitudeMode clampToSeaFloor is currently not supported, treating as clampToGround.');
  1766. } else if (defined(altitudeMode)) {
  1767. oneTimeWarning('kml-altitudeMode-unknown', 'KML - Unknown altitudeMode: ' + altitudeMode);
  1768. }
  1769. }
  1770. }
  1771. function processUnsupportedFeature(dataSource, node, processingData, deferredLoading) {
  1772. dataSource._unsupportedNode.raiseEvent(dataSource, processingData.parentEntity, node, processingData.entityCollection,
  1773. processingData.styleCollection, processingData.sourceResource, processingData.uriResolver);
  1774. oneTimeWarning('kml-unsupportedFeature-' + node.nodeName, 'KML - Unsupported feature: ' + node.nodeName);
  1775. }
  1776. var RefreshMode = {
  1777. INTERVAL : 0,
  1778. EXPIRE : 1,
  1779. STOP : 2
  1780. };
  1781. function cleanupString(s) {
  1782. if (!defined(s) || s.length === 0) {
  1783. return '';
  1784. }
  1785. var sFirst = s[0];
  1786. if (sFirst === '&' || sFirst === '?') {
  1787. s = s.substring(1);
  1788. }
  1789. return s;
  1790. }
  1791. var zeroRectangle = new Rectangle();
  1792. var scratchCartographic = new Cartographic();
  1793. var scratchCartesian2 = new Cartesian2();
  1794. var scratchCartesian3 = new Cartesian3();
  1795. function processNetworkLinkQueryString(resource, camera, canvas, viewBoundScale, bbox, ellipsoid) {
  1796. function fixLatitude(value) {
  1797. if (value < -CesiumMath.PI_OVER_TWO) {
  1798. return -CesiumMath.PI_OVER_TWO;
  1799. } else if (value > CesiumMath.PI_OVER_TWO) {
  1800. return CesiumMath.PI_OVER_TWO;
  1801. }
  1802. return value;
  1803. }
  1804. function fixLongitude(value) {
  1805. if (value > CesiumMath.PI) {
  1806. return value - CesiumMath.TWO_PI;
  1807. } else if (value < -CesiumMath.PI) {
  1808. return value + CesiumMath.TWO_PI;
  1809. }
  1810. return value;
  1811. }
  1812. var queryString = objectToQuery(resource.queryParameters);
  1813. // objectToQuery escapes [ and ], so fix that
  1814. queryString = queryString.replace(/%5B/g, '[').replace(/%5D/g, ']');
  1815. if (defined(camera) && camera._mode !== SceneMode.MORPHING) {
  1816. var centerCartesian;
  1817. var centerCartographic;
  1818. bbox = defaultValue(bbox, zeroRectangle);
  1819. if (defined(canvas)) {
  1820. scratchCartesian2.x = canvas.clientWidth * 0.5;
  1821. scratchCartesian2.y = canvas.clientHeight * 0.5;
  1822. centerCartesian = camera.pickEllipsoid(scratchCartesian2, ellipsoid, scratchCartesian3);
  1823. }
  1824. if (defined(centerCartesian)) {
  1825. centerCartographic = ellipsoid.cartesianToCartographic(centerCartesian, scratchCartographic);
  1826. } else {
  1827. centerCartographic = Rectangle.center(bbox, scratchCartographic);
  1828. centerCartesian = ellipsoid.cartographicToCartesian(centerCartographic);
  1829. }
  1830. if (defined(viewBoundScale) && !CesiumMath.equalsEpsilon(viewBoundScale, 1.0, CesiumMath.EPSILON9)) {
  1831. var newHalfWidth = bbox.width * viewBoundScale * 0.5;
  1832. var newHalfHeight = bbox.height * viewBoundScale * 0.5;
  1833. bbox = new Rectangle(fixLongitude(centerCartographic.longitude - newHalfWidth),
  1834. fixLatitude(centerCartographic.latitude - newHalfHeight),
  1835. fixLongitude(centerCartographic.longitude + newHalfWidth),
  1836. fixLatitude(centerCartographic.latitude + newHalfHeight)
  1837. );
  1838. }
  1839. queryString = queryString.replace('[bboxWest]', CesiumMath.toDegrees(bbox.west).toString());
  1840. queryString = queryString.replace('[bboxSouth]', CesiumMath.toDegrees(bbox.south).toString());
  1841. queryString = queryString.replace('[bboxEast]', CesiumMath.toDegrees(bbox.east).toString());
  1842. queryString = queryString.replace('[bboxNorth]', CesiumMath.toDegrees(bbox.north).toString());
  1843. var lon = CesiumMath.toDegrees(centerCartographic.longitude).toString();
  1844. var lat = CesiumMath.toDegrees(centerCartographic.latitude).toString();
  1845. queryString = queryString.replace('[lookatLon]', lon);
  1846. queryString = queryString.replace('[lookatLat]', lat);
  1847. queryString = queryString.replace('[lookatTilt]', CesiumMath.toDegrees(camera.pitch).toString());
  1848. queryString = queryString.replace('[lookatHeading]', CesiumMath.toDegrees(camera.heading).toString());
  1849. queryString = queryString.replace('[lookatRange]', Cartesian3.distance(camera.positionWC, centerCartesian));
  1850. queryString = queryString.replace('[lookatTerrainLon]', lon);
  1851. queryString = queryString.replace('[lookatTerrainLat]', lat);
  1852. queryString = queryString.replace('[lookatTerrainAlt]', centerCartographic.height.toString());
  1853. ellipsoid.cartesianToCartographic(camera.positionWC, scratchCartographic);
  1854. queryString = queryString.replace('[cameraLon]', CesiumMath.toDegrees(scratchCartographic.longitude).toString());
  1855. queryString = queryString.replace('[cameraLat]', CesiumMath.toDegrees(scratchCartographic.latitude).toString());
  1856. queryString = queryString.replace('[cameraAlt]', CesiumMath.toDegrees(scratchCartographic.height).toString());
  1857. var frustum = camera.frustum;
  1858. var aspectRatio = frustum.aspectRatio;
  1859. var horizFov = '';
  1860. var vertFov = '';
  1861. if (defined(aspectRatio)) {
  1862. var fov = CesiumMath.toDegrees(frustum.fov);
  1863. if (aspectRatio > 1.0) {
  1864. horizFov = fov;
  1865. vertFov = fov / aspectRatio;
  1866. } else {
  1867. vertFov = fov;
  1868. horizFov = fov * aspectRatio;
  1869. }
  1870. }
  1871. queryString = queryString.replace('[horizFov]', horizFov.toString());
  1872. queryString = queryString.replace('[vertFov]', vertFov.toString());
  1873. } else {
  1874. queryString = queryString.replace('[bboxWest]', '-180');
  1875. queryString = queryString.replace('[bboxSouth]', '-90');
  1876. queryString = queryString.replace('[bboxEast]', '180');
  1877. queryString = queryString.replace('[bboxNorth]', '90');
  1878. queryString = queryString.replace('[lookatLon]', '');
  1879. queryString = queryString.replace('[lookatLat]', '');
  1880. queryString = queryString.replace('[lookatRange]', '');
  1881. queryString = queryString.replace('[lookatTilt]', '');
  1882. queryString = queryString.replace('[lookatHeading]', '');
  1883. queryString = queryString.replace('[lookatTerrainLon]', '');
  1884. queryString = queryString.replace('[lookatTerrainLat]', '');
  1885. queryString = queryString.replace('[lookatTerrainAlt]', '');
  1886. queryString = queryString.replace('[cameraLon]', '');
  1887. queryString = queryString.replace('[cameraLat]', '');
  1888. queryString = queryString.replace('[cameraAlt]', '');
  1889. queryString = queryString.replace('[horizFov]', '');
  1890. queryString = queryString.replace('[vertFov]', '');
  1891. }
  1892. if (defined(canvas)) {
  1893. queryString = queryString.replace('[horizPixels]', canvas.clientWidth);
  1894. queryString = queryString.replace('[vertPixels]', canvas.clientHeight);
  1895. } else {
  1896. queryString = queryString.replace('[horizPixels]', '');
  1897. queryString = queryString.replace('[vertPixels]', '');
  1898. }
  1899. queryString = queryString.replace('[terrainEnabled]', '1');
  1900. queryString = queryString.replace('[clientVersion]', '1');
  1901. queryString = queryString.replace('[kmlVersion]', '2.2');
  1902. queryString = queryString.replace('[clientName]', 'Cesium');
  1903. queryString = queryString.replace('[language]', 'English');
  1904. resource.setQueryParameters(queryToObject(queryString));
  1905. }
  1906. function processNetworkLink(dataSource, node, processingData, deferredLoading) {
  1907. var r = processFeature(dataSource, node, processingData);
  1908. var networkEntity = r.entity;
  1909. var sourceResource = processingData.sourceResource;
  1910. var uriResolver = processingData.uriResolver;
  1911. var link = queryFirstNode(node, 'Link', namespaces.kml);
  1912. if (!defined(link)) {
  1913. link = queryFirstNode(node, 'Url', namespaces.kml);
  1914. }
  1915. if (defined(link)) {
  1916. var href = queryStringValue(link, 'href', namespaces.kml);
  1917. var viewRefreshMode;
  1918. var viewBoundScale;
  1919. if (defined(href)) {
  1920. var newSourceUri = href;
  1921. href = resolveHref(href, sourceResource, processingData.uriResolver);
  1922. // We need to pass in the original path if resolveHref returns a data uri because the network link
  1923. // references a document in a KMZ archive
  1924. if (/^data:/.test(href.getUrlComponent())) {
  1925. // So if sourceUri isn't the kmz file, then its another kml in the archive, so resolve it
  1926. if (!/\.kmz/i.test(sourceResource.getUrlComponent())) {
  1927. newSourceUri = sourceResource.getDerivedResource({
  1928. url: newSourceUri
  1929. });
  1930. }
  1931. } else {
  1932. newSourceUri = href.clone(); // Not a data uri so use the fully qualified uri
  1933. viewRefreshMode = queryStringValue(link, 'viewRefreshMode', namespaces.kml);
  1934. viewBoundScale = defaultValue(queryStringValue(link, 'viewBoundScale', namespaces.kml), 1.0);
  1935. var defaultViewFormat = (viewRefreshMode === 'onStop') ? 'BBOX=[bboxWest],[bboxSouth],[bboxEast],[bboxNorth]' : '';
  1936. var viewFormat = defaultValue(queryStringValue(link, 'viewFormat', namespaces.kml), defaultViewFormat);
  1937. var httpQuery = queryStringValue(link, 'httpQuery', namespaces.kml);
  1938. if (defined(viewFormat)) {
  1939. href.setQueryParameters(queryToObject(cleanupString(viewFormat)));
  1940. }
  1941. if (defined(httpQuery)) {
  1942. href.setQueryParameters(queryToObject(cleanupString(httpQuery)));
  1943. }
  1944. var ellipsoid = dataSource._ellipsoid;
  1945. processNetworkLinkQueryString(href, dataSource._camera, dataSource._canvas, viewBoundScale, dataSource._lastCameraView.bbox, ellipsoid);
  1946. }
  1947. var options = {
  1948. sourceUri : newSourceUri,
  1949. uriResolver : uriResolver,
  1950. context : networkEntity.id
  1951. };
  1952. var networkLinkCollection = new EntityCollection();
  1953. var promise = load(dataSource, networkLinkCollection, href, options).then(function(rootElement) {
  1954. var entities = dataSource._entityCollection;
  1955. var newEntities = networkLinkCollection.values;
  1956. entities.suspendEvents();
  1957. for (var i = 0; i < newEntities.length; i++) {
  1958. var newEntity = newEntities[i];
  1959. if (!defined(newEntity.parent)) {
  1960. newEntity.parent = networkEntity;
  1961. mergeAvailabilityWithParent(newEntity);
  1962. }
  1963. entities.add(newEntity);
  1964. }
  1965. entities.resumeEvents();
  1966. // Add network links to a list if we need they will need to be updated
  1967. var refreshMode = queryStringValue(link, 'refreshMode', namespaces.kml);
  1968. var refreshInterval = defaultValue(queryNumericValue(link, 'refreshInterval', namespaces.kml), 0);
  1969. if ((refreshMode === 'onInterval' && refreshInterval > 0 ) || (refreshMode === 'onExpire') || (viewRefreshMode === 'onStop')) {
  1970. var networkLinkControl = queryFirstNode(rootElement, 'NetworkLinkControl', namespaces.kml);
  1971. var hasNetworkLinkControl = defined(networkLinkControl);
  1972. var now = JulianDate.now();
  1973. var networkLinkInfo = {
  1974. id : createGuid(),
  1975. href : href,
  1976. cookie : {},
  1977. lastUpdated : now,
  1978. updating : false,
  1979. entity : networkEntity,
  1980. viewBoundScale : viewBoundScale,
  1981. needsUpdate : false,
  1982. cameraUpdateTime : now
  1983. };
  1984. var minRefreshPeriod = 0;
  1985. if (hasNetworkLinkControl) {
  1986. networkLinkInfo.cookie = queryToObject(defaultValue(queryStringValue(networkLinkControl, 'cookie', namespaces.kml), ''));
  1987. minRefreshPeriod = defaultValue(queryNumericValue(networkLinkControl, 'minRefreshPeriod', namespaces.kml), 0);
  1988. }
  1989. if (refreshMode === 'onInterval') {
  1990. if (hasNetworkLinkControl) {
  1991. refreshInterval = Math.max(minRefreshPeriod, refreshInterval);
  1992. }
  1993. networkLinkInfo.refreshMode = RefreshMode.INTERVAL;
  1994. networkLinkInfo.time = refreshInterval;
  1995. } else if (refreshMode === 'onExpire') {
  1996. var expires;
  1997. if (hasNetworkLinkControl) {
  1998. expires = queryStringValue(networkLinkControl, 'expires', namespaces.kml);
  1999. }
  2000. if (defined(expires)) {
  2001. try {
  2002. var date = JulianDate.fromIso8601(expires);
  2003. var diff = JulianDate.secondsDifference(date, now);
  2004. if (diff > 0 && diff < minRefreshPeriod) {
  2005. JulianDate.addSeconds(now, minRefreshPeriod, date);
  2006. }
  2007. networkLinkInfo.refreshMode = RefreshMode.EXPIRE;
  2008. networkLinkInfo.time = date;
  2009. } catch (e) {
  2010. oneTimeWarning('kml-refreshMode-onInterval-onExpire', 'KML - NetworkLinkControl expires is not a valid date');
  2011. }
  2012. } else {
  2013. oneTimeWarning('kml-refreshMode-onExpire', 'KML - refreshMode of onExpire requires the NetworkLinkControl to have an expires element');
  2014. }
  2015. } else if (dataSource._camera) { // Only allow onStop refreshes if we have a camera
  2016. networkLinkInfo.refreshMode = RefreshMode.STOP;
  2017. networkLinkInfo.time = defaultValue(queryNumericValue(link, 'viewRefreshTime', namespaces.kml), 0);
  2018. } else {
  2019. oneTimeWarning('kml-refrehMode-onStop-noCamera', 'A NetworkLink with viewRefreshMode=onStop requires a camera be passed in when creating the KmlDataSource');
  2020. }
  2021. if (defined(networkLinkInfo.refreshMode)) {
  2022. dataSource._networkLinks.set(networkLinkInfo.id, networkLinkInfo);
  2023. }
  2024. } else if (viewRefreshMode === 'onRegion') {
  2025. oneTimeWarning('kml-refrehMode-onRegion', 'KML - Unsupported viewRefreshMode: onRegion');
  2026. }
  2027. }).otherwise(function(error) {
  2028. oneTimeWarning('An error occured during loading ' + href.url);
  2029. dataSource._error.raiseEvent(dataSource, error);
  2030. });
  2031. deferredLoading.addPromise(promise);
  2032. }
  2033. }
  2034. }
  2035. function processFeatureNode(dataSource, node, processingData, deferredLoading) {
  2036. var featureProcessor = featureTypes[node.localName];
  2037. if (defined(featureProcessor)) {
  2038. return featureProcessor(dataSource, node, processingData, deferredLoading);
  2039. }
  2040. return processUnsupportedFeature(dataSource, node, processingData, deferredLoading);
  2041. }
  2042. function loadKml(dataSource, entityCollection, kml, sourceResource, uriResolver, context) {
  2043. entityCollection.removeAll();
  2044. var documentElement = kml.documentElement;
  2045. var document = documentElement.localName === 'Document' ? documentElement : queryFirstNode(documentElement, 'Document', namespaces.kml);
  2046. var name = queryStringValue(document, 'name', namespaces.kml);
  2047. if (!defined(name)) {
  2048. name = getFilenameFromUri(sourceResource.getUrlComponent());
  2049. }
  2050. // Only set the name from the root document
  2051. if (!defined(dataSource._name)) {
  2052. dataSource._name = name;
  2053. }
  2054. var deferredLoading = new KmlDataSource._DeferredLoading(dataSource);
  2055. var styleCollection = new EntityCollection(dataSource);
  2056. return when.all(processStyles(dataSource, kml, styleCollection, sourceResource, false, uriResolver)).then(function() {
  2057. var element = kml.documentElement;
  2058. if (element.localName === 'kml') {
  2059. var childNodes = element.childNodes;
  2060. for (var i = 0; i < childNodes.length; i++) {
  2061. var tmp = childNodes[i];
  2062. if (defined(featureTypes[tmp.localName])) {
  2063. element = tmp;
  2064. break;
  2065. }
  2066. }
  2067. }
  2068. var processingData = {
  2069. parentEntity: undefined,
  2070. entityCollection: entityCollection,
  2071. styleCollection: styleCollection,
  2072. sourceResource: sourceResource,
  2073. uriResolver: uriResolver,
  2074. context: context
  2075. };
  2076. entityCollection.suspendEvents();
  2077. processFeatureNode(dataSource, element, processingData, deferredLoading);
  2078. entityCollection.resumeEvents();
  2079. return deferredLoading.wait()
  2080. .then(function() {
  2081. return kml.documentElement;
  2082. });
  2083. });
  2084. }
  2085. function loadKmz(dataSource, entityCollection, blob, sourceResource) {
  2086. var deferred = when.defer();
  2087. zip.createReader(new zip.BlobReader(blob), function(reader) {
  2088. reader.getEntries(function(entries) {
  2089. var promises = [];
  2090. var uriResolver = {};
  2091. var docEntry;
  2092. var docDefer;
  2093. for (var i = 0; i < entries.length; i++) {
  2094. var entry = entries[i];
  2095. if (!entry.directory) {
  2096. var innerDefer = when.defer();
  2097. promises.push(innerDefer.promise);
  2098. if (/\.kml$/i.test(entry.filename)) {
  2099. // We use the first KML document we come across
  2100. // https://developers.google.com/kml/documentation/kmzarchives
  2101. // Unless we come across a .kml file at the root of the archive because GE does this
  2102. if (!defined(docEntry) || !/\//i.test(entry.filename)) {
  2103. if (defined(docEntry)) {
  2104. // We found one at the root so load the initial kml as a data uri
  2105. loadDataUriFromZip(docEntry, uriResolver, docDefer);
  2106. }
  2107. docEntry = entry;
  2108. docDefer = innerDefer;
  2109. } else {
  2110. // Wasn't the first kml and wasn't at the root
  2111. loadDataUriFromZip(entry, uriResolver, innerDefer);
  2112. }
  2113. } else {
  2114. loadDataUriFromZip(entry, uriResolver, innerDefer);
  2115. }
  2116. }
  2117. }
  2118. // Now load the root KML document
  2119. if (defined(docEntry)) {
  2120. loadXmlFromZip(docEntry, uriResolver, docDefer);
  2121. }
  2122. when.all(promises).then(function() {
  2123. reader.close();
  2124. if (!defined(uriResolver.kml)) {
  2125. deferred.reject(new RuntimeError('KMZ file does not contain a KML document.'));
  2126. return;
  2127. }
  2128. uriResolver.keys = Object.keys(uriResolver);
  2129. return loadKml(dataSource, entityCollection, uriResolver.kml, sourceResource, uriResolver);
  2130. }).then(deferred.resolve).otherwise(deferred.reject);
  2131. });
  2132. }, function(e) {
  2133. deferred.reject(e);
  2134. });
  2135. return deferred.promise;
  2136. }
  2137. function load(dataSource, entityCollection, data, options) {
  2138. options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  2139. var sourceUri = options.sourceUri;
  2140. var uriResolver = options.uriResolver;
  2141. var context = options.context;
  2142. var promise = data;
  2143. if (typeof data === 'string' || (data instanceof Resource)) {
  2144. data = Resource.createIfNeeded(data);
  2145. promise = data.fetchBlob();
  2146. sourceUri = defaultValue(sourceUri, data.clone());
  2147. // Add resource credits to our list of credits to display
  2148. var resourceCredits = dataSource._resourceCredits;
  2149. var credits = data.credits;
  2150. if (defined(credits)) {
  2151. var length = credits.length;
  2152. for (var i = 0; i < length; i++) {
  2153. resourceCredits.push(credits[i]);
  2154. }
  2155. }
  2156. } else {
  2157. sourceUri = defaultValue(sourceUri, Resource.DEFAULT.clone());
  2158. }
  2159. sourceUri = Resource.createIfNeeded(sourceUri);
  2160. return when(promise)
  2161. .then(function(dataToLoad) {
  2162. if (dataToLoad instanceof Blob) {
  2163. return isZipFile(dataToLoad).then(function(isZip) {
  2164. if (isZip) {
  2165. return loadKmz(dataSource, entityCollection, dataToLoad, sourceUri);
  2166. }
  2167. return readBlobAsText(dataToLoad).then(function(text) {
  2168. //There's no official way to validate if a parse was successful.
  2169. //The following check detects the error on various browsers.
  2170. //Insert missing namespaces
  2171. text = insertNamespaces(text);
  2172. //Remove Duplicate Namespaces
  2173. text = removeDuplicateNamespaces(text);
  2174. //IE raises an exception
  2175. var kml;
  2176. var error;
  2177. try {
  2178. kml = parser.parseFromString(text, 'application/xml');
  2179. } catch (e) {
  2180. error = e.toString();
  2181. }
  2182. //The parse succeeds on Chrome and Firefox, but the error
  2183. //handling is different in each.
  2184. if (defined(error) || kml.body || kml.documentElement.tagName === 'parsererror') {
  2185. //Firefox has error information as the firstChild nodeValue.
  2186. var msg = defined(error) ? error : kml.documentElement.firstChild.nodeValue;
  2187. //Chrome has it in the body text.
  2188. if (!msg) {
  2189. msg = kml.body.innerText;
  2190. }
  2191. //Return the error
  2192. throw new RuntimeError(msg);
  2193. }
  2194. return loadKml(dataSource, entityCollection, kml, sourceUri, uriResolver, context);
  2195. });
  2196. });
  2197. }
  2198. return loadKml(dataSource, entityCollection, dataToLoad, sourceUri, uriResolver, context);
  2199. })
  2200. .otherwise(function(error) {
  2201. dataSource._error.raiseEvent(dataSource, error);
  2202. console.log(error);
  2203. return when.reject(error);
  2204. });
  2205. }
  2206. /**
  2207. * A {@link DataSource} which processes Keyhole Markup Language 2.2 (KML).
  2208. * <p>
  2209. * KML support in Cesium is incomplete, but a large amount of the standard,
  2210. * as well as Google's <code>gx</code> extension namespace, is supported. See Github issue
  2211. * {@link https://github.com/AnalyticalGraphicsInc/cesium/issues/873|#873} for a
  2212. * detailed list of what is and isn't support. Cesium will also write information to the
  2213. * console when it encounters most unsupported features.
  2214. * </p>
  2215. * <p>
  2216. * Non visual feature data, such as <code>atom:author</code> and <code>ExtendedData</code>
  2217. * is exposed via an instance of {@link KmlFeatureData}, which is added to each {@link Entity}
  2218. * under the <code>kml</code> property.
  2219. * </p>
  2220. *
  2221. * @alias KmlDataSource
  2222. * @constructor
  2223. *
  2224. * @param {Object} options An object with the following properties:
  2225. * @param {Camera} options.camera The camera that is used for viewRefreshModes and sending camera properties to network links.
  2226. * @param {Canvas} options.canvas The canvas that is used for sending viewer properties to network links.
  2227. * @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The global ellipsoid used for geographical calculations.
  2228. * @param {Credit|String} [options.credit] A credit for the data source, which is displayed on the canvas.
  2229. *
  2230. * @see {@link http://www.opengeospatial.org/standards/kml/|Open Geospatial Consortium KML Standard}
  2231. * @see {@link https://developers.google.com/kml/|Google KML Documentation}
  2232. *
  2233. * @demo {@link https://sandcastle.cesium.com/index.html?src=KML.html|Cesium Sandcastle KML Demo}
  2234. *
  2235. * @example
  2236. * var viewer = new Cesium.Viewer('cesiumContainer');
  2237. * viewer.dataSources.add(Cesium.KmlDataSource.load('../../SampleData/facilities.kmz',
  2238. * {
  2239. * camera: viewer.scene.camera,
  2240. * canvas: viewer.scene.canvas
  2241. * })
  2242. * );
  2243. */
  2244. function KmlDataSource(options) {
  2245. options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  2246. var camera = options.camera;
  2247. var canvas = options.canvas;
  2248. //>>includeStart('debug', pragmas.debug);
  2249. if (!defined(camera)) {
  2250. throw new DeveloperError('options.camera is required.');
  2251. }
  2252. if (!defined(canvas)) {
  2253. throw new DeveloperError('options.canvas is required.');
  2254. }
  2255. //>>includeEnd('debug');
  2256. this._changed = new Event();
  2257. this._error = new Event();
  2258. this._loading = new Event();
  2259. this._refresh = new Event();
  2260. this._unsupportedNode = new Event();
  2261. this._clock = undefined;
  2262. this._entityCollection = new EntityCollection(this);
  2263. this._name = undefined;
  2264. this._isLoading = false;
  2265. this._pinBuilder = new PinBuilder();
  2266. this._networkLinks = new AssociativeArray();
  2267. this._entityCluster = new EntityCluster();
  2268. this._canvas = canvas;
  2269. this._camera = camera;
  2270. this._lastCameraView = {
  2271. position : defined(camera) ? Cartesian3.clone(camera.positionWC) : undefined,
  2272. direction : defined(camera) ? Cartesian3.clone(camera.directionWC) : undefined,
  2273. up : defined(camera) ? Cartesian3.clone(camera.upWC) : undefined,
  2274. bbox : defined(camera) ? camera.computeViewRectangle() : Rectangle.clone(Rectangle.MAX_VALUE)
  2275. };
  2276. this._ellipsoid = defaultValue(options.ellipsoid, Ellipsoid.WGS84);
  2277. // User specified credit
  2278. var credit = options.credit;
  2279. if (typeof credit === 'string') {
  2280. credit = new Credit(credit);
  2281. }
  2282. this._credit = credit;
  2283. // Create a list of Credit's from the resource that the user can't remove
  2284. this._resourceCredits = [];
  2285. }
  2286. /**
  2287. * Creates a Promise to a new instance loaded with the provided KML data.
  2288. *
  2289. * @param {Resource|String|Document|Blob} data A url, parsed KML document, or Blob containing binary KMZ data or a parsed KML document.
  2290. * @param {Object} options An object with the following properties:
  2291. * @param {Camera} options.camera The camera that is used for viewRefreshModes and sending camera properties to network links.
  2292. * @param {Canvas} options.canvas The canvas that is used for sending viewer properties to network links.
  2293. * @param {String} [options.sourceUri] Overrides the url to use for resolving relative links and other KML network features.
  2294. * @param {Boolean} [options.clampToGround=false] true if we want the geometry features (Polygons, LineStrings and LinearRings) clamped to the ground.
  2295. * @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The global ellipsoid used for geographical calculations.
  2296. * @param {Credit|String} [options.credit] A credit for the data source, which is displayed on the canvas.
  2297. *
  2298. * @returns {Promise.<KmlDataSource>} A promise that will resolve to a new KmlDataSource instance once the KML is loaded.
  2299. */
  2300. KmlDataSource.load = function(data, options) {
  2301. options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  2302. var dataSource = new KmlDataSource(options);
  2303. return dataSource.load(data, options);
  2304. };
  2305. defineProperties(KmlDataSource.prototype, {
  2306. /**
  2307. * Gets or sets a human-readable name for this instance.
  2308. * This will be automatically be set to the KML document name on load.
  2309. * @memberof KmlDataSource.prototype
  2310. * @type {String}
  2311. */
  2312. name : {
  2313. get : function() {
  2314. return this._name;
  2315. },
  2316. set : function(value) {
  2317. if (this._name !== value) {
  2318. this._name = value;
  2319. this._changed.raiseEvent(this);
  2320. }
  2321. }
  2322. },
  2323. /**
  2324. * Gets the clock settings defined by the loaded KML. This represents the total
  2325. * availability interval for all time-dynamic data. If the KML does not contain
  2326. * time-dynamic data, this value is undefined.
  2327. * @memberof KmlDataSource.prototype
  2328. * @type {DataSourceClock}
  2329. */
  2330. clock : {
  2331. get : function() {
  2332. return this._clock;
  2333. }
  2334. },
  2335. /**
  2336. * Gets the collection of {@link Entity} instances.
  2337. * @memberof KmlDataSource.prototype
  2338. * @type {EntityCollection}
  2339. */
  2340. entities : {
  2341. get : function() {
  2342. return this._entityCollection;
  2343. }
  2344. },
  2345. /**
  2346. * Gets a value indicating if the data source is currently loading data.
  2347. * @memberof KmlDataSource.prototype
  2348. * @type {Boolean}
  2349. */
  2350. isLoading : {
  2351. get : function() {
  2352. return this._isLoading;
  2353. }
  2354. },
  2355. /**
  2356. * Gets an event that will be raised when the underlying data changes.
  2357. * @memberof KmlDataSource.prototype
  2358. * @type {Event}
  2359. */
  2360. changedEvent : {
  2361. get : function() {
  2362. return this._changed;
  2363. }
  2364. },
  2365. /**
  2366. * Gets an event that will be raised if an error is encountered during processing.
  2367. * @memberof KmlDataSource.prototype
  2368. * @type {Event}
  2369. */
  2370. errorEvent : {
  2371. get : function() {
  2372. return this._error;
  2373. }
  2374. },
  2375. /**
  2376. * Gets an event that will be raised when the data source either starts or stops loading.
  2377. * @memberof KmlDataSource.prototype
  2378. * @type {Event}
  2379. */
  2380. loadingEvent : {
  2381. get : function() {
  2382. return this._loading;
  2383. }
  2384. },
  2385. /**
  2386. * Gets an event that will be raised when the data source refreshes a network link.
  2387. * @memberof KmlDataSource.prototype
  2388. * @type {Event}
  2389. */
  2390. refreshEvent : {
  2391. get : function() {
  2392. return this._refresh;
  2393. }
  2394. },
  2395. /**
  2396. * Gets an event that will be raised when the data source finds an unsupported node type.
  2397. * @memberof KmlDataSource.prototype
  2398. * @type {Event}
  2399. */
  2400. unsupportedNodeEvent : {
  2401. get : function() {
  2402. return this._unsupportedNode;
  2403. }
  2404. },
  2405. /**
  2406. * Gets whether or not this data source should be displayed.
  2407. * @memberof KmlDataSource.prototype
  2408. * @type {Boolean}
  2409. */
  2410. show : {
  2411. get : function() {
  2412. return this._entityCollection.show;
  2413. },
  2414. set : function(value) {
  2415. this._entityCollection.show = value;
  2416. }
  2417. },
  2418. /**
  2419. * Gets or sets the clustering options for this data source. This object can be shared between multiple data sources.
  2420. *
  2421. * @memberof KmlDataSource.prototype
  2422. * @type {EntityCluster}
  2423. */
  2424. clustering : {
  2425. get : function() {
  2426. return this._entityCluster;
  2427. },
  2428. set : function(value) {
  2429. //>>includeStart('debug', pragmas.debug);
  2430. if (!defined(value)) {
  2431. throw new DeveloperError('value must be defined.');
  2432. }
  2433. //>>includeEnd('debug');
  2434. this._entityCluster = value;
  2435. }
  2436. },
  2437. /**
  2438. * Gets the credit that will be displayed for the data source
  2439. * @memberof KmlDataSource.prototype
  2440. * @type {Credit}
  2441. */
  2442. credit : {
  2443. get : function() {
  2444. return this._credit;
  2445. }
  2446. }
  2447. });
  2448. /**
  2449. * Asynchronously loads the provided KML data, replacing any existing data.
  2450. *
  2451. * @param {Resource|String|Document|Blob} data A url, parsed KML document, or Blob containing binary KMZ data or a parsed KML document.
  2452. * @param {Object} [options] An object with the following properties:
  2453. * @param {Resource|String} [options.sourceUri] Overrides the url to use for resolving relative links and other KML network features.
  2454. * @param {Boolean} [options.clampToGround=false] true if we want the geometry features (Polygons, LineStrings and LinearRings) clamped to the ground. If true, lines will use corridors so use Entity.corridor instead of Entity.polyline.
  2455. * @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The global ellipsoid used for geographical calculations.
  2456. *
  2457. * @returns {Promise.<KmlDataSource>} A promise that will resolve to this instances once the KML is loaded.
  2458. */
  2459. KmlDataSource.prototype.load = function(data, options) {
  2460. //>>includeStart('debug', pragmas.debug);
  2461. if (!defined(data)) {
  2462. throw new DeveloperError('data is required.');
  2463. }
  2464. //>>includeEnd('debug');
  2465. options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  2466. DataSource.setLoading(this, true);
  2467. var oldName = this._name;
  2468. this._name = undefined;
  2469. this._clampToGround = defaultValue(options.clampToGround, false);
  2470. var that = this;
  2471. return load(this, this._entityCollection, data, options).then(function() {
  2472. var clock;
  2473. var availability = that._entityCollection.computeAvailability();
  2474. var start = availability.start;
  2475. var stop = availability.stop;
  2476. var isMinStart = JulianDate.equals(start, Iso8601.MINIMUM_VALUE);
  2477. var isMaxStop = JulianDate.equals(stop, Iso8601.MAXIMUM_VALUE);
  2478. if (!isMinStart || !isMaxStop) {
  2479. var date;
  2480. //If start is min time just start at midnight this morning, local time
  2481. if (isMinStart) {
  2482. date = new Date();
  2483. date.setHours(0, 0, 0, 0);
  2484. start = JulianDate.fromDate(date);
  2485. }
  2486. //If stop is max value just stop at midnight tonight, local time
  2487. if (isMaxStop) {
  2488. date = new Date();
  2489. date.setHours(24, 0, 0, 0);
  2490. stop = JulianDate.fromDate(date);
  2491. }
  2492. clock = new DataSourceClock();
  2493. clock.startTime = start;
  2494. clock.stopTime = stop;
  2495. clock.currentTime = JulianDate.clone(start);
  2496. clock.clockRange = ClockRange.LOOP_STOP;
  2497. clock.clockStep = ClockStep.SYSTEM_CLOCK_MULTIPLIER;
  2498. clock.multiplier = Math.round(Math.min(Math.max(JulianDate.secondsDifference(stop, start) / 60, 1), 3.15569e7));
  2499. }
  2500. var changed = false;
  2501. if (clock !== that._clock) {
  2502. that._clock = clock;
  2503. changed = true;
  2504. }
  2505. if (oldName !== that._name) {
  2506. changed = true;
  2507. }
  2508. if (changed) {
  2509. that._changed.raiseEvent(that);
  2510. }
  2511. DataSource.setLoading(that, false);
  2512. return that;
  2513. }).otherwise(function(error) {
  2514. DataSource.setLoading(that, false);
  2515. that._error.raiseEvent(that, error);
  2516. console.log(error);
  2517. return when.reject(error);
  2518. });
  2519. };
  2520. function mergeAvailabilityWithParent(child) {
  2521. var parent = child.parent;
  2522. if (defined(parent)) {
  2523. var parentAvailability = parent.availability;
  2524. if (defined(parentAvailability)) {
  2525. var childAvailability = child.availability;
  2526. if (defined(childAvailability)) {
  2527. childAvailability.intersect(parentAvailability);
  2528. } else {
  2529. child.availability = parentAvailability;
  2530. }
  2531. }
  2532. }
  2533. }
  2534. function getNetworkLinkUpdateCallback(dataSource, networkLink, newEntityCollection, networkLinks, processedHref) {
  2535. return function(rootElement) {
  2536. if (!networkLinks.contains(networkLink.id)) {
  2537. // Got into the odd case where a parent network link was updated while a child
  2538. // network link update was in flight, so just throw it away.
  2539. return;
  2540. }
  2541. var remove = false;
  2542. var networkLinkControl = queryFirstNode(rootElement, 'NetworkLinkControl', namespaces.kml);
  2543. var hasNetworkLinkControl = defined(networkLinkControl);
  2544. var minRefreshPeriod = 0;
  2545. if (hasNetworkLinkControl) {
  2546. if (defined(queryFirstNode(networkLinkControl, 'Update', namespaces.kml))) {
  2547. oneTimeWarning('kml-networkLinkControl-update', 'KML - NetworkLinkControl updates aren\'t supported.');
  2548. networkLink.updating = false;
  2549. networkLinks.remove(networkLink.id);
  2550. return;
  2551. }
  2552. networkLink.cookie = queryToObject(defaultValue(queryStringValue(networkLinkControl, 'cookie', namespaces.kml), ''));
  2553. minRefreshPeriod = defaultValue(queryNumericValue(networkLinkControl, 'minRefreshPeriod', namespaces.kml), 0);
  2554. }
  2555. var now = JulianDate.now();
  2556. var refreshMode = networkLink.refreshMode;
  2557. if (refreshMode === RefreshMode.INTERVAL) {
  2558. if (defined(networkLinkControl)) {
  2559. networkLink.time = Math.max(minRefreshPeriod, networkLink.time);
  2560. }
  2561. } else if (refreshMode === RefreshMode.EXPIRE) {
  2562. var expires;
  2563. if (defined(networkLinkControl)) {
  2564. expires = queryStringValue(networkLinkControl, 'expires', namespaces.kml);
  2565. }
  2566. if (defined(expires)) {
  2567. try {
  2568. var date = JulianDate.fromIso8601(expires);
  2569. var diff = JulianDate.secondsDifference(date, now);
  2570. if (diff > 0 && diff < minRefreshPeriod) {
  2571. JulianDate.addSeconds(now, minRefreshPeriod, date);
  2572. }
  2573. networkLink.time = date;
  2574. } catch (e) {
  2575. oneTimeWarning('kml-networkLinkControl-expires', 'KML - NetworkLinkControl expires is not a valid date');
  2576. remove = true;
  2577. }
  2578. } else {
  2579. oneTimeWarning('kml-refreshMode-onExpire', 'KML - refreshMode of onExpire requires the NetworkLinkControl to have an expires element');
  2580. remove = true;
  2581. }
  2582. }
  2583. var networkLinkEntity = networkLink.entity;
  2584. var entityCollection = dataSource._entityCollection;
  2585. var newEntities = newEntityCollection.values;
  2586. function removeChildren(entity) {
  2587. entityCollection.remove(entity);
  2588. var children = entity._children;
  2589. var count = children.length;
  2590. for (var i = 0; i < count; ++i) {
  2591. removeChildren(children[i]);
  2592. }
  2593. }
  2594. // Remove old entities
  2595. entityCollection.suspendEvents();
  2596. var entitiesCopy = entityCollection.values.slice();
  2597. var i;
  2598. for (i = 0; i < entitiesCopy.length; ++i) {
  2599. var entityToRemove = entitiesCopy[i];
  2600. if (entityToRemove.parent === networkLinkEntity) {
  2601. entityToRemove.parent = undefined;
  2602. removeChildren(entityToRemove);
  2603. }
  2604. }
  2605. entityCollection.resumeEvents();
  2606. // Add new entities
  2607. entityCollection.suspendEvents();
  2608. for (i = 0; i < newEntities.length; i++) {
  2609. var newEntity = newEntities[i];
  2610. if (!defined(newEntity.parent)) {
  2611. newEntity.parent = networkLinkEntity;
  2612. mergeAvailabilityWithParent(newEntity);
  2613. }
  2614. entityCollection.add(newEntity);
  2615. }
  2616. entityCollection.resumeEvents();
  2617. // No refresh information remove it, otherwise update lastUpdate time
  2618. if (remove) {
  2619. networkLinks.remove(networkLink.id);
  2620. } else {
  2621. networkLink.lastUpdated = now;
  2622. }
  2623. var availability = entityCollection.computeAvailability();
  2624. var start = availability.start;
  2625. var stop = availability.stop;
  2626. var isMinStart = JulianDate.equals(start, Iso8601.MINIMUM_VALUE);
  2627. var isMaxStop = JulianDate.equals(stop, Iso8601.MAXIMUM_VALUE);
  2628. if (!isMinStart || !isMaxStop) {
  2629. var clock = dataSource._clock;
  2630. if (clock.startTime !== start || clock.stopTime !== stop) {
  2631. clock.startTime = start;
  2632. clock.stopTime = stop;
  2633. dataSource._changed.raiseEvent(dataSource);
  2634. }
  2635. }
  2636. networkLink.updating = false;
  2637. networkLink.needsUpdate = false;
  2638. dataSource._refresh.raiseEvent(dataSource, processedHref.getUrlComponent(true));
  2639. };
  2640. }
  2641. var entitiesToIgnore = new AssociativeArray();
  2642. /**
  2643. * Updates any NetworkLink that require updating
  2644. * @function
  2645. *
  2646. * @param {JulianDate} time The simulation time.
  2647. * @returns {Boolean} True if this data source is ready to be displayed at the provided time, false otherwise.
  2648. */
  2649. KmlDataSource.prototype.update = function(time) {
  2650. var networkLinks = this._networkLinks;
  2651. if (networkLinks.length === 0) {
  2652. return true;
  2653. }
  2654. var now = JulianDate.now();
  2655. var that = this;
  2656. entitiesToIgnore.removeAll();
  2657. function recurseIgnoreEntities(entity) {
  2658. var children = entity._children;
  2659. var count = children.length;
  2660. for (var i = 0; i < count; ++i) {
  2661. var child = children[i];
  2662. entitiesToIgnore.set(child.id, child);
  2663. recurseIgnoreEntities(child);
  2664. }
  2665. }
  2666. var cameraViewUpdate = false;
  2667. var lastCameraView = this._lastCameraView;
  2668. var camera = this._camera;
  2669. if (defined(camera) &&
  2670. !(camera.positionWC.equalsEpsilon(lastCameraView.position, CesiumMath.EPSILON7) &&
  2671. camera.directionWC.equalsEpsilon(lastCameraView.direction, CesiumMath.EPSILON7) &&
  2672. camera.upWC.equalsEpsilon(lastCameraView.up, CesiumMath.EPSILON7))) {
  2673. // Camera has changed so update the last view
  2674. lastCameraView.position = Cartesian3.clone(camera.positionWC);
  2675. lastCameraView.direction = Cartesian3.clone(camera.directionWC);
  2676. lastCameraView.up = Cartesian3.clone(camera.upWC);
  2677. lastCameraView.bbox = camera.computeViewRectangle();
  2678. cameraViewUpdate = true;
  2679. }
  2680. var newNetworkLinks = new AssociativeArray();
  2681. var changed = false;
  2682. networkLinks.values.forEach(function(networkLink) {
  2683. var entity = networkLink.entity;
  2684. if (entitiesToIgnore.contains(entity.id)) {
  2685. return;
  2686. }
  2687. if (!networkLink.updating) {
  2688. var doUpdate = false;
  2689. if (networkLink.refreshMode === RefreshMode.INTERVAL) {
  2690. if (JulianDate.secondsDifference(now, networkLink.lastUpdated) > networkLink.time) {
  2691. doUpdate = true;
  2692. }
  2693. }
  2694. else if (networkLink.refreshMode === RefreshMode.EXPIRE) {
  2695. if (JulianDate.greaterThan(now, networkLink.time)) {
  2696. doUpdate = true;
  2697. }
  2698. } else if (networkLink.refreshMode === RefreshMode.STOP) {
  2699. if (cameraViewUpdate) {
  2700. networkLink.needsUpdate = true;
  2701. networkLink.cameraUpdateTime = now;
  2702. }
  2703. if (networkLink.needsUpdate && JulianDate.secondsDifference(now, networkLink.cameraUpdateTime) >= networkLink.time) {
  2704. doUpdate = true;
  2705. }
  2706. }
  2707. if (doUpdate) {
  2708. recurseIgnoreEntities(entity);
  2709. networkLink.updating = true;
  2710. var newEntityCollection = new EntityCollection();
  2711. var href = networkLink.href.clone();
  2712. href.setQueryParameters(networkLink.cookie);
  2713. var ellipsoid = defaultValue(that._ellipsoid, Ellipsoid.WGS84);
  2714. processNetworkLinkQueryString(href, that._camera, that._canvas, networkLink.viewBoundScale, lastCameraView.bbox, ellipsoid);
  2715. load(that, newEntityCollection, href, {context : entity.id})
  2716. .then(getNetworkLinkUpdateCallback(that, networkLink, newEntityCollection, newNetworkLinks, href))
  2717. .otherwise(function(error) {
  2718. var msg = 'NetworkLink ' + networkLink.href + ' refresh failed: ' + error;
  2719. console.log(msg);
  2720. that._error.raiseEvent(that, msg);
  2721. });
  2722. changed = true;
  2723. }
  2724. }
  2725. newNetworkLinks.set(networkLink.id, networkLink);
  2726. });
  2727. if (changed) {
  2728. this._networkLinks = newNetworkLinks;
  2729. this._changed.raiseEvent(this);
  2730. }
  2731. return true;
  2732. };
  2733. /**
  2734. * Contains KML Feature data loaded into the <code>Entity.kml</code> property by {@link KmlDataSource}.
  2735. * @alias KmlFeatureData
  2736. * @constructor
  2737. */
  2738. function KmlFeatureData() {
  2739. /**
  2740. * Gets the atom syndication format author field.
  2741. * @type Object
  2742. */
  2743. this.author = {
  2744. /**
  2745. * Gets the name.
  2746. * @type String
  2747. * @alias author.name
  2748. * @memberof! KmlFeatureData#
  2749. * @property author.name
  2750. */
  2751. name : undefined,
  2752. /**
  2753. * Gets the URI.
  2754. * @type String
  2755. * @alias author.uri
  2756. * @memberof! KmlFeatureData#
  2757. * @property author.uri
  2758. */
  2759. uri : undefined,
  2760. /**
  2761. * Gets the email.
  2762. * @type String
  2763. * @alias author.email
  2764. * @memberof! KmlFeatureData#
  2765. * @property author.email
  2766. */
  2767. email : undefined
  2768. };
  2769. /**
  2770. * Gets the link.
  2771. * @type Object
  2772. */
  2773. this.link = {
  2774. /**
  2775. * Gets the href.
  2776. * @type String
  2777. * @alias link.href
  2778. * @memberof! KmlFeatureData#
  2779. * @property link.href
  2780. */
  2781. href : undefined,
  2782. /**
  2783. * Gets the language of the linked resource.
  2784. * @type String
  2785. * @alias link.hreflang
  2786. * @memberof! KmlFeatureData#
  2787. * @property link.hreflang
  2788. */
  2789. hreflang : undefined,
  2790. /**
  2791. * Gets the link relation.
  2792. * @type String
  2793. * @alias link.rel
  2794. * @memberof! KmlFeatureData#
  2795. * @property link.rel
  2796. */
  2797. rel : undefined,
  2798. /**
  2799. * Gets the link type.
  2800. * @type String
  2801. * @alias link.type
  2802. * @memberof! KmlFeatureData#
  2803. * @property link.type
  2804. */
  2805. type : undefined,
  2806. /**
  2807. * Gets the link title.
  2808. * @type String
  2809. * @alias link.title
  2810. * @memberof! KmlFeatureData#
  2811. * @property link.title
  2812. */
  2813. title : undefined,
  2814. /**
  2815. * Gets the link length.
  2816. * @type String
  2817. * @alias link.length
  2818. * @memberof! KmlFeatureData#
  2819. * @property link.length
  2820. */
  2821. length : undefined
  2822. };
  2823. /**
  2824. * Gets the unstructured address field.
  2825. * @type String
  2826. */
  2827. this.address = undefined;
  2828. /**
  2829. * Gets the phone number.
  2830. * @type String
  2831. */
  2832. this.phoneNumber = undefined;
  2833. /**
  2834. * Gets the snippet.
  2835. * @type String
  2836. */
  2837. this.snippet = undefined;
  2838. /**
  2839. * Gets the extended data, parsed into a JSON object.
  2840. * Currently only the <code>Data</code> property is supported.
  2841. * <code>SchemaData</code> and custom data are ignored.
  2842. * @type String
  2843. */
  2844. this.extendedData = undefined;
  2845. }
  2846. // For testing
  2847. KmlDataSource._DeferredLoading = DeferredLoading;
  2848. KmlDataSource._getTimestamp = getTimestamp;
  2849. export default KmlDataSource;