static int isea_ptdi(ref isea_dgg g, int tri, isea_pt pt, out isea_pt di) { isea_pt v = pt; int quad = isea_ptdd(tri, ref v); quad = isea_dddi(ref g, quad, v, out di); return(quad); }
// convert projected triangle coords to quad xy coords, return quad number static int isea_ptdd(int tri, ref isea_pt pt) { bool downtri = (((tri - 1) / 5) % 2 == 1); int quad = ((tri - 1) % 5) + ((tri - 1) / 10) * 5 + 1; isea_rotate(ref pt, downtri?240.0:60.0); if (downtri) { pt.x += 0.5; // pt->y += cos(30.0 * Math.PI / 180.0); pt.y += 0.86602540378443864672; } return(quad); }
static int isea_tri_plane(int tri, ref isea_pt pt, double radius) { isea_pt tc; // center of triangle if (((tri - 1) / 5) % 2 == 1) { isea_rotate(ref pt, 180.0); } tc = isea_triangle_xy(tri); tc.x *= radius; tc.y *= radius; pt.x += tc.x; pt.y += tc.y; return(tri); }
static int isea_transform(ref isea_dgg g, isea_geo @in, out isea_pt @out) { isea_geo pole; pole.lat = g.o_lat; pole.lon = g.o_lon; isea_geo i = isea_ctran(ref pole, @in, g.o_az); int tri = isea_snyder_forward(i, out @out); @out.x *= g.radius; @out.y *= g.radius; g.triangle = tri; return(tri); }
// Proj 4 integration code follows // spheroid XY s_forward(LP lp) { XY xy; xy.x = xy.y = 0; isea_geo @in; @in.lon = lp.lam; @in.lat = lp.phi; isea_pt @out = isea_forward(dgg, @in); xy.x = @out.x; xy.y = @out.y; return(xy); }
static void isea_rotate(ref isea_pt pt, double degrees) { double rad = -degrees * Math.PI / 180.0; while (rad >= 2.0 * Math.PI) { rad -= 2.0 * Math.PI; } while (rad <= -2.0 * Math.PI) { rad += 2.0 * Math.PI; } double x = pt.x * Math.Cos(rad) + pt.y * Math.Sin(rad); double y = -pt.x * Math.Sin(rad) + pt.y * Math.Cos(rad); pt.x = x; pt.y = y; }
// q2di to seqnum static int isea_disn(ref isea_dgg g, int quad, isea_pt di) { if (quad == 0) { g.serial = 1; return(1); } // hexes in a quad int hexes = (int)(Math.Pow(g.aperture, g.resolution) + 0.5); if (quad == 11) { g.serial = (ulong)(1 + 10 * hexes + 1); return((int)g.serial); } int sn; if (g.aperture == 3 && g.resolution % 2 == 1) { int height = (int)(Math.Pow(g.aperture, (g.resolution - 1) / 2.0)); sn = ((int)di.x) * height; sn += ((int)di.y) / height; sn += (quad - 1) * hexes; sn += 2; } else { int sidelength = (int)(Math.Pow(g.aperture, g.resolution / 2.0) + 0.5); sn = (int)((quad - 1) * hexes + sidelength * di.x + di.y + 2); } g.serial = (ulong)sn; return(sn); }
// coord needs to be in radians static int isea_snyder_forward(isea_geo ll, out isea_pt @out) { // TODO by locality of reference, start by trying the same triangle // as last time // TODO put these constants in as radians to begin with snyder_constants c=constants[(int)snyder_polyhedron.SNYDER_POLY_ICOSAHEDRON]; // plane angle between radius vector to center and adjacent edge of // plane polygon double theta=c.theta*DEG2RAD; // spherical distance from center of polygon face to any of its // vertexes on the globe double g=c.g*DEG2RAD; // spherical angle between radius vector to center and adjacent edge // of spherical polygon on the globe double G=c.G*DEG2RAD; for(int i=1; i<=20; i++) { double z; isea_geo center; center=icostriangles[i]; // step 1 z=Math.Acos(Math.Sin(center.lat)*Math.Sin(ll.lat)+Math.Cos(center.lat)*Math.Cos(ll.lat)*Math.Cos(ll.lon-center.lon)); // not on this triangle if(z>g+0.000005) continue; // TODO DBL_EPSILON double Az=sph_azimuth(center.lon, center.lat, ll.lon, ll.lat); // step 2 // This calculates "some" vertex coordinate double az_offset=az_adjustment(i); Az-=az_offset; // TODO I don't know why we do this. It's not in snyder // maybe because we should have picked a better vertex if(Az<0.0) Az+=2.0*Math.PI; // adjust Az for the point to fall within the range of 0 to // 2(90 - theta) or 60 degrees for the hexagon, by // and therefore 120 degrees for the triangle // of the icosahedron // subtracting or adding multiples of 60 degrees to Az and // recording the amount of adjustment int Az_adjust_multiples=0; // how many multiples of 60 degrees we adjust the azimuth while(Az<0.0) { Az+=DEG120; Az_adjust_multiples--; } while(Az>DEG120+double.Epsilon) { Az-=DEG120; Az_adjust_multiples++; } // step 3 double cot_theta=1.0/Math.Tan(theta); double tan_g=Math.Tan(g); // TODO this is a constant // Calculate q from eq 9. // TODO cot_theta is cot(30) double q=Math.Atan2(tan_g, Math.Cos(Az)+Math.Sin(Az)*cot_theta); // not in this triangle if(z>q+0.000005) continue; // step 4 // Apply equations 5-8 and 10-12 in order // eq 5 // Rprime = 0.9449322893 * R; // R' in the paper is for the truncated double Rprime=0.91038328153090290025; // eq 6 double H=Math.Acos(Math.Sin(Az)*Math.Sin(G)*Math.Cos(g)-Math.Cos(Az)*Math.Cos(G)); // eq 7 // Ag = (Az + G + H - DEG180) * M_PI * R * R / DEG180; double Ag=Az+G+H-DEG180; // eq 8 double Azprime=Math.Atan2(2.0*Ag, Rprime*Rprime*tan_g*tan_g-2.0*Ag*cot_theta); // eq 10 // cot(theta) = 1.73205080756887729355 double dprime=Rprime*tan_g/(Math.Cos(Azprime)+Math.Sin(Azprime)*cot_theta); // eq 11 double f=dprime/(2.0*Rprime*Math.Sin(q/2.0)); // eq 12 double rho=2.0*Rprime*f*Math.Sin(z/2.0); // add back the same 60 degree multiple adjustment from step // 2 to Azprime Azprime+=DEG120*Az_adjust_multiples; // calculate rectangular coordinates double x=rho*Math.Sin(Azprime); double y=rho*Math.Cos(Azprime); // TODO // translate coordinates to the origin for the particular // hexagon on the flattened polyhedral map plot @out.x=x; @out.y=y; return i; } // should be impossible, this implies that the coordinate is not on // any triangle //fprintf(stderr, "impossible transform: %f %f is not on any triangle\n", // ll.lon*RAD2DEG, ll.lat*RAD2DEG); throw new Exception(); }
static int isea_ptdi(ref isea_dgg g, int tri, isea_pt pt, out isea_pt di) { isea_pt v=pt; int quad=isea_ptdd(tri, ref v); quad=isea_dddi(ref g, quad, v, out di); return quad; }
static void isea_rotate(ref isea_pt pt, double degrees) { double rad=-degrees*Math.PI/180.0; while(rad>=2.0*Math.PI) rad-=2.0*Math.PI; while(rad<=-2.0*Math.PI) rad+=2.0*Math.PI; double x=pt.x*Math.Cos(rad)+pt.y*Math.Sin(rad); double y=-pt.x*Math.Sin(rad)+pt.y*Math.Cos(rad); pt.x=x; pt.y=y; }
// TODO just encode the quad in the d or i coordinate // quad is 0-11, which can be four bits. // d' = d << 4 + q, d = d' >> 4, q = d' & 0xf // convert a q2di to global hex coord static int isea_hex(ref isea_dgg g, int tri, isea_pt pt, out isea_pt hex) { isea_pt v; int quad=isea_ptdi(ref g, tri, pt, out v); hex.x=((int)v.x<<4)+quad; hex.y=v.y; return 1; // silence the compiler #if false double d=v.x; double i=v.y; // Aperture 3 odd resolutions if(g.aperture==3&&g.resolution%2!=0) { int offset=(int)(Math.Pow(3.0, g.resolution-1)+0.5); d+=offset*((g.quad-1)%5); i+=offset*((g.quad-1)%5); if(quad==0) { d=0; i=offset; } else if(quad==11) { d=2*offset; i=0; } else if(quad>5) { d+=offset; } double x=(2*d-i)/3; double y=(2*i-d)/3; hex.x=x+offset/3; hex.y=y+2*offset/3; return 1; } // aperture 3 even resolutions and aperture 4 double sidelength=(int)(Math.Pow(g.aperture, g.resolution/2.0)+0.5); if(g.quad==0) { hex.x=0; hex.y=sidelength; } else if(g.quad==11) { hex.x=sidelength*2; hex.y=0; } else { hex.x=d+sidelength*((g.quad-1)%5); if(g.quad>5) hex.x+=sidelength; hex.y=i+sidelength*((g.quad-1)%5); } return 1; #endif }
// convert projected triangle coords to quad xy coords, return quad number static int isea_ptdd(int tri, ref isea_pt pt) { bool downtri=(((tri-1)/5)%2==1); int quad=((tri-1)%5)+((tri-1)/10)*5+1; isea_rotate(ref pt, downtri?240.0:60.0); if(downtri) { pt.x+=0.5; // pt->y += cos(30.0 * Math.PI / 180.0); pt.y+=0.86602540378443864672; } return quad; }
static int isea_dddi_ap3odd(ref isea_dgg g, int quad, isea_pt pt, out isea_pt di) { // This is the number of hexes from apex to base of a triangle double sidelength=(Math.Pow(2.0, g.resolution)+1.0)/2.0; // in hexes // apex to base is cos(30deg) double hexwidth=Math.Cos(Math.PI/6.0)/sidelength; // TODO I think sidelength is always x.5, so // (int)sidelength * 2 + 1 might be just as good int maxcoord=(int)(sidelength*2.0+0.5); isea_pt v=pt; hex h; h.z=0; hexbin2(hexwidth, v.x, v.y, out h.x, out h.y); h.iso=false; hex_iso(ref h); int d=h.x-h.z; int i=h.x+h.y+h.y; // you want to test for max coords for the next quad in the same // "row" first to get the case where both are max if(quad<=5) { if(d==0&&i==maxcoord) { // north pole quad=0; d=0; i=0; } else if(i==maxcoord) { // upper right in next quad quad+=1; if(quad==6) quad=1; i=maxcoord-d; d=0; } else if(d==maxcoord) { // lower right in quad to lower right quad+=5; d=0; } } else if(quad>=6) { if(i==0&&d==maxcoord) { // south pole quad=11; d=0; i=0; } else if(d==maxcoord) { // lower right in next quad quad+=1; if(quad==11) quad=6; d=maxcoord-i; i=0; } else if(i==maxcoord) { // upper right in quad to upper right quad=(quad-4)%5; i=0; } } di.x=d; di.y=i; g.quad=quad; return quad; }
// q2di to seqnum static int isea_disn(ref isea_dgg g, int quad, isea_pt di) { if(quad==0) { g.serial=1; return 1; } // hexes in a quad int hexes=(int)(Math.Pow(g.aperture, g.resolution)+0.5); if(quad==11) { g.serial=(ulong)(1+10*hexes+1); return (int)g.serial; } int sn; if(g.aperture==3&&g.resolution%2==1) { int height=(int)(Math.Pow(g.aperture, (g.resolution-1)/2.0)); sn=((int)di.x)*height; sn+=((int)di.y)/height; sn+=(quad-1)*hexes; sn+=2; } else { int sidelength=(int)(Math.Pow(g.aperture, g.resolution/2.0)+0.5); sn=(int)((quad-1)*hexes+sidelength*di.x+di.y+2); } g.serial=(ulong)sn; return sn; }
static int isea_dddi(ref isea_dgg g, int quad, isea_pt pt, out isea_pt di) { if(g.aperture==3&&g.resolution%2!=0) return isea_dddi_ap3odd(ref g, quad, pt, out di); // todo might want to do this as an iterated loop int sidelength; // in hexes if(g.aperture>0) sidelength=(int)(Math.Pow(g.aperture, g.resolution/2.0)+0.5); else sidelength=g.resolution; double hexwidth=1.0/sidelength; isea_pt v=pt; hex h; h.z=0; isea_rotate(ref v, -30.0); hexbin2(hexwidth, v.x, v.y, out h.x, out h.y); h.iso=false; hex_iso(ref h); // we may actually be on another quad if(quad<=5) { if(h.x==0&&h.z==-sidelength) { // north pole quad=0; h.z=0; h.y=0; h.x=0; } else if(h.z==-sidelength) { quad=quad+1; if(quad==6) quad=1; h.y=sidelength-h.x; h.z=h.x-sidelength; h.x=0; } else if(h.x==sidelength) { quad+=5; h.y=-h.z; h.x=0; } } else if(quad>=6) { if(h.z==0&&h.x==sidelength) { // south pole quad=11; h.x=0; h.y=0; h.z=0; } else if(h.x==sidelength) { quad=quad+1; if(quad==11) quad=6; h.x=h.y+sidelength; h.y=0; h.z=-h.x; } else if(h.y==-sidelength) { quad-=4; h.y=0; h.z=-h.x; } } di.x=h.x; di.y=-h.z; g.quad=quad; return quad; }
// coord needs to be in radians static int isea_snyder_forward(isea_geo ll, out isea_pt @out) { // TODO by locality of reference, start by trying the same triangle // as last time // TODO put these constants in as radians to begin with snyder_constants c = constants[(int)snyder_polyhedron.SNYDER_POLY_ICOSAHEDRON]; // plane angle between radius vector to center and adjacent edge of // plane polygon double theta = c.theta * DEG2RAD; // spherical distance from center of polygon face to any of its // vertexes on the globe double g = c.g * DEG2RAD; // spherical angle between radius vector to center and adjacent edge // of spherical polygon on the globe double G = c.G * DEG2RAD; for (int i = 1; i <= 20; i++) { double z; isea_geo center; center = icostriangles[i]; // step 1 z = Math.Acos(Math.Sin(center.lat) * Math.Sin(ll.lat) + Math.Cos(center.lat) * Math.Cos(ll.lat) * Math.Cos(ll.lon - center.lon)); // not on this triangle if (z > g + 0.000005) { continue; // TODO DBL_EPSILON } double Az = sph_azimuth(center.lon, center.lat, ll.lon, ll.lat); // step 2 // This calculates "some" vertex coordinate double az_offset = az_adjustment(i); Az -= az_offset; // TODO I don't know why we do this. It's not in snyder // maybe because we should have picked a better vertex if (Az < 0.0) { Az += 2.0 * Math.PI; } // adjust Az for the point to fall within the range of 0 to // 2(90 - theta) or 60 degrees for the hexagon, by // and therefore 120 degrees for the triangle // of the icosahedron // subtracting or adding multiples of 60 degrees to Az and // recording the amount of adjustment int Az_adjust_multiples = 0; // how many multiples of 60 degrees we adjust the azimuth while (Az < 0.0) { Az += DEG120; Az_adjust_multiples--; } while (Az > DEG120 + double.Epsilon) { Az -= DEG120; Az_adjust_multiples++; } // step 3 double cot_theta = 1.0 / Math.Tan(theta); double tan_g = Math.Tan(g); // TODO this is a constant // Calculate q from eq 9. // TODO cot_theta is cot(30) double q = Math.Atan2(tan_g, Math.Cos(Az) + Math.Sin(Az) * cot_theta); // not in this triangle if (z > q + 0.000005) { continue; } // step 4 // Apply equations 5-8 and 10-12 in order // eq 5 // Rprime = 0.9449322893 * R; // R' in the paper is for the truncated double Rprime = 0.91038328153090290025; // eq 6 double H = Math.Acos(Math.Sin(Az) * Math.Sin(G) * Math.Cos(g) - Math.Cos(Az) * Math.Cos(G)); // eq 7 // Ag = (Az + G + H - DEG180) * M_PI * R * R / DEG180; double Ag = Az + G + H - DEG180; // eq 8 double Azprime = Math.Atan2(2.0 * Ag, Rprime * Rprime * tan_g * tan_g - 2.0 * Ag * cot_theta); // eq 10 // cot(theta) = 1.73205080756887729355 double dprime = Rprime * tan_g / (Math.Cos(Azprime) + Math.Sin(Azprime) * cot_theta); // eq 11 double f = dprime / (2.0 * Rprime * Math.Sin(q / 2.0)); // eq 12 double rho = 2.0 * Rprime * f * Math.Sin(z / 2.0); // add back the same 60 degree multiple adjustment from step // 2 to Azprime Azprime += DEG120 * Az_adjust_multiples; // calculate rectangular coordinates double x = rho * Math.Sin(Azprime); double y = rho * Math.Cos(Azprime); // TODO // translate coordinates to the origin for the particular // hexagon on the flattened polyhedral map plot @out.x = x; @out.y = y; return(i); } // should be impossible, this implies that the coordinate is not on // any triangle //fprintf(stderr, "impossible transform: %f %f is not on any triangle\n", // ll.lon*RAD2DEG, ll.lat*RAD2DEG); throw new Exception(); }
static int isea_transform(ref isea_dgg g, isea_geo @in, out isea_pt @out) { isea_geo pole; pole.lat=g.o_lat; pole.lon=g.o_lon; isea_geo i=isea_ctran(ref pole, @in, g.o_az); int tri=isea_snyder_forward(i, out @out); @out.x*=g.radius; @out.y*=g.radius; g.triangle=tri; return tri; }
static int isea_dddi(ref isea_dgg g, int quad, isea_pt pt, out isea_pt di) { if (g.aperture == 3 && g.resolution % 2 != 0) { return(isea_dddi_ap3odd(ref g, quad, pt, out di)); } // todo might want to do this as an iterated loop int sidelength; // in hexes if (g.aperture > 0) { sidelength = (int)(Math.Pow(g.aperture, g.resolution / 2.0) + 0.5); } else { sidelength = g.resolution; } double hexwidth = 1.0 / sidelength; isea_pt v = pt; hex h; h.z = 0; isea_rotate(ref v, -30.0); hexbin2(hexwidth, v.x, v.y, out h.x, out h.y); h.iso = false; hex_iso(ref h); // we may actually be on another quad if (quad <= 5) { if (h.x == 0 && h.z == -sidelength) { // north pole quad = 0; h.z = 0; h.y = 0; h.x = 0; } else if (h.z == -sidelength) { quad = quad + 1; if (quad == 6) { quad = 1; } h.y = sidelength - h.x; h.z = h.x - sidelength; h.x = 0; } else if (h.x == sidelength) { quad += 5; h.y = -h.z; h.x = 0; } } else if (quad >= 6) { if (h.z == 0 && h.x == sidelength) { // south pole quad = 11; h.x = 0; h.y = 0; h.z = 0; } else if (h.x == sidelength) { quad = quad + 1; if (quad == 11) { quad = 6; } h.x = h.y + sidelength; h.y = 0; h.z = -h.x; } else if (h.y == -sidelength) { quad -= 4; h.y = 0; h.z = -h.x; } } di.x = h.x; di.y = -h.z; g.quad = quad; return(quad); }
static int isea_dddi_ap3odd(ref isea_dgg g, int quad, isea_pt pt, out isea_pt di) { // This is the number of hexes from apex to base of a triangle double sidelength = (Math.Pow(2.0, g.resolution) + 1.0) / 2.0; // in hexes // apex to base is cos(30deg) double hexwidth = Math.Cos(Math.PI / 6.0) / sidelength; // TODO I think sidelength is always x.5, so // (int)sidelength * 2 + 1 might be just as good int maxcoord = (int)(sidelength * 2.0 + 0.5); isea_pt v = pt; hex h; h.z = 0; hexbin2(hexwidth, v.x, v.y, out h.x, out h.y); h.iso = false; hex_iso(ref h); int d = h.x - h.z; int i = h.x + h.y + h.y; // you want to test for max coords for the next quad in the same // "row" first to get the case where both are max if (quad <= 5) { if (d == 0 && i == maxcoord) { // north pole quad = 0; d = 0; i = 0; } else if (i == maxcoord) { // upper right in next quad quad += 1; if (quad == 6) { quad = 1; } i = maxcoord - d; d = 0; } else if (d == maxcoord) { // lower right in quad to lower right quad += 5; d = 0; } } else if (quad >= 6) { if (i == 0 && d == maxcoord) { // south pole quad = 11; d = 0; i = 0; } else if (d == maxcoord) { // lower right in next quad quad += 1; if (quad == 11) { quad = 6; } d = maxcoord - i; i = 0; } else if (i == maxcoord) { // upper right in quad to upper right quad = (quad - 4) % 5; i = 0; } } di.x = d; di.y = i; g.quad = quad; return(quad); }
static int isea_tri_plane(int tri, ref isea_pt pt, double radius) { isea_pt tc; // center of triangle if(((tri-1)/5)%2==1) isea_rotate(ref pt, 180.0); tc=isea_triangle_xy(tri); tc.x*=radius; tc.y*=radius; pt.x+=tc.x; pt.y+=tc.y; return tri; }
// TODO just encode the quad in the d or i coordinate // quad is 0-11, which can be four bits. // d' = d << 4 + q, d = d' >> 4, q = d' & 0xf // convert a q2di to global hex coord static int isea_hex(ref isea_dgg g, int tri, isea_pt pt, out isea_pt hex) { isea_pt v; int quad = isea_ptdi(ref g, tri, pt, out v); hex.x = ((int)v.x << 4) + quad; hex.y = v.y; return(1); // silence the compiler #if false double d = v.x; double i = v.y; // Aperture 3 odd resolutions if (g.aperture == 3 && g.resolution % 2 != 0) { int offset = (int)(Math.Pow(3.0, g.resolution - 1) + 0.5); d += offset * ((g.quad - 1) % 5); i += offset * ((g.quad - 1) % 5); if (quad == 0) { d = 0; i = offset; } else if (quad == 11) { d = 2 * offset; i = 0; } else if (quad > 5) { d += offset; } double x = (2 * d - i) / 3; double y = (2 * i - d) / 3; hex.x = x + offset / 3; hex.y = y + 2 * offset / 3; return(1); } // aperture 3 even resolutions and aperture 4 double sidelength = (int)(Math.Pow(g.aperture, g.resolution / 2.0) + 0.5); if (g.quad == 0) { hex.x = 0; hex.y = sidelength; } else if (g.quad == 11) { hex.x = sidelength * 2; hex.y = 0; } else { hex.x = d + sidelength * ((g.quad - 1) % 5); if (g.quad > 5) { hex.x += sidelength; } hex.y = i + sidelength * ((g.quad - 1) % 5); } return(1); #endif }