¿Cómo apuntar correctamente el sol en CesiumJS?

Nov 21 2020

Preparé un ejemplo de castillo de arena de cesio basado en esta respuesta de @FSimardGIS, pero algo va mal: solo uno de los muchos rayos apunta correctamente al Sol, el otro parece aleatorio.

¿Qué pasa aquí? Tomé las coordenadas de prueba de la biblioteca SunCalc, pero también puede insertar datos manualmente, usando, por ejemplo, este sitio para los cálculos:

https://planetcalc.com/320/?day=2020-11-19%2004:31:37&plat=36.13&plon=138.36&gmtdiff=0&UTCoffset=0

Este es el código fuente de la otra respuesta:

function CalcPosFromAltAzDist(startPoint, vectorPointing) {
  var ellipsoid = Cesium.Ellipsoid.WGS84;
  var ENU = new Cesium.Matrix4();
  Cesium.Transforms.eastNorthUpToFixedFrame(startPoint,ellipsoid,ENU);
  var myX = vectorPointing.dist * Math.sin(vectorPointing.az * Math.PI / 180);
  var myY = vectorPointing.dist * Math.cos(vectorPointing.az * Math.PI / 180);
  var myZ = vectorPointing.dist * Math.sin(vectorPointing.alt * Math.PI / 180);
  var offset = new Cesium.Cartesian3(myX,myY,myZ);
  var finalPoint = Cesium.Matrix4.multiplyByPoint(ENU, offset, new Cesium.Cartesian3());
  return finalPoint;    
}

Llamar con:

vectorPointing= {"alt" : 30, "az" : 0};
startPoint= Cesium.Cartesian3.fromDegrees(290.6, -35.78);

Respuestas

1 jumpjack Nov 25 2020 at 21:50

He construido una solución basada en diferentes algoritmos y funciones de Cesium.

No entendí nada sobre las transformaciones y los métodos utilizados ... :-), pero funciona bien.

Ejemplo de castillo de arena.

Código fuente:

var viewer = new Cesium.Viewer("cesiumContainer");
viewer.scene.globe.enableLighting = true;

var suntest = [];

suntest[0] = {'time' : '2020-11-20T12:00:00.000Z', 'lat' : 0, 'lon' :-180, 'alt' : -69.936132980914, 'az' :170.491579546217};
suntest[1] = {'time' : '2020-11-20T12:00:00.000Z', 'lat' : 0, 'lon' :-150, 'alt' : -51.732788127111, 'az' :123.115116564788};
suntest[2] = {'time' : '2020-11-20T12:00:00.000Z', 'lat' : 0, 'lon' :-120, 'alt' : -24.870948550859, 'az' :111.897154212120};
suntest[3] = {'time' : '2020-11-20T12:00:00.000Z', 'lat' : 0, 'lon' :-90, 'alt' : 3.248817322124, 'az' :109.809795550210};
suntest[4] = {'time' : '2020-11-20T12:00:00.000Z', 'lat' : 0, 'lon' :-60, 'alt' : 31.247432115441, 'az' :113.313745536185};
suntest[5] = {'time' : '2020-11-20T12:00:00.000Z', 'lat' : 0, 'lon' :-30, 'alt' : 57.331014803665, 'az' :128.816746856710};
suntest[6] = {'time' : '2020-11-20T12:00:00.000Z', 'lat' : 0, 'lon' :0, 'alt' : 69.936132980893, 'az' :189.508420454115};
suntest[7] = {'time' : '2020-11-20T12:00:00.000Z', 'lat' : 0, 'lon' :30, 'alt' : 51.732788127357, 'az' :236.884883435008};
suntest[8] = {'time' : '2020-11-20T12:00:00.000Z', 'lat' : 0, 'lon' :60, 'alt' : 24.870948550745, 'az' :248.102845787901};
suntest[9] = {'time' : '2020-11-20T12:00:00.000Z', 'lat' : 0, 'lon' :90, 'alt' : -3.248817321848, 'az' :250.190204449796};
suntest[10] = {'time' : '2020-11-20T12:00:00.000Z', 'lat' : 0, 'lon' :120, 'alt' : -31.247432115554, 'az' :246.686254463785};
suntest[11] = {'time' : '2020-11-20T12:00:00.000Z', 'lat' : 0, 'lon' :150, 'alt' : -57.331014803436, 'az' :231.183253143577};
suntest[12] = {'time' : '2020-11-20T12:00:00.000Z', 'lat' : 0, 'lon' :180, 'alt' : -69.936132980873, 'az' :170.491579545552};
suntest[13] = {'time' : '2020-11-20T12:00:00.000Z', 'lat' : 10, 'lon' :-180, 'alt' : -79.671227366220, 'az' :161.573903931188};
suntest[14] = {'time' : '2020-11-20T12:00:00.000Z', 'lat' : 10, 'lon' :-150, 'alt' : -56.300357978857, 'az' :110.783422611356};
suntest[15] = {'time' : '2020-11-20T12:00:00.000Z', 'lat' : 10, 'lon' :-120, 'alt' : -28.225368684317, 'az' :107.175147833478};
suntest[16] = {'time' : '2020-11-20T12:00:00.000Z', 'lat' : 10, 'lon' :-90, 'alt' : -0.168643089275, 'az' :110.063187466725};
suntest[17] = {'time' : '2020-11-20T12:00:00.000Z', 'lat' : 10, 'lon' :-60, 'alt' : 26.878473297456, 'az' :118.330636399825};
suntest[18] = {'time' : '2020-11-20T12:00:00.000Z', 'lat' : 10, 'lon' :-30, 'alt' : 50.377210160732, 'az' :138.739128733690};
suntest[19] = {'time' : '2020-11-20T12:00:00.000Z', 'lat' : 10, 'lon' :0, 'alt' : 60.029872737499, 'az' :186.514048228677};
suntest[20] = {'time' : '2020-11-20T12:00:00.000Z', 'lat' : 10, 'lon' :30, 'alt' : 45.598015050067, 'az' :227.849125941148};


function drawLine(x1,y1,z1, x2, y2, z2) {
  var origin = new Cesium.Cartesian3(x1,y1,z1);
  var dest = new Cesium.Cartesian3(x2,y2,z2);
 
  viewer.entities.add({
    polyline: {
        positions: [
          origin, 
          dest
          ],
      arcType: Cesium.ArcType.NONE   , 
      width: 20,
      material: new Cesium.PolylineArrowMaterialProperty(Cesium.Color.WHITE)
    }
  });  
}



function createROIfromRotation(position,  rotation, lengthKM) {
  // position: Cartographic - {latitude, longitude, altitude})
  // rotation: HeadingPitchRoll - {heading, pitch, roll}
  
  // Based on answer found here:
  // https://stackoverflow.com/questions/58021985/create-a-point-in-a-direction-in-cesiumjs
  
    var cartesianPosition = Cesium.Ellipsoid.WGS84.cartographicToCartesian(position);

    rotation.heading = rotation.heading - Cesium.Math.toRadians(90);
    var referenceFrame1 = Cesium.Transforms.headingPitchRollQuaternion(cartesianPosition, rotation);
    var rotationMatrix = Cesium.Matrix3.fromQuaternion(referenceFrame1, new Cesium.Matrix3());
    var rotationScaled = Cesium.Matrix3.multiplyByVector(rotationMatrix, new Cesium.Cartesian3(lengthKM * 1000.0, 0, 0), new Cesium.Cartesian3());
    var roiPos = Cesium.Cartesian3.add(cartesianPosition, rotationScaled, new Cesium.Cartesian3());
    return roiPos;
}


function arrowFromTo(latitude, longitude, height, elev, azimut, lengthKM){
  latitude = Cesium.Math.toRadians(latitude);
  longitude = Cesium.Math.toRadians(longitude);
  var origin = new Cesium.Cartographic(longitude, latitude, height);
  var originC3 = new Cesium.Cartographic.toCartesian(origin);

  // Altitude (aka Elevation) and Azimuth can also be seen as Pitch and Heading, with Roll = 0:
  var heading = Cesium.Math.toRadians(azimut);
  var pitch = Cesium.Math.toRadians(elev);
  var roll = 0.0;
  var direction = new Cesium.HeadingPitchRoll(heading, pitch, roll);
 
  
  ////////////
  var result = createROIfromRotation(origin, direction, lengthKM);
  ////////////
  
  drawLine(originC3.x ,originC3.y, originC3.z, result.x ,result.y, result.z);
}



// Bind button to test function:
document.getElementById("btnCalc").addEventListener("click",function(a,b,c) {
  var obsLat = document.getElementById("obsLat").value * 1.0;
  var obsLon = document.getElementById("obsLon").value * 1.0;
  var obsAlt = document.getElementById("obsAlt").value * 1.0;
  var sunAlt = document.getElementById("sunAlt").value * 1.0;
  var sunAz = document.getElementById("sunAz").value * 1.0;
  var userDate = document.getElementById("userDate").value;
  
  viewer.clock.currentTime = Cesium.JulianDate.fromDate(new Date(userDate));
  arrowFromTo(obsLat,obsLon,obsAlt,sunAlt,sunAz,150000000);
            //latitude, longitude, height, elev, azimut, lengthKM
});


///////////////////////
// Draw test lines:
  viewer.clock.currentTime = Cesium.JulianDate.fromDate(new Date(suntest[0].time));
  
  for (var i = 0; i<suntest.length; i++) {
    arrowFromTo(suntest[i].lat, suntest[i].lon, 0, suntest[i].alt, suntest[i].az,150000000 );
  }

  
jumpjack Nov 27 2020 at 16:04

La razón del mal funcionamiento del algoritmo mencionado en la pregunta fueron dos fórmulas incorrectas; la función correcta es:

function CalcPosFromAltAzDist(startPoint, vectorPointing) {

  var ellipsoid = Cesium.Ellipsoid.WGS84;
  var ENU = new Cesium.Matrix4();
  Cesium.Transforms.eastNorthUpToFixedFrame(startPoint,ellipsoid,ENU);
  var myX = vectorPointing.dist * Math.cos(vectorPointing.alt * Math.PI / 180) * Math.sin(vectorPointing.az * Math.PI / 180);
  var myY = vectorPointing.dist * Math.cos(vectorPointing.alt * Math.PI / 180) * Math.cos(vectorPointing.az * Math.PI / 180);
  var myZ = vectorPointing.dist * Math.sin(vectorPointing.alt * Math.PI / 180);
  var offset = new Cesium.Cartesian3(myX,myY,myZ);
  var finalPoint = Cesium.Matrix4.multiplyByPoint(ENU, offset, new Cesium.Cartesian3());
  return finalPoint;
  
}

Faltaba el factor "Math.cos (vectorPointing.alt * Math.PI / 180)" en los cálculos myX y myY.

Ahora todo funciona bien:

Busque aquí un ejemplo de castillo de arena funcional