Deriving an ECDSA uncompressed public key from a compressed one - python

I am currently trying to derive a Bitcoin uncompressed ECDSA public key from a compressed one.
According to this link on the Bitcoin wiki, it is possible to do so... But how?
To give you more details: as of now I have compressed keys (33-bytes-long) gathered on the bitcoin network.
They are of the following format: <1-byte-long prefix><32-bytes-long X>.
From there, I would like to obtain an uncompressed key (65-bytes-long) whose format is:
<1-byte-long prefix><32-bytes-long X><32-bytes-long Y>
According to this other link on the Bitcoin wiki, it should be as easy as solving the equation:
Y^2 = X^3 + 7
However, I cannot seem to get there. My value for Y is simply far-off. Here is my code (the value for the public key come from the Bitcoin wiki example):
import binascii
from decimal import *
expected_uncompressed_key_hex = '0450863AD64A87AE8A2FE83C1AF1A8403CB53F53E486D8511DAD8A04887E5B23522CD470243453A299FA9E77237716103ABC11A1DF38855ED6F2EE187E9C582BA6'
expected_y_hex = expected_uncompressed_key_hex[-64:]
expected_y_dec = int(expected_y_hex, 16)
x_hex = expected_uncompressed_key_hex[2:66]
if expected_y_dec % 2 == 0:
prefix = "02"
else:
prefix = "03"
artificial_compressed_key = prefix + x_hex
getcontext().prec = 500
test_dec = Decimal(int(x_hex, 16))
y_square_dec = test_dec**3 + 7
if prefix == "02":
y_dec = - Decimal(y_square_dec).sqrt()
else:
y_dec = Decimal(y_square_dec).sqrt()
computed_y_hex = hex(int(y_dec))
computed_uncompressed_key = "04" + x + computed_y_hex
For information, my outputs are:
computed_y_hex = '0X2D29684BD207BF6D809F7D0EB78E4FD61C3C6700E88AB100D1075EFA8F8FD893080F35E6C7AC2E2214F8F4D088342951'
expected_y_hex = '2CD470243453A299FA9E77237716103ABC11A1DF38855ED6F2EE187E9C582BA6'
Thank you for your help!

You need to calculate in the field , which mostly means that you have to reduce your number to the remainder after dividing with p after each calculation. Calculating this is called taking the modulo and is written as % p in python.
Exponentiating in this field can be done more effectively than the naive way of just multiplying and reducing many times. This is called modular exponentiation. Python's built-in exponentation function pow(n,e,p) can take care of this.
The remaining problem is to find the square root. Luckily secp256k1 is chosen in a special way (), so that taking square roots is easy: A square root of x is .
So a simplified version of your code becomes:
import binascii
p_hex = 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F'
p = int(p_hex, 16)
compressed_key_hex = '0250863AD64A87AE8A2FE83C1AF1A8403CB53F53E486D8511DAD8A04887E5B2352'
x_hex = compressed_key_hex[2:66]
x = int(x_hex, 16)
prefix = compressed_key_hex[0:2]
y_square = (pow(x, 3, p) + 7) % p
y_square_square_root = pow(y_square, (p+1)/4, p)
if (prefix == "02" and y_square_square_root & 1) or (prefix == "03" and not y_square_square_root & 1):
y = (-y_square_square_root) % p
else:
y = y_square_square_root
computed_y_hex = format(y, '064x')
computed_uncompressed_key = "04" + x_hex + computed_y_hex
print computed_uncompressed_key

Here a sample code without any 3rd party python libs:
def pow_mod(x, y, z):
"Calculate (x ** y) % z efficiently."
number = 1
while y:
if y & 1:
number = number * x % z
y >>= 1
x = x * x % z
return number
# prime p = 2^256 - 2^32 - 2^9 - 2^8 - 2^7 - 2^6 - 2^4 - 1
p = 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f
# bitcoin's compressed public key of private key 55255657523dd1c65a77d3cb53fcd050bf7fc2c11bb0bb6edabdbd41ea51f641
compressed_key = '0314fc03b8df87cd7b872996810db8458d61da8448e531569c8517b469a119d267'
y_parity = int(compressed_key[:2]) - 2
x = int(compressed_key[2:], 16)
a = (pow_mod(x, 3, p) + 7) % p
y = pow_mod(a, (p+1)//4, p)
if y % 2 != y_parity:
y = -y % p
uncompressed_key = '04{:x}{:x}'.format(x, y)
print(uncompressed_key)
# should get 0414fc03b8df87cd7b872996810db8458d61da8448e531569c8517b469a119d267be5645686309c6e6736dbd93940707cc9143d3cf29f1b877ff340e2cb2d259cf
refer to bitcoin talk: https://bitcointalk.org/index.php?topic=644919.0

The field of the elliptic curve is not over the field of real numbers. It's over a finite field modulo some prime.
For Secp256k1 the prime p = 2^256 - 2^32 - 2^9 - 2^8 - 2^7 - 2^6 - 2^4 - 1.
Thus: y^2= (x^3) + 7 (mod p)
There's no direct way to solve the equation, you would need to use Cipolla's algorithm: https://en.wikipedia.org/wiki/Cipolla%27s_algorithm

I know that this question has been answered and I actually benefited from this answer, so thank you. The problem is that I found these answers 3 times while looking for the same solution in C# and I don't really code in python :). So for anybody trying to solve this here is a C# solution, have fun! :) (It uses BouncyCastle Library).
using System;
using System.Collections.Generic;
using System.Linq;
using MoreLinq;
using NBitcoin;
using Org.BouncyCastle.Asn1.X9;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Math;
using Org.BouncyCastle.Math.EC;
namespace BitcoinPublicKeyDecompression
{
public class Program
{
public static void Main()
{
const string cPubKey = "0250863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b2352";
var uPubKey = cPubKey.ToHexByteArray().BitcoinDecompressPublicKey().ToHexString();
var expectedUPubKey = new PubKey(cPubKey).Decompress().ToString();
Console.WriteLine($"Public Key:\n\n{cPubKey}\n\nhas been {(uPubKey == expectedUPubKey ? "correctly" : "incorrectly")} decompressed to:\n\n{uPubKey}");
Console.WriteLine("\nPress any key to quit...");
Console.ReadKey();
}
}
public static class Extensions
{
public static readonly byte[] EmptyByteArray = new byte[0];
public static byte[] BitcoinDecompressPublicKey(this byte[] bPubC)
{
var ecPubKey = bPubC.BitcoinCompressedPublicKeyToECPublicKey();
return ecPubKey.ToBitcoinUncompressedPublicKey();
}
public static ECPublicKeyParameters BitcoinCompressedPublicKeyToECPublicKey(this byte[] bPubC)
{
var pubKey = bPubC.Skip(1).ToArray();
var curve = ECNamedCurveTable.GetByName("secp256k1");
var domainParams = new ECDomainParameters(curve.Curve, curve.G, curve.N, curve.H, curve.GetSeed());
var yParity = new BigInteger(bPubC.Take(1).ToArray()).Subtract(BigInteger.Two);
var x = new BigInteger(1, pubKey);
var p = ((FpCurve)curve.Curve).Q;
var a = x.ModPow(new BigInteger("3"), p).Add(new BigInteger("7")).Mod(p);
var y = a.ModPow(p.Add(BigInteger.One).FloorDivide(new BigInteger("4")), p);
if (!y.Mod(BigInteger.Two).Equals(yParity))
y = y.Negate().Mod(p);
var q = curve.Curve.CreatePoint(x, y);
return new ECPublicKeyParameters(q, domainParams);
}
public static byte[] ToBitcoinUncompressedPublicKey(this AsymmetricKeyParameter ecPublicKey)
{
var publicKey = ((ECPublicKeyParameters)ecPublicKey).Q;
var xs = publicKey.AffineXCoord.ToBigInteger().ToByteArrayUnsigned().PadStart(32);
var ys = publicKey.AffineYCoord.ToBigInteger().ToByteArrayUnsigned().PadStart(32);
return new byte[] { 0x04 }.ConcatMany(xs, ys).ToArray();
}
public static BigInteger FloorDivide(this BigInteger a, BigInteger b)
{
if (a.CompareTo(BigInteger.Zero) > 0 ^ b.CompareTo(BigInteger.Zero) < 0 && !a.Mod(b).Equals(BigInteger.Zero))
return a.Divide(b).Subtract(BigInteger.One);
return a.Divide(b);
}
public static byte[] ToHexByteArray(this string str)
{
byte[] bytes;
if (string.IsNullOrEmpty(str))
bytes = EmptyByteArray;
else
{
var string_length = str.Length;
var character_index = str.StartsWith("0x", StringComparison.Ordinal) ? 2 : 0;
var number_of_characters = string_length - character_index;
var add_leading_zero = false;
if (0 != number_of_characters % 2)
{
add_leading_zero = true;
number_of_characters += 1;
}
bytes = new byte[number_of_characters / 2];
var write_index = 0;
if (add_leading_zero)
{
bytes[write_index++] = CharacterToByte(str[character_index], character_index);
character_index += 1;
}
for (var read_index = character_index; read_index < str.Length; read_index += 2)
{
var upper = CharacterToByte(str[read_index], read_index, 4);
var lower = CharacterToByte(str[read_index + 1], read_index + 1);
bytes[write_index++] = (byte)(upper | lower);
}
}
return bytes;
}
public static byte CharacterToByte(char character, int index, int shift = 0)
{
var value = (byte)character;
if (0x40 < value && 0x47 > value || 0x60 < value && 0x67 > value)
{
if (0x40 != (0x40 & value))
return value;
if (0x20 == (0x20 & value))
value = (byte)((value + 0xA - 0x61) << shift);
else
value = (byte)((value + 0xA - 0x41) << shift);
}
else if (0x29 < value && 0x40 > value)
value = (byte)((value - 0x30) << shift);
else
throw new InvalidOperationException($"Character '{character}' at index '{index}' is not valid alphanumeric character.");
return value;
}
public static string ToHexString(this byte[] value, bool prefix = false)
{
var strPrex = prefix ? "0x" : "";
return strPrex + string.Concat(value.Select(b => b.ToString("x2")).ToArray());
}
public static IEnumerable<T> ConcatMany<T>(this IEnumerable<T> enumerable, params IEnumerable<T>[] enums)
{
return enumerable.Concat(enums.SelectMany(x => x));
}
}
}
Result:

Related

PySide2 Qt Surface Example

I would like to reimplement the Qt C++ "Surface" example (Q3DSurface) in PySide2 but QSurfaceDataArray and QSurfaceDataRow are not available.
void SurfaceGraph::fillSqrtSinProxy()
{
float stepX = (sampleMax - sampleMin) / float(sampleCountX - 1);
float stepZ = (sampleMax - sampleMin) / float(sampleCountZ - 1);
QSurfaceDataArray *dataArray = new QSurfaceDataArray;
dataArray->reserve(sampleCountZ);
for (int i = 0 ; i < sampleCountZ ; i++) {
QSurfaceDataRow *newRow = new QSurfaceDataRow(sampleCountX);
// Keep values within range bounds, since just adding step can cause minor drift due
// to the rounding errors.
float z = qMin(sampleMax, (i * stepZ + sampleMin));
int index = 0;
for (int j = 0; j < sampleCountX; j++) {
float x = qMin(sampleMax, (j * stepX + sampleMin));
float R = qSqrt(z * z + x * x) + 0.01f;
float y = (qSin(R) / R + 0.24f) * 1.61f;
(*newRow)[index++].setPosition(QVector3D(x, y, z));
}
*dataArray << newRow;
}
m_sqrtSinProxy->resetArray(dataArray);
}
Is there are way to use a QVector<QSurfaceDataItem> in PySide2?
from PySide2.QtDataVisualization import QtDataVisualization as QDV
data_item = QDV.QSurfaceDataItem()
data_item.setPosition(QVector3D(x, y, z))
The QSurfaceDataItem is available but I can't pass the objects to QSurfaceDataProxy without QVector.

Do C++ and Python write to disk differently?

So, a few years ago I wrote a program in Python that writes the English names of a sequential list of numbers to a file (one, two, three, etc.). I have been working on getting a C++ version working off and on for the last month (personal project), and I think I have it running pretty well. One problem: it's five times slower than the Python version. I've tried switching string concatenation methods ( << vs operator+ vs operator+= vs .append()), using fprinf() instead of ofstream, pre-allocating the string size (.reserve()), and a lot of other things I can't remember, but I seemed to have run into a wall. Then I noticed that the C++ writing speed seems to max out around 70MB/s, whereas the Python version writes at around 350MB/s. The drive I am using is a 5400rpm disk (CrystalDiskMark gives a sequential write speed of 60-90 MB/s), so the C++ write speeds are believable, but the Python?
TL;DR: Python seems to be writing five times faster than possible, (near to the read speeds!) of the disk.
I've included the programs below, in case I am missing something obvious (plausible). "Benchmarking" involved running each program for the numbers 1-1,000,000, resulting in a file of 50,824KB. ~50s for C++, ~8.5s for Python.
Python:
##Code in Python version 2.7.5 for generating a file with the English names of a set range of numbers.
while 1:
s=input("Starting_Value:")
f=input("Ending_Value:")
filename=raw_input("Desired Name of File:")
##dictionary
one=["","one","two","three","four","five","six","seven","eight","nine","error_one"]
one2=["","-one","-two","-three","-four","-five","-six","-seven","-eight","-nine","error_one2"]
teen=["ten","eleven","twelve","thirteen","fourteen","fifteen","sixteen","seventeen","eighteen","nineteen","error_teen"]
ten=["","twenty","thirty","fourty","fifty","sixty","seventy","eighty","ninety","error_ten"]
##Functions
def translate(n): ##handles '' to 999
work=[]
if n>=100:
work.append(one[int(n/100)]+" hundred ")
n=n-(100*int(n/100))
if n>=10:
if n>=20:
work.append(ten[int(n/10)-1]+one2[n-(10*int(n/10))]+" ")
else:
work.append(teen[n%10]+" ")
elif n>=1:
work.append(one[n]+ " ")
elif n>=10:
if n>=20:
work.append(ten[int(n/10)-1]+one2[n-(10*int(n/10))]+" ")
else:
work.append(teen[n%10]+" ")
elif n>=1:
work.append(str(one[n])+" ")
end1=', '.join(work)
end1=end1.replace(", ","")
return end1
def english(m): ##handles billions, hundred millions, millions, hundred thousands, thousands
work2=[]
if m>=1000000000:
work2.append(str(translate(int(m/1000000000)))+"billion ")
elif m>=1000000:
work2.append(str(translate(int(m/1000000)))+"million "+str(translate(int(m-(1000000*int(m/1000000)))/1000))+"thousand "+str(translate(m-(1000*int(m/1000)))))
if ((int(m/1000)%1000)==0):
end3=str(', '.join(work2))
end4=end3.replace("thousand ", "")
work2[:]=[]
work2=[str(end4)]
else:
end3=str()
elif m>=1000:
work2.append(str(translate(int(m/1000)))+"thousand "+str(translate(m%1000)))
elif m>=1:
work2.append(translate(m))
end2=str(', '.join(work2))
end2=end2[:-1]
return end2
##Main Program - Iterator
file = open(str(filename), "a")
for i in range(f-s+1):
file.write(str(english(s))+", ")
s = s + 1
file.close()
a = raw_input("Close window to EXIT, or press ENTER to generate another file")
C++
//Generates a file of sequential numbers in English
//libraries
#include <iostream> //for output to terminal
#include <fstream> //for output to file
#include <string> //for handling strings
using namespace std; //yes
ofstream fout; //for convenience with 'cout'
//function prototypes
string thousands(int n); //translates 1 to 999
string millions(int m); //translates the hundred thousands, millions,
hundred millions
int lint(int j, int k); //outputs the digits of a number greater than the kth place i.e. lint(123456, 1000) = 123
//variables
int shi = 0; //starting value
int kut = 1; //ending value
int i = 0; //iterator
string fname = ""; //filename
string buffern = ""; //buffer for thousands
string bufferm = ""; //buffer for millions
string bufferf = ""; //first digit buffer
//dictionary
char one[10][7] = { ""," one"," two"," three"," four"," five"," six"," seven"," eight"," nine" };
char one2[10][7] = { "","-one","-two","-three","-four","-five","-six","-seven","-eight","-nine" };
char teen[10][11] = { " ten"," eleven"," twelve"," thirteen"," fourteen"," fifteen"," sixteen"," seventeen"," eighteen"," nineteen" };
char ten[10][9] = { "",""," twenty"," thirty"," fourty"," fifty"," sixty"," seventy"," eighty"," ninety" };
//main function
int main()
{
while (1)
{
//get user input
cout << " Starting Number: ";
cin >> shi;
cout << " Ending Number: ";
cin >> kut;
while (fout.is_open() != 1)
{
cout << " Desired Filename: ";
cin >> fname;
fname.append(".txt");
fout.open(fname);
if (fout.is_open() != 1)
cout << "\n Invalid file name. Please try again.\n";
}
//translate and write to file
if (shi == 0) { //handles starting at zero
fout << "zero,";
shi = 1;
}
else //handles spacing for next word
{
bufferf = millions(shi);
bufferf.erase(0, 1);
bufferf += ",";
fout << bufferf;
shi++;
}
for (i = shi; i < (kut); ++i) //Main Iterator
{
fout << millions(i) << ",";
}
fout << millions(kut) << "."; //handles last word
fout.close();
//display confirmation and prompt to exit/continue
cout << "\n Complete\n";
cin.get();
cin.ignore();
cout << endl;
}
}
//function definitions
string thousands(int n) //writes '' to 999
{
buffern = "";
buffern.reserve(30);
if (n >= 100) { //write hundreds place
buffern += one[lint(n, 100)];
buffern += " hundred";
n = n % 100;
if (n >= 10) { //write tens place
if (n >= 20) {
buffern += ten[lint(n, 10)];
buffern += one2[n % 10];
}
else { //deal with 'teens'
buffern += teen[n % 10];
}
}
else if (n >= 1) { //write ones place
buffern += one[n % 10];
}
}
else if (n >= 10) { //write tens place
if (n >= 20) {
buffern += ten[lint(n, 10)];
buffern += one2[n % 10];
}
else { //deal with 'teens'
buffern += teen[n % 10];
}
}
else if (n >= 1) { //write ones place
buffern += one[n];
}
return buffern;
}
string millions(int m)
{
bufferm = "";
bufferm.reserve(100);
if (m >= 1000000)
{
if (int(m / 1000) % 1000 == 0) { //if xxx,000,xxx
bufferm += thousands(lint(m, 1000000));
bufferm += " million";
bufferm += thousands(m % 1000);
}
else {
bufferm += thousands(lint(m, 1000000)); //millions
bufferm += " million";
bufferm += thousands(lint(m, 1000) % 1000); //hundred thousands
bufferm += " thousand";
bufferm += thousands(m % 1000); //thousands
}
}
else if (m >= 1000) {
bufferm += thousands(lint(m, 1000)); //hundred thousands
bufferm += " thousand";
bufferm += thousands(m % 1000); //thousands
}
else if (m >= 1) {
bufferm += thousands(m); //thousands
}
return bufferm;
}
int lint(int j, int k)
{
return ((j - (j % k)) / k);
}
I would appreciate any insights as to why the programs are running at different speeds, how the Python is writing so fast, or suggestions on speeding up the c++ code.
Edit: #VTT was right, not all of the C++ code was there. Added.

Convert Eigen to numpy matrix

I have the following code in C++ that uses the Eigen library, need help to translate to python (numpy)
Initialization
double b = 20.0;
Eigen::Vector3d C(1.0/10.2, 1.0/10.2, 1/30);
Eigen::MatrixXd U(5200, 3);
int i = 0;
for (double x = 10.2/2.0; x < 200; x += 10) {
for (double y = 10.2/2.0; y < 200; y += 10) {
for (double t = 0; t <= 360; t += 30) {
U(i, 0) = x;
U(i, 1) = y;
U(i, 2) = psi;
i += 1;
}
}
}
Function:
Eigen::VectorXd operator()(const Eigen::VectorXd& s) {
Eigen::VectorXd p(length());
p(0) = s[0];
p(1) = s[1];
p(2) = s[2];
p(3) = s[3];
for (int i = 0; i < U.rows(); i++) {
p(i + 4) = b*exp(-0.5*(s.tail(U.cols()) - U.row(i).transpose()).dot(C*(s.tail(U.cols())
- U.row(i).transpose())));
if (p(i + 4) < 0.1) {
p(i + 4) = 0;
}
}
return p;
}
Python version
Initialization:
my_x = 10.2/2.0
my_y = 10.2/2.0
my_p = 0
xx = []
while my_x < 200:
xx.append(my_x)
my_x += 10
yy = []
while my_y < 200:
yy.append(my_y)
my_y += 10
pps = []
while my_psi <= 360:
pps.append(my_p)
my_p+=30
U =[]
for x in xx:
for y in yy:
for p in pps:
U.append([x,y,p])
U = numpy.matrix(U)
C = numpy.array([1.0/10.2, 1.0/10.2, 1.0/30.0])
b = 20.0
The Function
Instead of operator() I will call the function doSomething()
def doSomething(s): # Where s is a numpy array (1-d vector)
p[0:4] = s[0:4]
for i in range (U.shape[0]):
s_dash = -0.5*(s - U[i].T)
s_ddash = C*s
s_dddash = s_dash.dot(s_ddash) - U[i].T
p[i+4] = b * numpy.exp(s_dddash)
if p[i+4] < 0.1: p[i+4] = 0
What I am confused:
In the C++ implementation, I think p[i+4] is supposed to be a single value
In my python version, I get a p[i+4] as square matrix
Each p[i+4] is a zero matrix.
I am unable to decipher my mistake. Please help!

How, when and what to vectorize in python?

Right, so this is basically a follow up of an earlier question of mine. I have some binary data that are in floating point binary format. Using C, the process is fast, but I lose some precision with atof(). I tried looking through the forum, and also elsewhere, but my problem was not solved. As such, I moved to python. Ah joy! the program worked perfectly well, but is so very slow compared to C. I looked up optimizations on python, which pointed me to Cython and Weave, but I have some doubts. If you will follow my code, I am confused where to apply the optimizing C code, since I am reading from the numpy object. My question, is it possible to read data using numpy functions within the Cython, and if so, please provide a small example.
The C Code uses PolSARpro's header files, and libbmp for creating the .bmp file
As a note, I am posting both my codes. God knows I had to go through a lot just to get the formulas working. This way, others in need can give their thoughts and input too :)
C Code (Working, but atof() loses precision, thus output lat long are slightly off)
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <polSARpro/bmpfile.c>
#include <polSARpro/graphics.c>
#include <polSARpro/matrix.c>
#include <polSARpro/processing.c>
#include <polSARpro/util.c>
#define METAL_THRESHOLD 5.000000
#define POLARIZATION_FRACTION_THRESHOLD 0.900000
#define PI 3.14159265
#define FOURTHPI PI/4
#define deg2rad PI/180
#define rad2deg 180./PI
/*double PI = 3.14159265;
double FOURTHPI = PI / 4;
double deg2rad = PI / 180;
double rad2deg = 180.0 / PI;*/
FILE *L1,*PF,*SPF;
FILE *txt;
FILE *finalLocations;
long i=0,loop_end;
int lig,col;
float l1,pf,spf;
long pos;
int Nlig,Ncol;
float *bufferout;
float *bufferin_L1,*bufferin_L2;
float valueL1,valuePF,xx;
float sizeGridX, sizeGridY, startX, startY;
float posX,posY;
int ZONE;
char Heading[10];
char setZone[15];
int p[4][2];
int degree, minute, second;
void UTM2LL(int ReferenceEllipsoid, double UTMNorthing, double UTMEasting, char* UTMZone, double *Lat, double *Long)
{
//converts UTM coords to lat/long. Equations from USGS Bulletin 1532
//East Longitudes are positive, West longitudes are negative.
//North latitudes are positive, South latitudes are negative
//Lat and Long are in decimal degrees.
//Written by Chuck Gantz- chuck.gantz#globalstar.com
double k0 = 0.9996;
double a = 6378137;
double eccSquared = 0.00669438;
double eccPrimeSquared;
double e1 = (1-sqrt(1-eccSquared))/(1+sqrt(1-eccSquared));
double N1, T1, C1, R1, D, M;
double LongOrigin;
double mu, phi1, phi1Rad;
double x, y;
int ZoneNumber;
char* ZoneLetter;
int NorthernHemisphere; //1 for northern hemispher, 0 for southern
x = UTMEasting - 500000.0; //remove 500,000 meter offset for longitude
y = UTMNorthing;
ZoneNumber = strtoul(UTMZone, &ZoneLetter, 10);
if((*ZoneLetter - 'N') >= 0)
NorthernHemisphere = 1;//point is in northern hemisphere
else
{
NorthernHemisphere = 0;//point is in southern hemisphere
y -= 10000000.0;//remove 10,000,000 meter offset used for southern hemisphere
}
LongOrigin = (ZoneNumber - 1)*6 - 180 + 3; //+3 puts origin in middle of zone
eccPrimeSquared = (eccSquared)/(1-eccSquared);
M = y / k0;
mu = M/(a*(1-eccSquared/4-3*eccSquared*eccSquared/64-5*eccSquared*eccSquared*eccSquared/256));
phi1Rad = mu + (3*e1/2-27*e1*e1*e1/32)*sin(2*mu)
+ (21*e1*e1/16-55*e1*e1*e1*e1/32)*sin(4*mu)
+(151*e1*e1*e1/96)*sin(6*mu);
phi1 = phi1Rad*rad2deg;
N1 = a/sqrt(1-eccSquared*sin(phi1Rad)*sin(phi1Rad));
T1 = tan(phi1Rad)*tan(phi1Rad);
C1 = eccPrimeSquared*cos(phi1Rad)*cos(phi1Rad);
R1 = a*(1-eccSquared)/pow(1-eccSquared*sin(phi1Rad)*sin(phi1Rad), 1.5);
D = x/(N1*k0);
*Lat = phi1Rad - (N1*tan(phi1Rad)/R1)*(D*D/2-(5+3*T1+10*C1-4*C1*C1-9*eccPrimeSquared)*D*D*D*D/24
+(61+90*T1+298*C1+45*T1*T1-252*eccPrimeSquared-3*C1*C1)*D*D*D*D*D*D/720);
*Lat = *Lat * rad2deg;
*Long = (D-(1+2*T1+C1)*D*D*D/6+(5-2*C1+28*T1-3*C1*C1+8*eccPrimeSquared+24*T1*T1)
*D*D*D*D*D/120)/cos(phi1Rad);
*Long = LongOrigin + *Long * rad2deg;
}
void convertToDegree(float decimal)
{
int negative = decimal < 0;
decimal = abs(decimal);
minute = (decimal * 3600/ 60);
second = fmodf((decimal * 3600),60);
degree = minute / 60;
minute = minute % 60;
if (negative)
{
if (degree > 0)
degree = -degree;
else if (minute > 0)
minute = -minute;
else
second = -second;
}
}
void readConfig(int *Row, int *Col)
{
char tmp[70];
int i=0;
FILE *fp = fopen("config.txt","r");
if(fp == NULL)
{
perror("Config.txt");
exit(1);
}
while(!feof(fp))
{
fgets(tmp,70,fp);
if (i==1)
*Row = atoi(tmp);
if(i==4)
*Col = atoi(tmp);
i++;
}
fclose(fp);
}
void readHDR(float *gridX,float *gridY,float *startXPos,float *startYPos)
{
FILE *fp = fopen("PF.bin.hdr","r");
int i=0;
char tmp[255];
char junk[255];
memset(tmp,0X00,sizeof(tmp));
memset(junk,0X00,sizeof(junk));
if(fp==NULL)
{
perror("Please locate or create PF.bin.hdr");
exit(0);
}
while(!feof(fp))
{
if(i==13)
break;
fgets(tmp,255,fp);
i++;
}
fclose(fp);
strcpy(junk,strtok(tmp,","));
strtok(NULL,",");
strtok(NULL,",");
strcpy(tmp,strtok(NULL,","));
//puts(tmp);
*startXPos = atof(tmp);
strcpy(tmp,strtok(NULL,","));
//puts(tmp);
*startYPos = atof(tmp);
strcpy(tmp,strtok(NULL,","));
//puts(tmp);
*gridX = atof(tmp);
strcpy(tmp,strtok(NULL,","));
//puts(tmp);
*gridY = atof(tmp);
strcpy(tmp,strtok(NULL,","));
ZONE = atoi(tmp);
strcpy(tmp,strtok(NULL,","));
strcpy(Heading,tmp);
}
int main()
{
bmpfile_t *bmp;
double Lat;
double Long;
int i;
rgb_pixel_t pixelMetal = {128, 64, 0, 0};
rgb_pixel_t pixelOthers = {128, 64, 0, 0};
readConfig(&Nlig,&Ncol);
readHDR(&sizeGridX,&sizeGridY,&startX,&startY);
//startX = startX - (double) 0.012000;
//startY = startY + (double)0.111000;
printf("Enter the rectangle's top-left and bottom-right region of interest points as: x y\n");
for(i=0;i<2;i++)
{
printf("Enter point %d::\t",i+1);
scanf("%d %d",&p[i][0], &p[i][1]);
}
printf("Grid Size(X,Y)::( %f,%f ), Start Positions(X,Y)::( %f, %f ), ZONE::%d, Heading:: %s\n\n",sizeGridX,sizeGridY,startX,startY,ZONE,Heading);
pixelMetal.red = 255;
pixelMetal.blue = 010;
pixelMetal.green = 010;
pixelOthers.red = 8;
pixelOthers.blue = 8;
pixelOthers.green = 8;
L1 = fopen("l1.bin","rb");
PF =fopen("PF.bin","rb");
SPF = fopen("SPF_L1.bin","wb");
//txt = fopen("locations(UTM).txt","w");
finalLocations = fopen("locationsROI.txt","w");
if(L1==NULL || PF==NULL || SPF==NULL || finalLocations == NULL)
{
perror("Error in opening files!");
return -1;
}
fseek(L1,0,SEEK_END);
pos = ftell(L1);
loop_end = pos;
printf("L1.bin contains::\t%ld elements\n",pos);
fseek(PF,0,SEEK_END);
pos = ftell(PF);
printf("PF.bin contains::\t%ld elements\n",pos);
fseek(L1,0,SEEK_SET);
fseek(PF,0,SEEK_SET);
bmp = bmp_create(Ncol,Nlig,8); //width * height
bufferin_L1 = vector_float(Ncol);
bufferin_L2 = vector_float(Ncol);
bufferout = vector_float(Ncol);
printf("Resources Allocated. Beginning...\n");
for (lig = 0; lig < Nlig; lig++) /* rows */
{
if (lig%(int)(Nlig/20) == 0)
{
printf("%f\r", 100. * lig / (Nlig - 1));
fflush(stdout);
}
fread(&bufferin_L1[0], sizeof(float), Ncol, L1);
fread(&bufferin_L2[0], sizeof(float), Ncol, PF);
for (col = 0; col < Ncol; col++) /* columns */
{
valueL1 = bufferin_L1[col];
valuePF = bufferin_L2[col];
if(valueL1 >= METAL_THRESHOLD && valuePF >= POLARIZATION_FRACTION_THRESHOLD)
{
if(col >= p[0][0] && col <= p[1][0] && lig >= p[0][1] && lig <= p[1][1])
{
xx = fabs(valueL1 + valuePF);
bmp_set_pixel(bmp,col,lig,pixelMetal);
posX = startX + (sizeGridX * col);
posY = startY - (sizeGridY * lig);
//fprintf(txt,"%f %f %d %s\n",posX,posY,ZONE,Heading);
sprintf(setZone,"%d",ZONE);
if(strstr(Heading,"Nor")!=NULL)
strcat(setZone,"N");
else
strcat(setZone,"S");
UTM2LL(23, posY, posX, setZone, &Lat, &Long); // 23 for WGS-84
convertToDegree(Lat);
//fprintf(finalLocations,"UTM:: %.2fE %.2fN , Decimal: %f %f , Degree: %d %d %d, ",posX,posY,Lat,Long,degree,minute,second);
//fprintf(finalLocations,"%.2fE,%.2fN,%f,%f ,%d,%d,%d,",posX,posY,Lat,Long,degree,minute,second);
fprintf(finalLocations,"%.2f,%.2f,%f,%f ,%d,%d,%d,",posX,posY,Lat,Long,degree,minute,second);
convertToDegree(Long);
fprintf(finalLocations,"%d,%d,%d\n",degree,minute,second);
}
else
{
xx = fabs(valueL1) ;
bmp_set_pixel(bmp,col,lig,pixelOthers);
}
}
else
{
xx = fabs(valueL1) ;
bmp_set_pixel(bmp,col,lig,pixelOthers);
}
bufferout[col] = xx;
}
fwrite(&bufferout[0], sizeof(float), Ncol, SPF);
}
free_vector_float(bufferout);
fclose(L1);
fclose(PF);
fclose(SPF);
//fclose(txt);
fclose(finalLocations);
printf("\n----------Writing BMP File!----------\n");
bmp_save(bmp,"SPF_L1(ROI).bmp");
bmp_destroy(bmp);
printf("\nDone!\n");
}
As well as the Python code::
# -*- coding: utf-8 -*-
"""
Created on Wed Apr 10 10:29:18 2013
#author: Binayaka
"""
import numpy as Num;
import math;
import array;
class readConfiguration(object):
def __init__(self,x):
self.readConfig(x);
def readConfig(self,x):
try:
crs = open(x,'r');
srs = open('config.txt','r');
except IOError:
print "Files missing!";
else:
rows = crs.readlines();
values = rows[12].split(',');
rows = srs.readlines();
self.startX = float(values[3]);
self.startY = float(values[4]);
self.gridSizeX = float(values[5]);
self.gridSizeY = float(values[6]);
self.Zone = int(values[7]);
self.Hemisphere = values[8];
self.NRows = int(rows[1].strip());
self.NCols = int(rows[4].strip());
self.MetalThreshold = 5.000000;
self.PFThreshold = 0.900000;
self.rad2deg = 180/math.pi;
self.deg2rad = math.pi/180;
self.FOURTHPI = math.pi/4;
crs.close();
srs.close();
def decdeg2dms(dd):
negative = dd < 0;
dd = abs(dd);
minutes,seconds = divmod(dd*3600,60);
degrees,minutes = divmod(minutes,60);
if negative:
if degrees > 0:
degrees = -degrees;
elif minutes > 0:
minutes = -minutes;
else:
seconds = -seconds;
return (degrees,minutes,seconds);
def UTM2LL(self,UTMEasting, UTMNorthing):
k0 = 0.9996;
a = 6378137;
eccSquared = 0.00669438;
e1 = (1-math.sqrt(1-eccSquared))/(1+math.sqrt(1-eccSquared));
x = UTMEasting - 500000.0;#remove 500,000 meter offset for longitude
y = UTMNorthing;
if self.Hemisphere == "North":
self.Hemi = 1;
else:
self.Hemi = -1;
y -= 10000000.0;
LongOrigin = (self.Zone - 1)*6 - 180 + 3;
eccPrimeSquared = (eccSquared)/(1-eccSquared);
M = y / k0;
mu = M/(a*(1-eccSquared/4-3*eccSquared*eccSquared/64-5*eccSquared*eccSquared*eccSquared/256));
phi1Rad = mu + (3*e1/2-27*e1*e1*e1/32)*math.sin(2*mu) + (21*e1*e1/16-55*e1*e1*e1*e1/32)*math.sin(4*mu) +(151*e1*e1*e1/96)*math.sin(6*mu);
#phi1 = phi1Rad*self.rad2deg;
N1 = a/math.sqrt(1-eccSquared*math.sin(phi1Rad)*math.sin(phi1Rad));
T1 = math.tan(phi1Rad)*math.tan(phi1Rad);
C1 = eccPrimeSquared*math.cos(phi1Rad)*math.cos(phi1Rad);
R1 = a*(1-eccSquared)/pow(1-eccSquared*math.sin(phi1Rad)*math.sin(phi1Rad), 1.5);
D = x/(N1*k0);
self.Lat = phi1Rad - (N1*math.tan(phi1Rad)/R1)*(D*D/2-(5+3*T1+10*C1-4*C1*C1-9*eccPrimeSquared)*D*D*D*D/24 +(61+90*T1+298*C1+45*T1*T1-252*eccPrimeSquared-3*C1*C1)*D*D*D*D*D*D/720);
self.Lat = self.Lat * self.rad2deg;
self.Long = (D-(1+2*T1+C1)*D*D*D/6+(5-2*C1+28*T1-3*C1*C1+8*eccPrimeSquared+24*T1*T1)*D*D*D*D*D/120)/math.cos(phi1Rad);
self.Long = LongOrigin + self.Long * self.rad2deg;
def printConfiguration(self):
""" Just to check whether our reading was correct """
print "Metal Threshold:\t" + str(self.MetalThreshold);
print "PF Threshold:\t" + str(self.PFThreshold);
print "Start X:\t" + str(self.startX);
print "Start Y:\t" + str(self.startY);
print "Grid size(X) :\t" + str(self.gridSizeX);
print "Grid size(Y) :\t" + str(self.gridSizeY);
def createROIfile(self,ROIFilename):
firstPoint = raw_input('Enter topLeft point coord\t').split();
secondPoint = raw_input('Enter bottomRight point coord\t').split();
try:
L1 = open('l1.bin','rb');
PF = open('PF.bin','rb');
SPF = open('pySPF_L1.bin','wb');
targetFilename = open(ROIFilename,'w');
except IOError:
print "Files Missing!";
else:
L1.seek(0,2);
elementsL1 = L1.tell();
L1.seek(0,0);
PF.seek(0,2);
elementsPF = PF.tell();
PF.seek(0,0);
print "L1.bin contains\t" + str(elementsL1) + " elements";
print "PF.bin contains\t" + str(elementsPF) + " elements";
binvaluesL1 = array.array('f');
binvaluesPF = array.array('f');
binvaluesSPF = array.array('f');
for row in range(0,self.NRows):
binvaluesL1.read(L1,self.NCols);
binvaluesPF.read(PF,self.NCols);
dataL1 = Num.array(binvaluesL1, dtype=Num.float);
dataPF = Num.array(binvaluesPF, dtype=Num.float);
dataSPF = dataL1 + dataPF;
binvaluesSPF.fromlist(Num.array(dataSPF).tolist());
for col in range(0,self.NCols):
if(dataL1[col] >= self.MetalThreshold and dataPF[col] >= self.PFThreshold):
if(col >= int(firstPoint[0]) and col <= int(secondPoint[0]) and row >= int(firstPoint[1]) and row <= int(secondPoint[1])):
posX = self.startX + (self.gridSizeX * col);
posY = self.startY - (self.gridSizeY * row);
self.UTM2LL(posY,posX);
tmp1 = self.decdeg2dms(posY);
tmp2 = self.decdeg2dms(posX);
strTarget = "Decimal Degree:: " + str(posX) + "E " + str(posY) + "N \t Lat long:: " + str(tmp1) + " " + str(tmp2) + "\n";
targetFilename.write(strTarget);
binvaluesSPF.tofile(SPF);
L1.close();
PF.close();
SPF.close();
targetFilename.close();
print "Done!";
dimensions = readConfiguration('PF.bin.hdr');
dimensions.printConfiguration();
dimensions.createROIfile('testPythonROI.txt');
Its the Python code that needs Optimization, as the values of NRows and NCols can and do reach the order of thousands.
A few general comments:
With python, it's really best to stick to PEP8 for a multitude of reasons. Python programmers are particularly picky about readability and essentially universally adhere to the community coding guidelines (PEP8). Avoid camelCase, keep lines below 80 columns, leave the semicolons out, and feel free to occasionally ignore these guidelines where they'd make things less readable.
There's no need for the builtin array type here if you're using numpy. I'm confused why you're constantly converting back and forth...
Use a projection library. Specify what datum and ellipsoid you're using, otherwise the coordinates (easting/northing or lat/long) have absolutely no meaning.
Don't use one big class as a hold-all for unrelated things. There's nothing wrong with just having a few functions. You don't need to make it into a class unless it makes sense to do so.
Use vectorized operations with numpy arrays.
Here's what would appear to be your performance bottleneck:
for row in range(0,self.NRows):
binvaluesL1.read(L1,self.NCols);
binvaluesPF.read(PF,self.NCols);
dataL1 = Num.array(binvaluesL1, dtype=Num.float);
dataPF = Num.array(binvaluesPF, dtype=Num.float);
dataSPF = dataL1 + dataPF;
binvaluesSPF.fromlist(Num.array(dataSPF).tolist());
for col in range(0,self.NCols):
if(dataL1[col] >= self.MetalThreshold and dataPF[col] >= self.PFThreshold):
if(col >= int(firstPoint[0]) and col <= int(secondPoint[0]) and row >= int(firstPoint[1]) and row <= int(secondPoint[1])):
posX = self.startX + (self.gridSizeX * col);
posY = self.startY - (self.gridSizeY * row);
self.UTM2LL(posY,posX);
tmp1 = self.decdeg2dms(posY);
tmp2 = self.decdeg2dms(posX);
strTarget = "Decimal Degree:: " + str(posX) + "E " + str(posY) + "N \t Lat long:: " + str(tmp1) + " " + str(tmp2) + "\n";
targetFilename.write(strTarget);
binvaluesSPF.tofile(SPF);
One of your biggest problems is the way you're reading in your data. You're constantly reading things in as one thing, then converting that to a list, then converting that to a numpy array. There's absolutely no need to jump through all those hoops. Numpy will unpack your binary floats for you just like array will.
Just do grid = np.fromfile(yourfile, dtype=np.float32).reshape(ncols, nrows). (Outside the loop.)
After that, your nested loops can be easily vectorized and expressed with just a few lines of code.
Here's how I would write your code. This probably won't run as-is, as I can't test it with your data. However, it should give you some general ideas.
import numpy as np
import pyproj
def main():
config = Config('PF.bin.hdr')
grid1, grid2 = load_data('l1.bin', 'PF.bin', config.nrows, config.ncols)
spf = grid1 + grid2
spf.tofile('pySPF_L1.bin')
easting_aoi, northing_aoi = subset_data(grid1, grid2, config)
save_selected_region(easting_aoi, northing_aoi, config.zone,
'testPythonROI.txt')
def load_data(filename1, filename2, nrows, ncols):
"""It would really be good to use more descriptive variable names than "L1"
and "PF". I have no idea what L1 and PF are, so I'm just calling them
grid1 and grid2."""
grid1 = np.fromfile(filename1, dtype=np.float32).reshape(nrows, ncols)
grid2 = np.fromfile(filename2, dtype=np.float32).reshape(nrows, ncols)
return grid1, grid2
def subset_data(grid1, grid2, config):
"""Select points that satisfy some threshold criteria (explain??) and are
within a user-specified rectangular AOI."""
northing, easting = np.mgrid[:config.nrows, :config.ncols]
easting = config.xstart + config.xgridsize * easting
northing = config.ystart + config.ygridsize * northing
grids = grid1, grid2, easting, northing
grid1, grid2, easting, northing = [item[config.user_aoi] for item in grids]
mask = (grid1 >= config.metal_threshold) & (grid2 >= config.pf_threshold)
return easting[mask], northing[mask]
def save_selected_region(easting, northing, zone, filename):
"""Convert the given eastings and northings (in UTM zone "zone") to
lat/long and save to a tab-delimited-text file."""
lat, lon = utm2geographic(easting, northing, zone)
data = np.vstack([easting, northing, lat, lon]).T
with open(filename, 'w') as outfile:
outfile.write('Easting\tNorthing\tLatitude\tLongitude\n')
np.savetxt(outfile, data, delimiter='\t')
def utm2geographic(easting, northing, zone):
"""We need to know which datum/ellipsoid the UTM coords are in as well!!!!
I'm assuming it's a Clark 1866 ellipsoid, based on the numbers in your
code..."""
utm = pyproj.Proj(proj='utm', zone=zone, ellip='clrk66')
geographic = pyproj.Proj(proj='latlong', ellip='clrk66')
return pyproj.transform(utm, geographic, easting, northing)
class Config(object):
"""Read and store configuration values for (something?)."""
config_file = 'config.txt'
def __init__(self, filename):
"""You should add docstrings to clarify what you're expecting
"filename" to contain."""
with open(filename, 'r') as infile:
crs_values = list(infile)[12].split(',')
crs_values = [float(item) for item in crs_values]
self.xstart, self.ystart = crs_values[3:5]
self.xgridsize, self.ygridsize = crs_values[5:7]
self.zone = int(crs_values[7])
with open(self.config_file, 'r') as infile:
srs_values = list(infile)
self.nrows, self.ncols = srs_values[1], srs_values[4]
# It would be good to explain a bit about these (say, units, etc)
self.metal_threshold = 5.0
self.pf_threshold = 0.9
self.user_aoi = self.read_user_aoi()
def read_user_aoi(self):
"""Get an area of interest of the grids in pixel coordinates."""
top_left = raw_input('Enter top left index\t')
bottom_right = raw_input('Enter bottom right index\t')
min_i, min_j = [int(item) for item in top_left.split()]
max_i, max_j = [int(item) for item in bottom_right.split()]
return slice(min_i, max_i), slice(min_j, max_j)
if __name__ == '__main__':
main()

Making the diamond square fractal algorithm infinite

I'm trying to generate an infinite map, as such. I'm doing this in Python, and I can't get the noise libraries to correctly work (they don't seem to ever find my VS2010, and doing it in raw Python would be way too slow). As such, I'm trying to use the Diamond-Square Algorithm.
Would it be possible, in some way, to make this technically infinite?
If not, should I just go back to attempting to get one of the Python noise bindings to work?
This can be done. Divide up your landscape into "tiles", and apply the midpoint displacement algorithm (as I prefer to call it) within each tile.
Each tile must be big enough so that the height at one corner does not significantly depend on the height in another. In that way, you can create and destroy tiles on the fly, setting the height at newly appearing corners to an independent random value.
That value (and the randomness within the tile as well) must be seeded from the tile's position, so that you get the same tile each time.
The latest version of the noise module (at http://pypi.python.org/pypi/noise/1.0b3) mentions that it "fixed problems compiling with Visual C++ on Windows" - you might want to try it again. It installs and works properly for me (Python 2.7.1, MinGW, Windows 7).
If you arrange your tiles in an x,y grid and seed the random number generator for each tile like random.seed((x,y)), then every time you return to the same patch of world it will re-create the same terrain.
With the diamond-square algorithm, two sides of each tile depend on the adjoining tiles; however the dependence does not propagate all the way across the tile. If you let each tile depend on the tiles preceding it (ie above and to the left) and write a create_fake_tile function which assumes that all the preceding-tile values are 0, you get a "bad" tile in which the right column and bottom row are the correct values on which the next tile depends. If you draw each screen starting with the tile row and column preceding the first row and column on screen, then the visible portion of your world will be correct.
Can you apply midpoint displacement from the bottom up? Say you're working on a discrete grid, and you want to generate an MxN region. Get yourself a weighting function w(i). Start by applying a random displacement with weight w(0) to each point. Then apply a random displacement with weight w(1) to every other point, and propagate the displacement to intervening points. Then do every fourth point with w(2), again propagating. You can now generate a grid of any size, without having any starting assumption about the size.
If there's some N for which w(i > N) = 0, then you can stop generating when you hit it - which is rather important if you want the generation to ever terminate! You probably want a w function that starts at 1, increases to some value, and then falls off to zero; the increasing bit models the power-law roughness of terrain up to 100-km or so scales, and the decreasing bit models the fact that whole planets are more or less spherical. If you were doing Mars rather than Earth, you'd want w(i) to go further, because of the Tharsis bulge.
Now replace the random function with a random-looking but deterministic function of the point coordinates (feed the coordinates into a SHA1 hash, for example). Now, whenever you generate some particular part of the grid, it will look the same.
You should be able to do diamond-square in much the same way as this; you just need to alternate the shape of the displacements.
I solved the Diamond Squared algorithm for infinite landscapes.
function diamondSquaredMap(x, y, width, height, iterations) {
var map = fieldDiamondSquared(x, y, x+width, y+height, iterations);
var maxdeviation = getMaxDeviation(iterations);
for (var j = 0; j < width; j++) {
for (var k = 0; k < height; k++) {
map[j][k] = map[j][k] / maxdeviation;
map[j][k] = (map[j][k] + 1) / 2;
}
}
return map;
function create2DArray(d1, d2) {
var x = new Array(d1),
i = 0,
j = 0;
for (i = 0; i < d1; i += 1) {
x[i] = new Array(d2);
}
return x;
}
function fieldDiamondSquared(x0, y0, x1, y1, iterations) {
if (x1 < x0) { return null; }
if (y1 < y0) { return null; }
var finalwidth = x1 - x0;
var finalheight = y1 - y0;
var finalmap = create2DArray(finalwidth, finalheight);
if (iterations === 0) {
for (var j = 0; j < finalwidth; j++) {
for (var k = 0; k < finalheight; k++) {
finalmap[j][k] = displace(iterations,x0+j,y0+k) ;
}
}
return finalmap;
}
var ux0 = Math.floor(x0 / 2) - 1;
var uy0 = Math.floor(y0 / 2) - 1;
var ux1 = Math.ceil(x1 / 2) + 1;
var uy1 = Math.ceil(y1 / 2) + 1;
var uppermap = fieldDiamondSquared(ux0, uy0, ux1, uy1, iterations-1);
var uw = ux1 - ux0;
var uh = uy1 - uy0;
var cx0 = ux0 * 2;
var cy0 = uy0 * 2;
var cw = uw*2-1;
var ch = uh*2-1;
var currentmap = create2DArray(cw,ch);
for (var j = 0; j < uw; j++) {
for (var k = 0; k < uh; k++) {
currentmap[j*2][k*2] = uppermap[j][k];
}
}
var xoff = x0 - cx0;
var yoff = y0 - cy0;
for (var j = 1; j < cw-1; j += 2) {
for (var k = 1; k < ch-1; k += 2) {
currentmap[j][k] = ((currentmap[j - 1][k - 1] + currentmap[j - 1][k + 1] + currentmap[j + 1][k - 1] + currentmap[j + 1][k + 1]) / 4) + displace(iterations,cx0+j,cy0+k);
}
}
for (var j = 1; j < cw-1; j += 2) {
for (var k = 2; k < ch-1; k += 2) {
currentmap[j][k] = ((currentmap[j - 1][k] + currentmap[j + 1][k] + currentmap[j][k - 1] + currentmap[j][k + 1]) / 4) + displace(iterations,cx0+j,cy0+k);
}
}
for (var j = 2; j < cw-1; j += 2) {
for (var k = 1; k < ch-1; k += 2) {
currentmap[j][k] = ((currentmap[j - 1][k] + currentmap[j + 1][k] + currentmap[j][k - 1] + currentmap[j][k + 1]) / 4) + displace(iterations,cx0+j,cy0+k);
}
}
for (var j = 0; j < finalwidth; j++) {
for (var k = 0; k < finalheight; k++) {
finalmap[j][k] = currentmap[j+xoff][k+yoff];
}
}
return finalmap;
}
// Random function to offset
function displace(iterations, x, y) {
return (((PRH(iterations,x,y) - 0.5)*2)) / (iterations+1);
}
function getMaxDeviation(iterations) {
var dev = 0.5 / (iterations+1);
if (iterations <= 0) return dev;
return getMaxDeviation(iterations-1) + dev;
}
//This function returns the same result for given values but should be somewhat random.
function PRH(iterations,x,y) {
var hash;
x &= 0xFFF;
y &= 0xFFF;
iterations &= 0xFF;
hash = (iterations << 24);
hash |= (y << 12);
hash |= x;
var rem = hash & 3;
var h = hash;
switch (rem) {
case 3:
hash += h;
hash ^= hash << 32;
hash ^= h << 36;
hash += hash >> 22;
break;
case 2:
hash += h;
hash ^= hash << 22;
hash += hash >> 34;
break;
case 1:
hash += h;
hash ^= hash << 20;
hash += hash >> 2;
}
hash ^= hash << 6;
hash += hash >> 10;
hash ^= hash << 8;
hash += hash >> 34;
hash ^= hash << 50;
hash += hash >> 12;
return (hash & 0xFFFF) / 0xFFFF;
}
};
//CANVAS CONTROL
window.onload = terrainGeneration;
function terrainGeneration() {
"use strict";
var mapDimension,
roughness,
iterations,
mapCanvas = document.getElementById('canvas');
var update = document.getElementById('update');
var xpos = 0;
var ypos = 0;
mapDimension = 512;
mapCanvas.width = mapDimension;
mapCanvas.height = mapDimension;
var updatefunction = function() {
var elIterations = document.getElementById('iterations');
iterations = parseInt(elIterations.value, 10);
iterations = iterations || 6;
MoveMap(10,0);
}
update.onclick = updatefunction;
updatefunction();
function MoveMap(dx, dy) {
xpos -= dx;
ypos -= dy;
var map = diamondSquaredMap(xpos, ypos, mapDimension, mapDimension, iterations);
drawMap(mapDimension, "canvas", map);
}
var m = this;
m.map = document.getElementById("canvas");
m.width = mapDimension;
m.height = mapDimension;
m.hoverCursor = "auto";
m.dragCursor = "url(data:image/vnd.microsoft.icon;base64,AAACAAEAICACAAcABQAwAQAAFgAAACgAAAAgAAAAQAAAAAEAAQAAAAAAAAEAAAAAAAAAAAAAAgAAAAAAAAAAAAAA////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD8AAAA/AAAAfwAAAP+AAAH/gAAB/8AAAH/AAAB/wAAA/0AAANsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//////////////////////////////////////////////////////////////////////////////////////gH///4B///8Af//+AD///AA///wAH//+AB///wAf//4AH//+AD///yT/////////////////////////////8=), default";
m.scrollTime = 300;
m.mousePosition = new Coordinate;
m.mouseLocations = [];
m.velocity = new Coordinate;
m.mouseDown = false;
m.timerId = -1;
m.timerCount = 0;
m.viewingBox = document.createElement("div");
m.viewingBox.style.cursor = m.hoverCursor;
m.map.parentNode.replaceChild(m.viewingBox, m.map);
m.viewingBox.appendChild(m.map);
m.viewingBox.style.overflow = "hidden";
m.viewingBox.style.width = m.width + "px";
m.viewingBox.style.height = m.height + "px";
m.viewingBox.style.position = "relative";
m.map.style.position = "absolute";
function drawMap(size, canvasId, mapData) {
var canvas = document.getElementById(canvasId),
ctx = canvas.getContext("2d"),
x = 0,
y = 0,
colorFill,
img = ctx.createImageData(canvas.height, canvas.width);
for (x = 0; x < size; x++) {
for (y = 0; y < size; y++) {
colorFill = {r: 0, g: 0, b: 0};
var standardShade = Math.floor(mapData[x][y] * 250);
colorFill = {r: standardShade, g: standardShade, b: standardShade};
var pData = (x + (y * canvas.width)) * 4;
img.data[pData] = colorFill.r;
img.data[pData + 1] = colorFill.g;
img.data[pData + 2] = colorFill.b;
img.data[pData + 3] = 255;
}
}
ctx.putImageData(img, 0, 0);
}
function AddListener(element, event, f) {
if (element.attachEvent) {
element["e" + event + f] = f;
element[event + f] = function() {
element["e" + event + f](window.event)
};
element.attachEvent("on" + event, element[event + f])
} else
element.addEventListener(event, f, false)
}
function Coordinate(startX, startY) {
this.x = startX;
this.y = startY;
}
var MouseMove = function(b) {
var e = b.clientX - m.mousePosition.x;
var d = b.clientY - m.mousePosition.y;
MoveMap(e, d);
m.mousePosition.x = b.clientX;
m.mousePosition.y = b.clientY
};
/**
* mousedown event handler
*/
AddListener(m.viewingBox, "mousedown", function(e) {
m.viewingBox.style.cursor = m.dragCursor;
// Save the current mouse position so we can later find how far the
// mouse has moved in order to scroll that distance
m.mousePosition.x = e.clientX;
m.mousePosition.y = e.clientY;
// Start paying attention to when the mouse moves
AddListener(document, "mousemove", MouseMove);
m.mouseDown = true;
event.preventDefault ? event.preventDefault() : event.returnValue = false;
});
/**
* mouseup event handler
*/
AddListener(document, "mouseup", function() {
if (m.mouseDown) {
var handler = MouseMove;
if (document.detachEvent) {
document.detachEvent("onmousemove", document["mousemove" + handler]);
document["mousemove" + handler] = null;
} else {
document.removeEventListener("mousemove", handler, false);
}
m.mouseDown = false;
if (m.mouseLocations.length > 0) {
var clickCount = m.mouseLocations.length;
m.velocity.x = (m.mouseLocations[clickCount - 1].x - m.mouseLocations[0].x) / clickCount;
m.velocity.y = (m.mouseLocations[clickCount - 1].y - m.mouseLocations[0].y) / clickCount;
m.mouseLocations.length = 0;
}
}
m.viewingBox.style.cursor = m.hoverCursor;
});
}
<html><head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Canvas Terrain Generator</title>
<link rel="stylesheet" href="terrain.css" type="text/css">
<style type="text/css"></style><style type="text/css"></style></head>
<body>
Click and Pan.<br>
<div style="cursor: auto; overflow: hidden; width: 512px; height: 512px; position: relative;"><canvas id="canvas" width="512" height="512" style="position: absolute;"></canvas></div>
<br>
<form>
<fieldset>
<legend>Height Map Properties</legend>
<ol class="options">
<li>
<input type="text" name="iterations" id="iterations">
<label for="iterations">
Iterations(6)
</label>
</li>
</ol>
</fieldset>
<input type="button" value="Update" id="update">
</form>
</body></html>
https://jsfiddle.net/Tatarize/1Lrj3s2v/3/
Click and Drag jsfiddle
And actually found this question doing my due diligence. Yes. Though the answers provided here are mostly wrong it totally can be done. Conceptually what you need to do is view each iteration of the algorithm as an infinite field and the base case as an infinite field of perfectly random numbers. So each iteration is a diamond squared version of the previous one.
And to solve a range of say tile [100-200][100-200] at iteration 7, what you need is the entire block half that size (~a quarter that size h*w) at iteration 6 plus one extra edge on each side. So if you have a function that solves for a given tile:
getTerrain(x0,y0,x1,y1,iteration)... you can solve this for that tile if you have [49,101][49,101] at iteration 6. Then you simply apply diamond squared everywhere you can which won't work at the edges but will work everywhere else, and provide you with exactly enough data to solve for the tile [100-200][100-200] at iteration 7.
To get that tile at iteration 6 it will request [23-52][23-52] from iteration 5. And this will continue until iteration 0 just gives you [-1,3][-1,3] which will be 16 perfectly random values. If you requested a tile so large that iteration 0 didn't yet reach that size and position, you'll just need a different sliver which is fine because you're just making them up anyway.
In this way you get rid of the seeding step completely, simplify the algorithm, make it infinite, make it take a reasonable memory footprint. Using a deterministic pseudorandom number hashed from the coords and it will let you generate and regenerate the identical tiles on the fly, because you actually generated the upper lower iterations and just did so in a focused manner.
My blog has more information on it,
http://godsnotwheregodsnot.blogspot.com/2013/11/field-diamond-squared-fractal-terrain.html
In reality most of the complication of the algorithm comes from having the base case four points in a loop, as this is not close to the simplest case. And it's apparently escaped most people that you could have even simplified that by doing 1 point in a loop (though this is still pointlessly complex). Or anything that makes the algorithm seem to have at least 4x4 points. You could even start with 4x4 points iterate once, drop any row or column with an unknown value (all the edges), then have a 5x5 block, then a 7x7 block then an 11x11 block... (2n-3) x (2n-3).
In short, if you have an infinite field for your base case, you can actually have an infinite field, the iterations just determine how blended your stuff is. And if you use something deterministic for the pseudorandom injected offset, you pretty much have a very quick infinite landscape generator.
and here's a demonstration of it in javascript,
http://tatarize.nfshost.com/FieldDiamondSquare.htm
Here's an implementation in javascript. It simply gives you a view of the correct field at that iteration, position, and size. The above linked example uses this code in a movable canvas. It stores no data and recalculates each frame.
function diamondSquaredMap(x, y, width, height, iterations) {
var map = fieldDiamondSquared(x, y, x+width, y+height, iterations);
var maxdeviation = getMaxDeviation(iterations);
for (var j = 0; j < width; j++) {
for (var k = 0; k < height; k++) {
map[j][k] = map[j][k] / maxdeviation;
}
}
return map;
function create2DArray(d1, d2) {
var x = new Array(d1),
i = 0,
j = 0;
for (i = 0; i < d1; i += 1) {
x[i] = new Array(d2);
}
return x;
}
function fieldDiamondSquared(x0, y0, x1, y1, iterations) {
if (x1 < x0) { return null; }
if (y1 < y0) { return null; }
var finalwidth = x1 - x0;
var finalheight = y1 - y0;
var finalmap = create2DArray(finalwidth, finalheight);
if (iterations === 0) {
for (var j = 0; j < finalwidth; j++) {
for (var k = 0; k < finalheight; k++) {
finalmap[j][k] = displace(iterations,x0+j,y0+k) ;
}
}
return finalmap;
}
var ux0 = Math.floor(x0 / 2) - 1;
var uy0 = Math.floor(y0 / 2) - 1;
var ux1 = Math.ceil(x1 / 2) + 1;
var uy1 = Math.ceil(y1 / 2) + 1;
var uppermap = fieldDiamondSquared(ux0, uy0, ux1, uy1, iterations-1);
var uw = ux1 - ux0;
var uh = uy1 - uy0;
var cx0 = ux0 * 2;
var cy0 = uy0 * 2;
var cw = uw*2-1;
var ch = uh*2-1;
var currentmap = create2DArray(cw,ch);
for (var j = 0; j < uw; j++) {
for (var k = 0; k < uh; k++) {
currentmap[j*2][k*2] = uppermap[j][k];
}
}
var xoff = x0 - cx0;
var yoff = y0 - cy0;
for (var j = 1; j < cw-1; j += 2) {
for (var k = 1; k < ch-1; k += 2) {
currentmap[j][k] = ((currentmap[j - 1][k - 1] + currentmap[j - 1][k + 1] + currentmap[j + 1][k - 1] + currentmap[j + 1][k + 1]) / 4) + displace(iterations,cx0+j,cy0+k);
}
}
for (var j = 1; j < cw-1; j += 2) {
for (var k = 2; k < ch-1; k += 2) {
currentmap[j][k] = ((currentmap[j - 1][k] + currentmap[j + 1][k] + currentmap[j][k - 1] + currentmap[j][k + 1]) / 4) + displace(iterations,cx0+j,cy0+k);
}
}
for (var j = 2; j < cw-1; j += 2) {
for (var k = 1; k < ch-1; k += 2) {
currentmap[j][k] = ((currentmap[j - 1][k] + currentmap[j + 1][k] + currentmap[j][k - 1] + currentmap[j][k + 1]) / 4) + displace(iterations,cx0+j,cy0+k);
}
}
for (var j = 0; j < finalwidth; j++) {
for (var k = 0; k < finalheight; k++) {
finalmap[j][k] = currentmap[j+xoff][k+yoff];
}
}
return finalmap;
}
// Random function to offset
function displace(iterations, x, y) {
return (((PRH(iterations,x,y) - 0.5)*2)) / (iterations+1);
}
function getMaxDeviation(iterations) {
var dev = 0.5 / (iterations+1);
if (iterations <= 0) return dev;
return getMaxDeviation(iterations-1) + dev;
}
//This function returns the same result for given values but should be somewhat random.
function PRH(iterations,x,y) {
var hash;
x &= 0xFFF;
y &= 0xFFF;
iterations &= 0xFF;
hash = (iterations << 24);
hash |= (y << 12);
hash |= x;
var rem = hash & 3;
var h = hash;
switch (rem) {
case 3:
hash += h;
hash ^= hash << 32;
hash ^= h << 36;
hash += hash >> 22;
break;
case 2:
hash += h;
hash ^= hash << 22;
hash += hash >> 34;
break;
case 1:
hash += h;
hash ^= hash << 20;
hash += hash >> 2;
}
hash ^= hash << 6;
hash += hash >> 10;
hash ^= hash << 8;
hash += hash >> 34;
hash ^= hash << 50;
hash += hash >> 12;
return (hash & 0xFFFF) / 0xFFFF;
}
};
Updated to add, I went on from this, and made my own noise algorithm based on the same scoped field algorithm here, Here's the Jsfiddle for it.
https://jsfiddle.net/rkdzau7o/
You can click and drag the noise around.

Categories

Resources