1 /*
2  * Copyright (C) 2008 Fabien Chereau
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License
6  * as published by the Free Software Foundation; either version 2
7  * of the License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA  02110-1335, USA.
17  */
18 
19 #include "StelSkyImageTile.hpp"
20 #include "StelTextureMgr.hpp"
21 #include "StelApp.hpp"
22 #include "StelFileMgr.hpp"
23 #include "StelUtils.hpp"
24 #include "StelTexture.hpp"
25 #include "StelProjector.hpp"
26 #include "StelToneReproducer.hpp"
27 #include "StelCore.hpp"
28 #include "StelSkyDrawer.hpp"
29 #include "StelPainter.hpp"
30 #include "StelModuleMgr.hpp"
31 #include "SolarSystem.hpp"
32 #include <QDebug>
33 
34 #include <cstdio>
35 
StelSkyImageTile()36 StelSkyImageTile::StelSkyImageTile()
37 {
38 	initCtor();
39 }
40 
initCtor()41 void StelSkyImageTile::initCtor()
42 {
43 	minResolution = -1;
44 	luminance = -1;
45 	alphaBlend = false;
46 	noTexture = false;
47 	texFader = Q_NULLPTR;
48 	birthJD = -1e10;
49 	withAberration = true;
50 }
51 
52 // Constructor
StelSkyImageTile(const QString & url,StelSkyImageTile * parent)53 StelSkyImageTile::StelSkyImageTile(const QString& url, StelSkyImageTile* parent) : MultiLevelJsonBase(parent)
54 {
55 	initCtor();
56 	if (parent!=Q_NULLPTR)
57 	{
58 		luminance = parent->luminance;
59 		alphaBlend = parent->alphaBlend;
60 	}
61 	initFromUrl(url);
62 }
63 
64 // Constructor from a map used for JSON files with more than 1 level
StelSkyImageTile(const QVariantMap & map,StelSkyImageTile * parent)65 StelSkyImageTile::StelSkyImageTile(const QVariantMap& map, StelSkyImageTile* parent) : MultiLevelJsonBase(parent)
66 {
67 	initCtor();
68 	if (parent!=Q_NULLPTR)
69 	{
70 		luminance = parent->luminance;
71 		alphaBlend = parent->alphaBlend;
72 	}
73 	initFromQVariantMap(map);
74 }
75 
76 // Destructor
~StelSkyImageTile()77 StelSkyImageTile::~StelSkyImageTile()
78 {
79 }
80 
draw(StelCore * core,StelPainter & sPainter,float opacity)81 void StelSkyImageTile::draw(StelCore* core, StelPainter& sPainter, float opacity)
82 {
83 	Q_UNUSED(opacity)
84 	const StelProjectorP prj = core->getProjection(StelCore::FrameJ2000);
85 
86 	// compute aberration correction vector
87 	Vec3d vel(0.0);
88 	if ((core) && (core->getUseAberration()) && (core->getCurrentPlanet()) && (withAberration))
89 	{
90 		vel=core->getCurrentPlanet()->getHeliocentricEclipticVelocity();
91 		vel=StelCore::matVsop87ToJ2000*vel*core->getAberrationFactor()*(AU/(86400.0*SPEED_OF_LIGHT));
92 	}
93 
94 	const float limitLuminance = core->getSkyDrawer()->getLimitLuminance();
95 	QMultiMap<double, StelSkyImageTile*> result;
96 	// TODO: adjust that viewportconvexpolygon by aberration to select the right tiles.
97 	// I thought the viewportpolygon needs to be enlarged just a bit. (I use 20 arcseconds here as estimate of max. aberration from earth.)
98 	//getTilesToDraw(result, core, prj->getViewportConvexPolygon(0,0)->getEnlarged(20./3600.*M_PI_180 *core->getAberrationFactor()), limitLuminance, true);
99 	// But it seems not even the AllSky region prevents clipping, so it must be caused somewhere else.
100 //	const SphericalCap& hp = prj->getBoundingCap();
101 //	getTilesToDraw(result, core, SphericalRegionP(new SphericalCap(hp)), limitLuminance, true);
102 	getTilesToDraw(result, core, SphericalRegionP(new AllSkySphericalRegion()), limitLuminance, true);
103 
104 	int numToBeLoaded=0;
105 	for (auto* t : result)
106 		if (t->isReadyToDisplay()==false)
107 			++numToBeLoaded;
108 	updatePercent(result.size(), numToBeLoaded);
109 
110 	// Draw in the good order
111 	sPainter.setBlending(true, GL_ONE, GL_ONE);
112 	auto i = result.end();
113 	while (i!=result.begin())
114 	{
115 		--i;
116 		i.value()->drawTile(core, sPainter, vel);
117 	}
118 
119 	deleteUnusedSubTiles();
120 }
121 
122 // Return the list of tiles which should be drawn.
getTilesToDraw(QMultiMap<double,StelSkyImageTile * > & result,StelCore * core,const SphericalRegionP & viewPortPoly,float limitLuminance,bool recheckIntersect)123 void StelSkyImageTile::getTilesToDraw(QMultiMap<double, StelSkyImageTile*>& result, StelCore* core, const SphericalRegionP& viewPortPoly, float limitLuminance, bool recheckIntersect)
124 {
125 #ifndef NDEBUG
126 	// When this method is called, we can assume that:
127 	// - the parent tile min resolution was reached
128 	// - the parent tile is intersecting FOV
129 	// - the parent tile is not scheduled for deletion
130 	const StelSkyImageTile* parent = qobject_cast<StelSkyImageTile*>(QObject::parent());
131 	if (parent!=Q_NULLPTR)
132 	{
133 		Q_ASSERT(isDeletionScheduled()==false);
134 		const double degPerPixel = 1./core->getProjection(StelCore::FrameJ2000)->getPixelPerRadAtCenter()*M_180_PI;
135 		Q_ASSERT(degPerPixel<parent->minResolution);
136 
137 		Q_ASSERT(parent->isDeletionScheduled()==false);
138 	}
139 #endif
140 
141 	// An error occured during loading
142 	if (errorOccured)
143 		return;
144 
145 	// The JSON file is currently being downloaded
146 	if (downloading)
147 	{
148 		//qDebug() << "Downloading " << contructorUrl;
149 		return;
150 	}
151 
152 	if (luminance>0 && luminance<limitLuminance)
153 	{
154 		// Schedule a deletion
155 		scheduleChildsDeletion();
156 		return;
157 	}
158 
159 	if (birthJD>-1e10 && birthJD>core->getJD())
160 	{
161 		// Schedule a deletion
162 		scheduleChildsDeletion();
163 		return;
164 	}
165 
166 	// Check that we are in the screen
167 	bool fullInScreen = true;
168 	bool intersectScreen = false;
169 	if (recheckIntersect)
170 	{
171 		if (skyConvexPolygons.isEmpty())
172 		{
173 			// If no polygon is defined, we assume that the tile covers the whole sky
174 			fullInScreen=false;
175 			intersectScreen=true;
176 		}
177 		else
178 		{
179 			for (const auto& poly : skyConvexPolygons)
180 			{
181 				if (viewPortPoly->contains(poly))
182 				{
183 					intersectScreen = true;
184 				}
185 				else
186 				{
187 					fullInScreen = false;
188 					if (viewPortPoly->intersects(poly))
189 						intersectScreen = true;
190 				}
191 			}
192 		}
193 	}
194 	// The tile is outside screen
195 	if (fullInScreen==false && intersectScreen==false)
196 	{
197 		// Schedule a deletion
198 		scheduleChildsDeletion();
199 		return;
200 	}
201 
202 	// The tile is in screen, and it is a precondition that its resolution is higher than the limit
203 	// make sure that it's not going to be deleted
204 	cancelDeletion();
205 
206 	if (noTexture==false)
207 	{
208 		if (!tex)
209 		{
210 			// The tile has an associated texture, but it is not yet loaded: load it now
211 			StelTextureMgr& texMgr=StelApp::getInstance().getTextureManager();
212 			tex = texMgr.createTextureThread(absoluteImageURI, StelTexture::StelTextureParams(true));
213 			if (!tex)
214 			{
215 				qWarning() << "WARNING : Can't create tile: " << absoluteImageURI;
216 				errorOccured = true;
217 				return;
218 			}
219 		}
220 
221 		// The tile is in screen and has a texture: every test passed :) The tile will be displayed
222 		result.insert(minResolution, this);
223 	}
224 
225 	// Check if we reach the resolution limit
226 	const float degPerPixel = 1.f/core->getProjection(StelCore::FrameJ2000)->getPixelPerRadAtCenter()*M_180_PIf;
227 	if (degPerPixel < minResolution)
228 	{
229 		if (subTiles.isEmpty() && !subTilesUrls.isEmpty())
230 		{
231 			// Load the sub tiles because we reached the maximum resolution and they are not yet loaded
232 			for (const auto& s : subTilesUrls)
233 			{
234 				StelSkyImageTile* nt;
235 				if (s.type()==QVariant::Map)
236 					nt = new StelSkyImageTile(s.toMap(), this);
237 				else
238 				{
239 					Q_ASSERT(s.type()==QVariant::String);
240 					nt = new StelSkyImageTile(s.toString(), this);
241 				}
242 				subTiles.append(nt);
243 			}
244 		}
245 		// Try to add the subtiles
246 		for (auto* tile : subTiles)
247 		{
248 			qobject_cast<StelSkyImageTile*>(tile)->getTilesToDraw(result, core, viewPortPoly, limitLuminance, !fullInScreen);
249 		}
250 	}
251 	else
252 	{
253 		// The subtiles should not be displayed because their resolution is too high
254 		scheduleChildsDeletion();
255 	}
256 }
257 
258 // Draw the image on the screen.
259 // Assume GL_TEXTURE_2D is enabled
drawTile(StelCore * core,StelPainter & sPainter,const Vec3d & vel)260 bool StelSkyImageTile::drawTile(StelCore* core, StelPainter& sPainter, const Vec3d &vel)
261 {
262 	if (!tex->bind())
263 		return false;
264 
265 	if (!texFader)
266 	{
267 		texFader = new QTimeLine(1000, this);
268 		texFader->start();
269 	}
270 
271 	// Draw the real texture for this image
272 	float ad_lum = (luminance>0) ? qMin(1.0f, core->getToneReproducer()->adaptLuminanceScaled(luminance)) : 1.f;
273 	Vec4f color;
274 	if (alphaBlend || texFader->state()==QTimeLine::Running)
275 	{
276 		if (!alphaBlend)
277 			sPainter.setBlending(true); // Normal transparency mode
278 		else
279 			sPainter.setBlending(true, GL_ONE, GL_ONE); // additive blending
280 		color.set(ad_lum,ad_lum,ad_lum, static_cast<float>(texFader->currentValue()));
281 	}
282 	else
283 	{
284 		sPainter.setBlending(false);
285 		color.set(ad_lum,ad_lum,ad_lum, 1.f);
286 	}
287 
288 	const bool withExtinction=(getFrameType()!=StelCore::FrameAltAz && core->getSkyDrawer()->getFlagHasAtmosphere() && core->getSkyDrawer()->getExtinction().getExtinctionCoefficient()>=0.01f);
289 
290 	for (const auto& poly : skyConvexPolygons)
291 	{
292 		// Not sure: Are all skyConvexPolygons in J2000 frame? This would also simplify code below...
293 		// No, by scripting we can have other frames!
294 		//Q_ASSERT(getFrameType() == StelCore::FrameJ2000);
295 
296 		Vec4f extinctedColor = color;
297 		if (withExtinction)
298 		{
299 			Vec3d bary= poly->getPointInside(); // This is a "frame" vector that points "somewhere" in the first triangle.
300 
301 			// 2017-03: we need a definite J2000 vector:
302 			Vec3d baryJ2000;
303 			double lng, lat, ra, dec; // aux. values for coordinate transformations
304 			double eclJ2000, eclJDE;
305 			//qDebug() << "Frame: " << getFrameType();
306 			switch (getFrameType()) // all possible but AzAlt!
307 			{
308 				case StelCore::FrameJ2000:
309 					baryJ2000=bary;
310 					break;
311 				case	StelCore::FrameEquinoxEqu:
312 					baryJ2000=core->equinoxEquToJ2000(bary, StelCore::RefractionOff);
313 					break;
314 				case	StelCore::FrameObservercentricEclipticJ2000:
315 					// For the ecliptic cases, apply clumsy trafos from StelUtil!
316 					eclJ2000=GETSTELMODULE(SolarSystem)->getEarth()->getRotObliquity(2451545.0);
317 					StelUtils::rectToSphe(&lng, &lat, bary);
318 					StelUtils::eclToEqu(lng, lat, eclJ2000, &ra, &dec);
319 					StelUtils::spheToRect(ra, dec, baryJ2000);
320 					break;
321 				case	StelCore::FrameObservercentricEclipticOfDate:
322 					// Trafo to eqDate, then as above.
323 					eclJDE = GETSTELMODULE(SolarSystem)->getEarth()->getRotObliquity(core->getJDE());
324 					StelUtils::rectToSphe(&lng, &lat, bary);
325 					StelUtils::eclToEqu(lng, lat, eclJDE, &ra, &dec); // convert to Equatorial/equinox of date
326 					StelUtils::spheToRect(ra, dec, bary);
327 					baryJ2000=core->equinoxEquToJ2000(bary, StelCore::RefractionOff);
328 					break;
329 				case	StelCore::FrameGalactic:
330 					baryJ2000=core->galacticToJ2000(bary);
331 					break;
332 				case	StelCore::FrameSupergalactic:
333 					baryJ2000=core->supergalacticToJ2000(bary);
334 					break;
335 				default:
336 					Q_ASSERT(0);
337 					qDebug() << "StelSkyImageTile: unknown FrameType. Assume J2000.";
338 					baryJ2000=bary;
339 			}
340 			Vec3d altAz = core->j2000ToAltAz(baryJ2000, StelCore::RefractionOff);
341 			float extinctionMagnitude=0.0f;
342 			altAz.normalize();
343 			core->getSkyDrawer()->getExtinction().forward(altAz, &extinctionMagnitude);
344 			// compute a simple factor from magnitude loss.
345 			float extinctionFactor=std::pow(0.4f , extinctionMagnitude); // drop of one magnitude: factor 2.5 or 40%
346 			extinctedColor[0]*=fabs(extinctionFactor);
347 			extinctedColor[1]*=fabs(extinctionFactor);
348 			extinctedColor[2]*=fabs(extinctionFactor);
349 		}
350 		sPainter.setColor(extinctedColor);
351 		sPainter.drawSphericalRegion(poly.data(), StelPainter::SphericalPolygonDrawModeTextureFill, Q_NULLPTR, true, 5., vel);
352 	}
353 
354 #ifdef DEBUG_STELSKYIMAGE_TILE
355 	if (debugFont==Q_NULLPTR)
356 	{
357 		debugFont = &StelApp::getInstance().getFontManager().getStandardFont(StelApp::getInstance().getLocaleMgr().getSkyLanguage(), 12);
358 	}
359 	color.set(1.0,0.5,0.5,1.0);
360 	for (const auto& poly : skyConvexPolygons)
361 	{
362 		Vec3d win;
363 		Vec3d bary = poly->getPointInside();
364 		sPainter.getProjector()->project(bary,win);
365 		sPainter.drawText(debugFont, win[0], win[1], getAbsoluteImageURI());
366 		sPainter.enableTexture2d(false);
367 		sPainter.drawSphericalRegion(poly.get(), StelPainter::SphericalPolygonDrawModeBoundary, &color);
368 		sPainter.enableTexture2d(true);
369 	}
370 #endif
371 
372 	return true;
373 }
374 
375 // Return true if the tile is fully loaded and can be displayed
isReadyToDisplay() const376 bool StelSkyImageTile::isReadyToDisplay() const
377 {
378 	return tex && tex->canBind();
379 }
380 
381 // Load the tile from a valid QVariantMap
loadFromQVariantMap(const QVariantMap & map)382 void StelSkyImageTile::loadFromQVariantMap(const QVariantMap& map)
383 {
384 	if (map.contains("imageCredits"))
385 	{
386 		QVariantMap dsCredits = map.value("imageCredits").toMap();
387 		dataSetCredits.shortCredits = dsCredits.value("short").toString();
388 		dataSetCredits.fullCredits = dsCredits.value("full").toString();
389 		dataSetCredits.infoURL = dsCredits.value("infoUrl").toString();
390 	}
391 	if (map.contains("serverCredits"))
392 	{
393 		QVariantMap sCredits = map.value("serverCredits").toMap();
394 		serverCredits.shortCredits = sCredits.value("short").toString();
395 		serverCredits.fullCredits = sCredits.value("full").toString();
396 		serverCredits.infoURL = sCredits.value("infoUrl").toString();
397 	}
398 	if (map.contains("description"))
399 	{
400 		htmlDescription = map.value("description").toString();
401 		if (parent()==Q_NULLPTR)
402 		{
403 			htmlDescription+= "<h3>URL: "+constructorUrl+"</h3>";
404 		}
405 	}
406 	else
407 	{
408 		if (parent()==Q_NULLPTR)
409 		{
410 			htmlDescription= "<h3>URL: "+constructorUrl+"</h3>";
411 		}
412 	}
413 
414 	shortName = map.value("shortName").toString();
415 	if (shortName.isEmpty())
416 		shortName = "no name";
417 	bool ok=false;
418 	if (!map.contains("minResolution"))
419 		throw std::runtime_error(qPrintable(QString("minResolution is mandatory")));
420 	minResolution = map.value("minResolution").toFloat(&ok);
421 	if (!ok)
422 	{
423 		throw std::runtime_error(qPrintable(QString("minResolution expects a double value, found: \"%1\"").arg(map.value("minResolution").toString())));
424 	}
425 
426 	if (map.contains("luminance"))
427 	{
428 		luminance = map.value("luminance").toFloat(&ok);
429 		if (!ok)
430 			throw std::runtime_error("luminance expects a float value");
431 		qWarning() << "luminance in preview JSON files is deprecated. Replace with maxBrightness.";
432 	}
433 
434 	if (map.contains("maxBrightness"))
435 	{
436 		// maxBrightness is the maximum nebula brightness in Vmag/arcmin^2
437 		luminance = map.value("maxBrightness").toFloat(&ok);
438 		if (!ok)
439 			throw std::runtime_error("maxBrightness expects a float value");
440 		luminance = StelApp::getInstance().getCore()->getSkyDrawer()->surfaceBrightnessToLuminance(luminance);
441 	}
442 
443 	if (map.contains("alphaBlend"))
444 	{
445 		alphaBlend = map.value("alphaBlend").toBool();
446 	}
447 
448 	// Load the convex polygons (if any)
449 	QVariantList polyList = map.value("skyConvexPolygons").toList();
450 	if (polyList.empty())
451 		polyList = map.value("worldCoords").toList();
452 	else
453 		qWarning() << "skyConvexPolygons in preview JSON files is deprecated. Replace with worldCoords.";
454 
455 	// Load the matching textures positions (if any)
456 	QVariantList texCoordList = map.value("textureCoords").toList();
457 	if (!texCoordList.isEmpty() && polyList.size()!=texCoordList.size())
458 			throw std::runtime_error("the number of convex polygons does not match the number of texture space polygon");
459 
460 	for (int i=0;i<polyList.size();++i)
461 	{
462 		const QVariant& polyRaDec = polyList.at(i);
463 		QVector<Vec3d> vertices;
464 		for (const auto& vRaDec : polyRaDec.toList())
465 		{
466 			const QVariantList vl = vRaDec.toList();
467 			Vec3d v;
468 			StelUtils::spheToRect(vl.at(0).toFloat(&ok)*M_PI_180f, vl.at(1).toFloat(&ok)*M_PI_180f, v);
469 			if (!ok)
470 				throw std::runtime_error("wrong Ra and Dec, expect a double value");
471 			vertices.append(v);
472 		}
473 		Q_ASSERT(vertices.size()==4);
474 
475 		if (!texCoordList.isEmpty())
476 		{
477 			const QVariant& polyXY = texCoordList.at(i);
478 			QVector<Vec2f> texCoords;
479 			for (const auto& vXY : polyXY.toList())
480 			{
481 				const QVariantList vl = vXY.toList();
482 				texCoords.append(Vec2f(vl.at(0).toFloat(&ok), vl.at(1).toFloat(&ok)));
483 				if (!ok)
484 					throw std::runtime_error("wrong X and Y, expect a double value");
485 			}
486 			Q_ASSERT(texCoords.size()==4);
487 
488 			SphericalTexturedConvexPolygon* pol = new SphericalTexturedConvexPolygon(vertices, texCoords);
489 			Q_ASSERT(pol->checkValid());
490 			skyConvexPolygons.append(SphericalRegionP(pol));
491 		}
492 		else
493 		{
494 			SphericalConvexPolygon* pol = new SphericalConvexPolygon(vertices);
495 			Q_ASSERT(pol->checkValid());
496 			skyConvexPolygons.append(SphericalRegionP(pol));
497 		}
498 	}
499 
500 	if (map.contains("imageUrl"))
501 	{
502 		QString imageUrl = map.value("imageUrl").toString();
503 		if (baseUrl.startsWith("http", Qt::CaseInsensitive))
504 		{
505 			absoluteImageURI = baseUrl+imageUrl;
506 		}
507 		else
508 		{
509 			absoluteImageURI = StelFileMgr::findFile(baseUrl+imageUrl);
510 			if (absoluteImageURI.isEmpty())
511 			{
512 				// Maybe the user meant a file in stellarium local files
513 				absoluteImageURI = imageUrl;
514 			}
515 		}
516 	}
517 	else
518 		noTexture = true;
519 
520 	birthJD = map.value("birthJD", -1e10).toDouble();
521 
522 	// This is a list of URLs to the child tiles or a list of already loaded map containing child information
523 	// (in this later case, the StelSkyImageTile objects will be created later)
524 	subTilesUrls = map.value("subTiles").toList();
525 	for (auto& variant : subTilesUrls)
526 	{
527 		if (variant.type() == QVariant::Map)
528 		{
529 			// Check if the JSON object is a reference, i.e. if it contains a $ref key
530 			QVariantMap m = variant.toMap();
531 			if (m.size()==1 && m.contains("$ref"))
532 			{
533 				variant = QString(m["$ref"].toString());
534 			}
535 		}
536 	}
537 // 	if (subTilesUrls.size()>10)
538 // 	{
539 // 		qWarning() << "Large tiles number for " << shortName << ": " << subTilesUrls.size();
540 // 	}
541 
542 	withAberration = map.value("withAberration", true).toBool();
543 }
544 
545 // Convert the image informations to a map following the JSON structure.
toQVariantMap() const546 QVariantMap StelSkyImageTile::toQVariantMap() const
547 {
548 	QVariantMap res;
549 
550 	// Image credits
551 	QVariantMap imCredits;
552 	if (!dataSetCredits.shortCredits.isEmpty())
553 		imCredits["short"]=dataSetCredits.shortCredits;
554 	if (!dataSetCredits.fullCredits.isEmpty())
555 		imCredits["full"]=dataSetCredits.fullCredits;
556 	if (!dataSetCredits.infoURL.isEmpty())
557 		imCredits["infoUrl"]=dataSetCredits.infoURL;
558 	if (!imCredits.empty())
559 		res["imageCredits"]=imCredits;
560 
561 	// Server credits
562 	QVariantMap serCredits;
563 	if (!serverCredits.shortCredits.isEmpty())
564 		imCredits["short"]=serverCredits.shortCredits;
565 	if (!serverCredits.fullCredits.isEmpty())
566 		imCredits["full"]=serverCredits.fullCredits;
567 	if (!serverCredits.infoURL.isEmpty())
568 		imCredits["infoUrl"]=serverCredits.infoURL;
569 	if (!serCredits.empty())
570 		res["serverCredits"]=serCredits;
571 
572 	// Misc
573 	if (!shortName.isEmpty())
574 		res["shortName"] = shortName;
575 	if (minResolution>0)
576 		res["minResolution"]=minResolution;
577 	if (luminance>0)
578 		res["maxBrightness"]=StelApp::getInstance().getCore()->getSkyDrawer()->luminanceToSurfacebrightness(luminance);
579 	if (alphaBlend)
580 		res["alphaBlend"]=true;
581 	if (noTexture==false)
582 		res["imageUrl"]=absoluteImageURI;
583 	if (birthJD>-1e10)
584 		res["birthJD"]=birthJD;
585 	res["withAberration"] = withAberration;
586 
587 	// Polygons
588 	// TODO
589 
590 	// textures positions
591 	// TODO
592 
593 	if (!subTilesUrls.empty())
594 	{
595 		res["subTiles"] = subTilesUrls;
596 	}
597 
598 	return res;
599 }
600