newuoa.c   check for denom equal to 0   2 Sept 2016 13:43
mdplib.c   reinstate check of denom against 0 in update   2 Sept 2016 13:36
lincoa.c   reinstate check of denom against 0 in update   2 Sept 2016 13:36
memory.h   add struct xy   22 Aug 2016 11:32
lincoatest.c   for new lincoa   22 Aug 2016 09:52
lincoa.c   fix for infinite loop   22 Aug 2016 08:59
mdplib.c   fix for infinite loop   22 Aug 2016 08:59
pix.js   “   10 Aug 2016 08:44
pix.js   better formatting   10 Aug 2016 08:42
pix.js   slighly improved info   10 Aug 2016 08:36
pixlib.js   slighly improved info   10 Aug 2016 08:36
routemaster.js   keyword=value URL syntax   9 Aug 2016 15:15
routemasterlib.js   keyword=value URL syntax   9 Aug 2016 15:15
tcxlib.js   keyword=value URL syntax   9 Aug 2016 15:15
pix.js   bugfix, rudimentary tab info   4 Aug 2016 15:40
pixlib.js   bugfix, rudimentary tab info   4 Aug 2016 15:40
pixlib.js   bugfix   4 Aug 2016 14:45
pix.js   google-friendly url parms   4 Aug 2016 14:26
pixlib.js   google-friendly url parms   4 Aug 2016 14:26
pixlib.js   bugfix (l 165)   27 July 2016 15:53
routemaster.js   ... and *not* for indexes   26 July 2016 20:58
routemaster.js   add stars to meta description for individual routes   26 July 2016 20:30
routemaster.js   concatenate segment titles for the meta description of a route index   26 July 2016 20:09
routemaster.js   optimise optimisation   26 July 2016 14:10
routemasterlib.js   optimise optimisation   26 July 2016 14:10
tcxlib.js   optimise optimisation   26 July 2016 14:10
pix.js   licence   25 July 2016 20:18
pixlib.js   licence   25 July 2016 20:18
tcxlib.js   add licence   25 July 2016 14:17
routemaster.js   new vsn   25 July 2016 12:25
routemasterlib.js   new vsn   25 July 2016 12:25
tcxlib.js   new vsn   25 July 2016 12:25
ij.h   for new memory.h   2 June 2016 10:53
ijtest.c   for new memory.h   2 June 2016 10:53
testunreal.c   for new memory.h   2 June 2016 10:46
memorytest.c   for new memory.h   2 June 2016 10:39
unreal.h   for new memory.h   2 June 2016 09:58
qr.c   for new memory.h   2 June 2016 09:50
cholesky.c   for new memory.h   2 June 2016 09:48
memory.h   rewrite   2 June 2016 09:45
tcxlib.js   set valid[[t] correctly for valid times   31 May 2016 16:58
tcxedit.js   cosmetic   31 May 2016 16:02
tcxedit.js   bugfixes for download   31 May 2016 16:02
tcxlib.js   bugfixes for download   31 May 2016 16:02
lincoa.c   use quadprog to bring starting point within constraints   26 May 2016 15:07
lincoatest.c   use quadprog to bring starting point within constraints   26 May 2016 15:07
quadprog.c   bugfixes   26 May 2016 14:01
quadprogtest.c   bugfixes   26 May 2016 14:01
quadprog.c   add MIT licence   25 May 2016 09:33
quadprog.c   bugfix to findpivot   25 May 2016 09:00
quadprog.c   cosmetic changes   24 May 2016 13:22
quadprog.c   correct a comment   24 May 2016 11:32
quadprog.c   use perturbation method to protect against degeneracy   24 May 2016 10:54
lincoa.c   transpose xpt/xp   9 May 2016 09:04
newuoa.c   transpose xpt/xp   9 May 2016 09:04
powelllib.c   transpose xpt/xp   9 May 2016 09:04
lincoa.c   move more to powelllib   9 May 2016 08:26
newuoa.c   move more to powelllib   9 May 2016 08:26
powelllib.c   move more to powelllib   9 May 2016 08:26
lincoa.c   break out calcvlagbeta   8 May 2016 15:06
newuoa.c   break out calcvlagbeta   8 May 2016 15:06
powelllib.c   break out calcvlagbeta   8 May 2016 15:06
newuoa.c   whoops   8 May 2016 14:28
lincoa.c   break out shiftxbase   8 May 2016 14:26
newuoa.c   break out shiftxbase   8 May 2016 14:26
powelllib.c   break out shiftxbase   8 May 2016 14:26
lincoa.c   split update into powelllib   6 May 2016 11:19
newuoa.c   split update into powelllib   6 May 2016 11:19
memory.h   better freadline   6 May 2016 08:51
fablincoa.c   added manually   6 May 2016 08:39
routemasterlib.js   better welcome screen   21 Apr 2016 16:12
tcxlib.js   fixed to accept versante sud GPX, a little extra blurb   21 Apr 2016 10:38
tcxedit.js   fixed to accept versante sud GPX, a little extra blurb   21 Apr 2016 10:38
routemasterlib.js   fixed to accept versante sud GPX, a little extra blurb   21 Apr 2016 10:38
tcxedit.js   use srcset in walkto thumbnails   16 Apr 2016 10:41
tcxedit.js   bugfix in walkto   16 Apr 2016 09:27
tcxedit.js   major rewrite for overviews   15 Apr 2016 16:36
pixlib.js   use findimage; correct srcset bug   12 Apr 2016 09:45
thumbs.js   now in pixlib   12 Apr 2016 08:26
pix.js   use srcset (and hithumb) for thumbs   12 Apr 2016 08:24
pixlib.js   use srcset (and hithumb) for thumbs   12 Apr 2016 08:24
pix.js   bugfixes   9 Apr 2016 17:00
pixlib.js   bugfixes   9 Apr 2016 17:00
pix.js   add links filed to list file   9 Apr 2016 16:06
pixlib.js   rename prefetch to loadfunc in genimage   8 Apr 2016 12:07
pix.js   add (and use) genimage   8 Apr 2016 12:03
pixlib.js   add (and use) genimage   8 Apr 2016 12:03
pix.js   use findimage   8 Apr 2016 11:24
pixlib.js   add findimage   8 Apr 2016 11:21
tcxedit.js   use pixlib   5 Apr 2016 08:40
pix.js   bugfix   4 Apr 2016 21:11
pix.js   fewer hard-wired styles   4 Apr 2016 15:34
pixlib.js   fewer hard-wired styles   4 Apr 2016 15:34
pix.js   no longer intercept pinches from tabular display   4 Apr 2016 08:28
pixlib.js   no longer intercept pinches from tabular display   4 Apr 2016 08:28
pix.js   downswipe = return   1 Apr 2016 11:53
pix.js   debug swipes   1 Apr 2016 11:22
pixlib.js   debug swipes   1 Apr 2016 11:22
pix.js   kludge for webkit overflow; bugfix   31 Mar 2016 11:22
pixlib.js   kludge for webkit overflow; bugfix   31 Mar 2016 11:22
pix.js   break out pixlib   30 Mar 2016 16:57
tcxedit.js   bugfix   24 Mar 2016 16:53
pix.js   use full title   6 Mar 2016 13:54
pix.js   tidier title   6 Mar 2016 13:21
pix.js      6 Mar 2016 13:05
pix.js   delayed swapping in of implicitly resized images   6 Mar 2016 11:49
cholesky.c   cosmetic   4 Mar 2016 17:44
pix.js   don’t link to resized versions   4 Mar 2016 14:35
cholesky.c   abort in logcholesky if not pd   2 Mar 2016 10:05
slopingbw.h   bugfix to sort   2 Mar 2016 08:38
tcxedit.js   error message if XMLHttpRequest doesn’t have return code 200   28 Feb 2016 13:43
tcxedit.js   for more general photo lists   27 Feb 2016 09:18
pix.js   bugfixes   26 Feb 2016 14:40
tcxedit.js   add deltimes; fix some bugs   11 Feb 2016 15:22
tcxedit.js   correct for garmin readability   10 Feb 2016 19:55
tcxedit.js      9 Feb 2016 08:35
tcxedit.js   bugfix for gpx files   9 Feb 2016 08:12
slopingtest.c   better header file   8 Feb 2016 17:03
slopingbw.h   better header file   8 Feb 2016 17:03
tcxedit.js   keep times, allow multiple photos at a waypoint, sundry minor changes   8 Feb 2016 10:51
slopingtest.c   more use of slopingbw.h   8 Feb 2016 10:45
slopingbw.c   new header file   6 Feb 2016 15:46
slopingtest.c   new header file   6 Feb 2016 15:46
ij.h   use generic shellsort   6 Feb 2016 15:26
slopingbw.c   bugfixes   30 Dec 2015 14:17
slopingtest.c   bugfixes   30 Dec 2015 14:17
tcxedit.js   add maxsep   24 Nov 2015 17:59
tcxedit.js   already optimised → no donesomething   17 Nov 2015 16:07
tcxedit.js   read coursepoints from gpx input   16 Nov 2015 13:47
random.h   modified ranrot prototype   28 Sept 2015 08:34
newuoa.c   fix cond bug   17 Sept 2015 13:36
newuoa.hh   for the archive only   1 Sept 2015 11:42

Archived from optim.html

/* This is NEWUOA for unconstrained minimization. The codes were written by Powell in Fortran and then translated to C with f2c. I further modified the code to make it independent of libf2c and f2c.h. Please refer to "The NEWUOA software for unconstrained optimization without derivatives", which is available at www.damtp.cam.ac.uk, for more information. */ /* The original fortran codes are distributed without restrictions. The C++ codes are distributed under MIT license. */ /* The MIT License Copyright (c) 2004, by M.J.D. Powell <mjdp@cam.ac.uk> 2008, by Attractive Chaos <attractivechaos@aol.co.uk> 2015, by Colin Champion <colin.champion@masterlyinactivity.com> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "memory.h" #include <math.h> #define pi 3.141592653589793 static int cond = -1 ; int uoacond() { return cond ; } int update(int,int,double **,double **,int,double *,double,int) ; void shiftxbase(int n,int npt,double **xp,double *xopt,double *vlag,double **b, double **z,double *pq,double *hq,int idz) ; double calcvlagbeta(int n,int npt,double *vlag,double **z,double **b,double *w, double *xopt,double *d,double beta,double dsq,double xsq, int idz) ; int chooseknew(int n,int npt,double beta,double **z,double *vlag,double **xp, double *xopt,double detrat,double rhosq,int ktemp,int idz) ; double genpqw(int n,int npt,double *pqw,double **z,int knew,int idz) ; void updatehq(int n,int npt,double *hq,double pqknew,double **xpt,int knew) ; void updategopt(int n,double *gopt,double *hq,double *step) ; void updategopt2(int n,int npt,double *gopt,double *xopt,double *pq,double **) ; /* -------------------------------------------------------------------------- */ static double biglag(int n,int npt,double *xopt,double **xp,double **b, double **z,int idz,int knew,double delta,double *d) { /* N is the number of variables. NPT is the number of interpolation * equations. XOPT is the best interpolation point so far. XPT * contains the coordinates of the current interpolation * points. BMAT provides the last N columns of H. ZMAT and IDZ give * a factorization of the first NPT by NPT submatrix of H. NDIM is * the first dimension of BMAT and has the value NPT+N. KNEW is the * index of the interpolation point that is going to be moved. DELTA * is the current trust region bound. D will be set to the step from * XOPT to the new point. ALPHA will be set to the KNEW-th diagonal * element of the H matrix. HCOL, GC, GD, S and W will be used for * working space. */ /* The step D is calculated in a way that attempts to maximize the * modulus of LFUNC(XOPT+D), subject to the bound ||D|| <= DELTA, * where LFUNC is the KNEW-th Lagrange function. */ int i,j,k,iterc,isave ; double sp,ss,cf1,cf2,cf3,cf4,cf5,dhd,cth,tau,sth,sum,temp,step,angle,scale ; double denom,delsq,tempa,tempb,taubeg,tauold,taumax,dd,gg,alpha ; double *hcol=vector(npt),*w=vector(n),*s=vector(n),*gd=vector(n) ; double *gc=vector(n) ; delsq = delta * delta ; /* Set the first NPT components of HCOL to the leading elements of * the KNEW-th column of H. */ alpha = genpqw(n,npt,hcol,z,knew,idz) ; /* Set the unscaled initial direction D. Form the gradient of LFUNC * atXOPT, and multiply D by the second derivative matrix of LFUNC. */ for(dd=i=0;i<n;i++) { d[i] = xp[knew][i] - xopt[i] ; gc[i] = b[i][knew] ; dd += d[i]*d[i] ; } for(k=0;k<npt;k++) { for(temp=sum=j=0;j<n;j++) { temp += xp[k][j] * xopt[j] ; sum += xp[k][j] * d[j] ; } temp *= hcol[k] ; sum *= hcol[k] ; for(i=0;i<n;i++) { gc[i] += temp * xp[k][i] ; gd[i] += sum * xp[k][i] ; } } /* Scale D and GD, with a sign change if required. Set S to another * vector in the initial two dimensional subspace. */ for(gg=sp=dhd=i=0;i<n;i++) { gg += gc[i] * gc[i] ; sp += d[i] * gc[i] ; dhd += d[i] * gd[i] ; } scale = delta / sqrt(dd) ; if(sp*dhd<0) scale = -scale ; if(sp*sp>dd*.99*gg) temp = 1 ; else temp = 0 ; tau = scale * (fabs(sp)+0.5*scale*fabs(dhd)) ; if(gg*delsq<tau*.01*tau) temp = 1 ; for(i=0;i<n;i++) { d[i] *= scale ; gd[i] *= scale ; s[i] = gc[i] + temp*gd[i] ; } /* Begin the iteration by overwriting S with a vector that has the * required length and direction, except that termination occurs if * the given D and S are nearly parallel. */ for(iterc=0;iterc<n;iterc++) { for(dd=sp=ss=i=0;i<n;i++) { dd += d[i] * d[i] ; sp += d[i] * s[i] ; ss += s[i] * s[i] ; } temp = dd*ss - sp*sp ; if(temp<=dd*1e-8*ss) break ; denom = sqrt(temp); for(i=0;i<n;i++) { s[i] = (dd*s[i]-sp*d[i]) / denom ; w[i] = 0 ; } /* Calculate the coefficients of the objective function on the * circle, beginning with the multiplication of S by the second * derivative matrix. */ for(k=0;k<npt;k++) { for(sum=j=0;j<n;j++) sum += xp[k][j] * s[j] ; sum *= hcol[k] ; for(j=0;j<n;j++) w[j] += sum * xp[k][j] ; } for(cf1=cf2=cf3=cf4=cf5=i=0;i<n;i++) { cf1 += s[i] * w[i]; cf2 += d[i] * gc[i]; cf3 += s[i] * gc[i]; cf4 += d[i] * gd[i]; cf5 += s[i] * gd[i]; } cf1 /= 2 ; cf4 = cf4/2 - cf1 ; /* Seek the value of the angle that maximizes the modulus of TAU. */ taubeg = cf1 + cf2 + cf4 ; taumax = tauold = taubeg; temp = 2 * pi / 50.0 ; for(isave=0,i=1;i<50;tauold=tau,i++) { angle = (double) i*temp; cth = cos(angle) ; sth = sin(angle) ; tau = cf1 + (cf2 + cf4*cth) * cth + (cf3 + cf5*cth) * sth ; if(fabs(tau)>fabs(taumax)) { taumax = tau ; isave = i ; tempa = tauold ; } else if(i==isave+1) tempb = tau ; } if(isave==0) tempa = tau ; else if(isave==49) tempb = taubeg ; step = 0 ; if(tempa!=tempb) { tempa -= taumax ; tempb -= taumax ; step = 0.5 * (tempa-tempb) / (tempa+tempb) ; } angle = temp * (isave+step) ; /* Calculate the new D and GD. Then test for convergence. */ cth = cos(angle) ; sth = sin(angle) ; tau = cf1 + (cf2 + cf4*cth) * cth + (cf3 + cf5*cth) * sth ; for(i=0;i<n;i++) { d[i] = cth*d[i] + sth*s[i] ; gd[i] = cth*gd[i] + sth*w[i] ; s[i] = gc[i] + gd[i] ; } if(fabs(tau)<=fabs(taubeg)*1.1) break ; } free(hcol,w,s,gd,gc) ; return alpha ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------------------------------------------------- */ static double bigden(int n,int npt,double *xopt,double **xp,double **b, double **z,int idz,int kopt,int knew, double *d,double *w, double *vlag) { /* N is the number of variables. * NPT is the number of interpolation equations. * XOPT is the best interpolation point so far. * XPT contains the coordinates of the current interpolation points. * BMAT provides the last N columns of H. * ZMAT and IDZ give a factorization of the first NPT by NPT submatrix of H. * NDIMis the first dimension of BMAT and has the value NPT+N. * KOPT is the index of the optimal interpolation point. * KNEW is the index of the interpolation point that is going to be moved. * D will be set to the step from XOPT to the new point, and on entry it * should be the D that was calculated by the last call of BIGLAG. The length of the initial D provides a trust region bound on the final D. * W will be set to Wcheck for the final choice of D. * VLAG will be set to Theta*Wcheck+e_b for the final choice of D. * D is calculated in a way that should provide a denominator with a large modulus in the updating formula when the KNEW-th interpolation point is shifted to the new position XOPT+D. * the RETURN value beta will be found in the updating formula when the KNEW-th interpolation point is moved to its new position. */ int i,j,k,isave,iterc,jc,nw,ksav,ndim=npt+n ; double dd,ds,ss,den[9],par[9],tau,sum,diff,temp,step,beta,alpha,angle ; double denex[9],tempa,tempb,tempc,ssden,dtest,xoptd,xopts,denold,denmax ; double densav,dstemp,sumold,sstemp,xoptsq ; double **prod=matrix(ndim,5),**wvec=matrix(ndim,5),*s=vector(n) ; /* Store the first NPT elements of the KNEW-th column of H in W(N+1) * to W(N+NPT). */ alpha = genpqw(n,npt,w+n,z,knew,idz) ; /* The initial search direction D is taken from the last call of * BIGBDLAG, and the initial S is set below, usually to the direction * from X_OPT to X_KNEW, but a different direction to an * interpolation point may be chosen, in order to prevent S from * being nearly parallel to D. */ for(dd=ds=ss=i=0;i<n;i++) { dd += d[i]*d[i] ; s[i] = xp[knew][i] - xopt[i] ; ds += d[i] * s[i] ; ss += s[i] * s[i] ; } if(ds*ds>dd*.99*ss) { ksav = knew ; dtest = ds * ds / ss ; for(k=0;k<npt;k++) if(k!=kopt) { for(dstemp=sstemp=i=0;i<n;i++) { diff = xp[k][i] - xopt[i] ; dstemp += d[i] * diff ; sstemp += diff*diff ; } if(dstemp*dstemp/sstemp<dtest) { ksav = k ; dtest = dstemp * dstemp / sstemp ; ds = dstemp ; ss = sstemp ; } } for(i=0;i<n;i++) s[i] = xp[ksav][i] - xopt[i] ; } ssden = dd*ss - ds*ds ; densav = 0 ; /* Begin the iteration by overwriting S with a vector that has the * required length and direction. */ /* ------------------------------------------------------------------------ */ for(iterc=1;;iterc++) { temp = 1.0 / sqrt(ssden) ; for(xoptsq=xoptd=xopts=i=0;i<n;i++) { s[i] = temp * (dd*s[i]-ds*d[i]) ; xoptd += xopt[i] * d[i] ; xopts += xopt[i] * s[i] ; xoptsq += xopt[i] * xopt[i] ; } /* Set the coefficients of the first 2 terms of BETA. */ tempa = 0.5 * xoptd * xoptd ; tempb = 0.5 * xopts * xopts ; den[0] = dd * (xoptsq + 0.5*dd) + tempa + tempb ; den[1] = 2 * xoptd * dd ; den[2] = 2 * xopts * dd ; den[3] = tempa - tempb ; den[4] = xoptd * xopts ; for(i=4;i<8;i++) den[i+1] = 0 ; /* Put the coefficients of Wcheck in WVEC. */ for(k=0;k<npt;k++) { for(tempa=tempb=tempc=i=0;i<n;i++) { tempa += xp[k][i] * d[i]; tempb += xp[k][i] * s[i]; tempc += xp[k][i] * xopt[i]; } wvec[k][0] = 0.25 * (tempa*tempa + tempb*tempb) ; wvec[k][1] = tempa * tempc ; wvec[k][2] = tempb * tempc ; wvec[k][3] = 0.25 * (tempa*tempa - tempb*tempb) ; wvec[k][4] = 0.5 * tempa * tempb ; } for(i=0;i<n;i++) { wvec[i+npt][0] = 0 ; wvec[i+npt][1] = d[i] ; wvec[i+npt][2] = s[i] ; wvec[i+npt][3] = 0 ; wvec[i+npt][4] = 0 ; } /* Put the coefficents of THETA*Wcheck in PROD. */ for(jc=0;jc<5;jc++) { if(jc==2||jc==3) nw = ndim ; else nw = npt ; for(k=0;k<npt;k++) prod[k][jc] = 0 ; for(j=0;j<npt-n-1;j++) { for(sum=k=0;k<npt;k++) sum += z[j][k] * wvec[k][jc] ; if(j<idz) sum = -sum ; for(k=0;k<npt;k++) prod[k][jc] += sum * z[j][k] ; } if(nw==ndim) for(k=0;k<npt;k++) { for(sum=j=0;j<n;j++) sum += b[j][k] * wvec[npt+j][jc] ; prod[k][jc] += sum ; } for(j=0;j<n;j++) { for(sum=i=0;i<nw;i++) sum += b[j][i] * wvec[i][jc] ; prod[npt+j][jc] = sum ; } } /* Include in DEN the part of BETA that depends on THETA. */ for(k=0;k<ndim;k++) { for(sum=i=0;i<5;i++) sum += ( par[i] = 0.5 * prod[k][i] * wvec[k][i] ) ; den[0] -= par[0] + sum ; tempa = prod[k][0] * wvec[k][1] + prod[k][1] * wvec[k][0] ; tempb = prod[k][1] * wvec[k][3] + prod[k][3] * wvec[k][1] ; tempc = prod[k][2] * wvec[k][4] + prod[k][4] * wvec[k][2] ; den[1] -= tempa + 0.5 * (tempb+tempc) ; den[5] -= 0.5 * (tempb-tempc) ; tempa = prod[k][0] * wvec[k][2] + prod[k][2] * wvec[k][0] ; tempb = prod[k][1] * wvec[k][4] + prod[k][4] * wvec[k][1] ; tempc = prod[k][2] * wvec[k][3] + prod[k][3] * wvec[k][2] ; den[2] -= tempa + 0.5 * (tempb-tempc) ; den[6] -= 0.5 * (tempb+tempc) ; tempa = prod[k][0] * wvec[k][3] + prod[k][3] * wvec[k][0] ; den[3] -= tempa + par[1] - par[2] ; tempa = prod[k][0] * wvec[k][4] + prod[k][4] * wvec[k][0] ; tempb = prod[k][1] * wvec[k][2] + prod[k][2] * wvec[k][1] ; den[4] -= tempa + 0.5 * tempb ; den[7] -= par[3] - par[4] ; tempa = prod[k][3] * wvec[k][4] + prod[k][4] * wvec[k][3] ; den[8] -= 0.5 * tempa ; } /* Extend DEN so that it holds all the coefficients of DENOM. */ for(sum=i=0;i<5;i++) { tempa = prod[knew][i] ; sum += ( par[i] = 0.5 * tempa * tempa ) ; } denex[0] = alpha*den[0] + par[0] + sum ; tempa = 2 * prod[knew][0] * prod[knew][1] ; tempb = prod[knew][1] * prod[knew][3] ; tempc = prod[knew][2] * prod[knew][4] ; denex[1] = alpha*den[1] + tempa + tempb + tempc ; denex[5] = alpha*den[5] + tempb - tempc ; tempa = 2 * prod[knew][0] * prod[knew][2] ; tempb = prod[knew][1] * prod[knew][4] ; tempc = prod[knew][2] * prod[knew][3] ; denex[2] = alpha*den[2] + tempa + tempb - tempc ; denex[6] = alpha*den[6] + tempb + tempc ; tempa = 2 * prod[knew][0] * prod[knew][3] ; denex[3] = alpha*den[3] + tempa + par[1] - par[2] ; tempa = 2 * prod[knew][0] * prod[knew][4] ; denex[4] = alpha*den[4] + tempa + prod[knew][1]*prod[knew][2] ; denex[7] = alpha*den[7] + par[3] - par[4] ; denex[8] = alpha*den[8] + prod[knew][3]*prod[knew][4] ; /* Seek the value of the angle that maximizes the modulus of DENOM. */ sum = denex[0] + denex[1] + denex[3] + denex[5] + denex[7]; denold = denmax = sum ; temp = 2 * pi / 50 ; par[0] = 1 ; for(isave=0,i=1;i<50;i++) { angle = i * temp ; par[1] = cos(angle) ; par[2] = sin(angle) ; for(j=4;j<9;j+=2) { par[j-1] = par[1]*par[j-3] - par[2]*par[j-2] ; par[j] = par[1]*par[j-2] + par[2]*par[j-3] ; } sumold = sum ; for(sum=j=0;j<9;j++) sum += denex[j]*par[j] ; if(fabs(sum)>fabs(denmax)) { denmax = sum ; isave = i ; tempa = sumold ; } else if(i==isave+1) tempb = sum ; } if(isave==0) tempa = sum ; else if(isave==49) tempb = denold ; step = 0 ; if(tempa!=tempb) { tempa -= denmax ; tempb -= denmax ; step = 0.5 * (tempa-tempb) / (tempa+tempb) ; } /* Calculate the new parameters of the denominator, the new VLAG * vector and the new D. Then test for convergence. */ angle = temp * (isave+step) ; par[1] = cos(angle) ; par[2] = sin(angle) ; for(j=4;j<9;j+=2) { par[j-1] = par[1]*par[j-3] - par[2]*par[j-2] ; par[j] = par[1]*par[j-2] + par[2]*par[j-3] ; } for(denmax=beta=j=0;j<9;j++) { beta += den[j]*par[j] ; denmax += denex[j]*par[j] ; } for(k=0;k<ndim;k++) for(vlag[k]=j=0;j<5;j++) vlag[k] += prod[k][j] * par[j] ; tau = vlag[knew] ; for(dd=tempa=tempb=i=0;i<n;i++) { d[i] = par[1]*d[i] + par[2]*s[i] ; w[i] = xopt[i] + d[i] ; dd += d[i] * d[i] ; tempa += d[i] * w[i] ; tempb += w[i] * w[i] ; } if(iterc>=n) break ; if(iterc>1) densav = fmax(densav, denold) ; if(fabs(denmax)<=fabs(densav)*1.1) break ; densav = denmax ; /* Set S to 0.5 the gradient of the denominator with respect to * D. Then branch for the next iteration. */ for(i=0;i<n;i++) { temp = tempa*xopt[i] + tempb*d[i] - vlag[npt+i] ; s[i] = tau*b[i][knew] + alpha*temp ; } for(k=0;k<npt;k++) { for(sum=j=0;j<n;j++) sum += xp[k][j] * w[j] ; temp = (tau*w[n+k]-alpha*vlag[k]) * sum ; for(i=0;i<n;i++) s[i] += temp * xp[k][i] ; } for(ss=ds=i=0;i<n;i++) { ss += s[i]*s[i] ; ds += d[i]*s[i] ; } ssden = dd*ss - ds*ds ; if(ssden<dd*1e-8*ss) break ; } /* Set the vector W before the RETURN from the subroutine. */ for(k=0;k<ndim;k++) for(w[k]=j=0;j<5;j++) w[k] += wvec[k][j] * par[j] ; vlag[kopt] += 1 ; freematrix(prod,wvec) ; free(s) ; return beta ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------------------------------------------------- */ /* The following instructions set the vector HD to the vector D multiplied by the second derivative matrix of Q. */ static void sethd(double *hd,int n,double **xp,double *d,double *hq,double *pq,int npt) { int i,j,k ; double temp ; for(i=0;i<n;i++) hd[i] = 0 ; updategopt(n,hd,hq,d) ; updategopt2(n,npt,hd,d,pq,xp) ; } /* -------------------------------------------------------------------------- */ static double trsapp(int n, int npt, double *xopt, double **xp, double *gq, double *hq, double *pq,double delsq, double *step) { /* The arguments NPT, XOPT, XPT, GQ, HQ and PQ have their usual meanings, * in order to define the current quadratic model Q. * DELTA is the trust region radius, and has to be positive. STEP * will be set to the calculated trial step. CRVMIN will be returned as the * least curvature of H aint the conjugate directions that occur, * except that it is set to 0 ifSTEP goes all the way to the trust * region boundary. The calculation of STEP begins with the * truncated conjugate gradient method. If the boundary of the trust * region is reached, then further changes to STEP may be made, each * one being in the 2D space spanned by the current STEP and the * corresponding gradient of Q. Thus STEP should provide a * substantial reduction to Q within the trust region. */ int i,j,iterc,isave,itermax ; double dd,cf,dg,gg,ds,sg,ss,dhd,dhs,cth,sgk,shs,sth,qadd,qbeg,qred,qmin ; double temp,qsav,qnew,ggbeg,alpha,angle,reduc,ggsav,tempa,tempb,bstep ; double ratio,angtest,crvmin ; double *d=vector(n),*g=vector(n),*hd=vector(n),*hs=vector(n) ; itermax = n ; for(i=0;i<n;i++) d[i] = xopt[i] ; sethd(hd,n,xp,d,hq,pq,npt) ; /* Prepare for the first line search. */ for(qred=dd=i=0;i<n;i++) { step[i] = hs[i] = 0 ; g[i] = gq[i] + hd[i] ; d[i] = -g[i] ; dd += d[i]*d[i] ; } if(dd==0) { free(d,g,hd,hs) ; return 0 ; } ggbeg = gg = dd ; /* ------------------------------------------------------------------------ */
/*page*/ /* -- Calculate the step to the trust region boundary and the product HD -- */ for(ds=ss=0,iterc=1;ss<delsq;iterc++) { temp = delsq - ss; bstep = temp / (ds + sqrt(ds * ds + dd * temp)); sethd(hd,n,xp,d,hq,pq,npt) ; for(dhd=j=0;j<n;j++) dhd += d[j] * hd[j] ; /* Update CRVMIN and set the step-length ALPHA. */ alpha = bstep ; if(dhd>0) { if(iterc==1) crvmin = dhd/dd ; else crvmin = fmin(crvmin,dhd/dd) ; alpha = fmin(alpha,gg/dhd) ; } qred += ( qadd = alpha * (gg - 0.5*alpha*dhd) ) ; /* Update STEP and HS. */ for(ggsav=gg,gg=i=0;i<n;i++) { step[i] += alpha * d[i] ; hs[i] += alpha * hd[i] ; temp = g[i] + hs[i] ; gg += temp * temp ; } /* Begin another conjugate direction iteration if required. */ if(alpha>=bstep) break ; ds = 0 ; if(qadd>qred*.01&&gg>ggbeg*1e-4&&iterc<itermax) { temp = gg / ggsav ; for(dd=ss=i=0;i<n;i++) { d[i] = temp*d[i] - g[i] - hs[i] ; dd += d[i]*d[i] ; ds += d[i]*step[i] ; ss += step[i]*step[i] ; } } if(ds<=0) { free(d,g,hd,hs) ; return crvmin ; } } /* ------------------------------------------------------------------------ */
/*page*/ /* ------------ Test whether an alternative iteration is required --------- */ for(iterc++;gg>ggbeg*1e-4;iterc++) { for(sg=shs=i=0;i<n;i++) { sg += step[i]*g[i] ; shs += step[i]*hs[i] ; } sgk = sg + shs ; angtest = sgk / sqrt(gg*delsq) ; if(angtest<=-.99) break ; /* Begin the alternative iteration by calculating D and HD and some * scalar products. */ temp = sqrt(delsq*gg - sgk*sgk) ; tempa = delsq / temp ; tempb = sgk / temp ; for(i=0;i<n;i++) d[i] = tempa*(g[i]+hs[i]) - tempb*step[i] ; sethd(hd,n,xp,d,hq,pq,npt) ; for(dg=dhd=dhs=i=0;i<n;i++) { dg += d[i]*g[i] ; dhd += hd[i]*d[i] ; dhs += hd[i]*step[i] ; } /* Seek the value of the angle that minimizes Q. */ cf = 0.5 * (shs-dhd) ; qbeg = sg + cf ; qsav = qmin = qbeg ; temp = 2 * pi / 50 ; for(isave=0,i=1;i<50;i++) { angle = i * temp ; cth = cos(angle) ; sth = sin(angle) ; qnew = (sg+cf*cth)*cth + (dg+dhs*cth)*sth ; if(qnew<qmin) { qmin = qnew ; isave = i ; tempa = qsav ; } else if(i==isave+1) tempb = qnew ; qsav = qnew ; } if(isave==0) tempa = qnew ; else if(isave==49) tempb = qbeg ; angle = 0 ; if(tempa!=tempb) { tempa -= qmin ; tempb -= qmin ; angle = 0.5 * (tempa-tempb) / (tempa+tempb) ; } angle = temp * (isave+angle) ; /* Calculate the new STEP and HS. Then test for convergence. */ cth = cos(angle) ; sth = sin(angle) ; reduc = qbeg - (sg + cf * cth) * cth - (dg + dhs * cth) * sth ; for(gg=i=0;i<n;i++) { step[i] = cth*step[i] + sth*d[i] ; hs[i] = cth*hs[i] + sth*hd[i] ; temp = g[i] + hs[i] ; gg += temp*temp ; } qred += reduc ; ratio = reduc / qred ; if(iterc>=itermax||ratio<=0.01) break ; } free(d,g,hd,hs) ; return 0 ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------------------------------------------------- */ static double setrhodelta(double *rho,double rhoend,double *delta) { double ratio=rho[0]/rhoend ; delta[0] = rho[0] / 2 ; if(ratio<=16) rho[0] = rhoend ; else if(ratio<=250) rho[0] = sqrt(ratio) * rhoend ; else rho[0] *= 0.1 ; delta[0] = fmax(delta[0],rho[0]) ; return ratio ; } static double setknewdistsq (int n,int npt,double delta,int *knew,double *xopt,double **xp) { int j,k ; double sum,temp,distsq=4*delta*delta ; for(k=0;k<npt;k++) { for(sum=j=0;j<n;j++) { temp = xp[k][j] - xopt[j] ; sum += temp*temp ; } if(sum>distsq) { knew[0] = k ; distsq = sum ; } } return distsq ; } /* -------------------------------------------------------------------------- */ double uoamin(double (*func)(double*),double *x,int n, double rhobeg,double rhoend,int maxfun) { /* This subroutine seeks the least value of a function of many * variables, by a trust region method that forms quadratic models * by interpolation. There can be some freedom in the interpolation * conditions, which is taken up by minimizing the Frobenius norm of * the change to the second derivative of the quadratic model, * beginning with a zero matrix. The arguments of the subroutine are * as follows. */ /* N must be set to the number of variables and must be at least * two. NPT is the number of interpolation conditions. Its value * must be in the interval [N+2,(N+1)(N+2)/2]. Initial values of the * variables must be set in X(1),X(2),...,X(N). They will be changed * to the values that give the least calculated F. xinc and tol * must be set to the initial and final values of a trust region * radius, so both must be positive with tol<=xinc. Typically * xinc should be about one tenth of the greatest expected change * to a variable, and tol should indicate the accuracy that is * required in the final values of the variables. MAXFUN must be set * to an upper bound on the number of calls of CALFUN. */ /* SUBROUTINE func(x) must be provided by the user. */ /* XBASE will hold a shift of origin that should reduce the contributions * from rounding errors to values of the model and Lagrange functions. * XOPT will be set to the displacement from XBASE of the vector of variables that provides the least calculated F so far. * XNEW will be set to the displacement from XBASE of the vector of variables for the current calculation of F. */ /* ------------------------------------------------------------------------ */
/*page*/ /* ------------------------------------------------------------------------ */ /* XPT will contain the interpolation point coordinates relative to XBASE. * FVAL will hold the values of F at the interpolation points. * GQ will hold the gradient of the quadratic model at XBASE. * HQ will hold the explicit second derivatives of the quadratic model. * PQ will contain the parameters of the implicit second derivatives of the quadratic model. * BMAT will hold the last N columns of H. * ZMAT will hold the factorization of the leading NPT by NPT submatrix * of H, this factorization being ZMAT times Diag(DZ) times ZMAT^T, * where the elements of DZ are plus or minus 1.0, asspecified by IDZ. * NDIM is the first dimension of BMAT and has the value NPT+N. * D is reserved for trial steps from XOPT. * VLAG will contain the values of the Lagrange functions at a new point X. * They are part of a product that requires VLAG to be of length NDIM. */ int i,j,k,ih,nf,nh,nfm,idz,ipt,jpt,nfmm,knew,kopt,nptm,ksave,nfsav,itemp ; int ktemp,itest,npt=2*n+1 ; double f,dsq,rho,sum,fbeg,diff,beta,gisq,temp,suma,sumb,fopt,gqsq ; double xipt,xjpt,diffa,diffb,diffc,alpha,delta,recip,reciq,fsave ; double dnorm,ratio,dstep,vquad,detrat,crvmin,distsq,xoptsq ; double *xbase=vector(n),*xopt=vector(n),*xnew=vector(n),*fval=vector(npt) ; double *gq=vector(n),*hq=vector((n*(n+1))/2),*pq=vector(npt),*d=vector(n) ; double *vlag=vector(npt+n),*w=vector(2*npt),*pqw=vector(npt) ; double **z=matrix(npt-(n+1),npt),**b=matrix(n,npt+n),**xp=matrix(npt,n) ; nh = (n*(n+1)) / 2 ; nptm = npt - (n+1) ; if(maxfun<1) maxfun = 1 ; for(j=0;j<n;j++) xbase[j] = x[j] ; /* Begin the initialization procedure. NF becomes 1 more than the * number of function values so far. The coordinates of the * displacement of the next initial interpolation point from XBASE * are set in XPT(NF,.). */ recip = 1 / (rhobeg*rhobeg) ; reciq = sqrt(0.5) * recip ; /* ------------------------------------------------------------------------ */
/*page*/ /* ------------------------------------------------------------------------ */ for(nf=0;nf<npt;nf++) { nfm = nf-1 ; nfmm = nfm-n ; if(nfm<2*n) // always happens when npt = 2*n+1 { if(nfm>=0&&nfm<n) xp[nf][nfm] = rhobeg ; else if(nfm>=n) xp[nf][nfmm] = -rhobeg ; } else // the case which never happens { itemp = nfmm / n ; jpt = nfm - itemp * n - n ; ipt = jpt + itemp ; if(ipt>=n) { ipt -= n ; swap(ipt,jpt) ; } xipt = rhobeg ; if(fval[ipt+n+1] < fval[ipt+1]) xipt = -xipt ; xjpt = rhobeg ; if(fval[jpt+n+1] < fval[jpt+1]) xjpt = -xjpt ; xp[nf][ipt] = xipt ; xp[nf][jpt] = xjpt ; } /* Calculate the next value of F. The least function value so far and its * index are required. */ for(j=0;j<n;j++) x[j] = xp[nf][j] + xbase[j]; fval[nf] = f = func(x) ; if(nf==0) { fbeg = fopt = f ; kopt = 0 ; } else if(f<fopt) { fopt = f ; kopt = nf ; } /* Set the non0 initial elements of BMAT and the quadratic model * in the cases when NF is at most 2*N+1. */ if(nfm<2*n) { if(nfm>=0&&nfm<n) { gq[nfm] = (f-fbeg) / rhobeg ; if(npt<=nf+n) { b[nfm][0] = -1/rhobeg ; b[nfm][nf] = 1/rhobeg ; b[nfm][npt+nfm-1] = -(rhobeg*rhobeg)/2 ; } } else if(nfm>=n) { b[nfmm][nf-n] = 1/(2*rhobeg) ; b[nfmm][nf] = -1/(2*rhobeg) ; z[nfmm][0] = -(reciq+reciq) ; z[nfmm][nf] = z[nfmm][nf-n] = reciq ; ih = (nfmm * (nfmm+3)) / 2 ; temp = (fbeg-f) / rhobeg ; hq[ih] = (gq[nfmm]-temp) / rhobeg; gq[nfmm] = .5 * (gq[nfmm] + temp); } } /* Set the off-diagonal second derivatives of the Lagrange * functions and the initial quadratic model. */ else { ih = (ipt*(ipt+1))/2 + jpt ; if(xipt<0) ipt += n ; if(xjpt<0) jpt += n ; z[nfmm][0] = z[nfmm][nf] = recip ; z[nfmm][ipt] = z[nfmm][jpt] = -recip ; hq[ih] = (fbeg - fval[ipt+1] - fval[jpt+1] + f) / (xipt*xjpt) ; } } /* ------------------------------------------------------------------------ */
/*page*/ /* - Begin the iterative procedure, because the initial model is complete - */ diffa = diffb = itest = idz = 0 ; for(i=0;i<n;i++) xopt[i] = xp[kopt][i] ; /* Generate the next trust region step and test its length. Set KNEW * to -1 ifthe purpose of the next F will be to improve the * model. */ /* ------------------------------------------------------------------------ */ for(knew=-1,delta=rho=rhobeg,nfsav=nf;;) { if(knew<0) for(;;) { crvmin = trsapp(n,npt,xopt,xp,gq,hq,pq,delta*delta,d) ; for(dsq=i=0;i<n;i++) dsq += d[i]*d[i] ; dnorm = fmin(delta,sqrt(dsq)) ; if(dnorm>=rho/2) break ; // knew must be -1 delta /= 10 ; ratio = -1 ; if(delta<=rho*1.5) delta = rho ; temp = crvmin * .125 * rho * rho ; if(nf<=nfsav+2||temp<=diffa||temp<=diffb||temp<=diffc) { distsq = setknewdistsq(n,npt,delta,&knew,xopt,xp) ; /* If KNEW is positive, then set DSTEP, and branch back for the next * iteration, which will generate a "model step". */ if(knew>=0) { dstep = fmax(rho,fmin(sqrt(distsq)/10,delta/2)) ; dsq = dstep*dstep ; break ; } else if(delta>rho||dnorm>rho) continue ; } /* Return from the calculation, after another Newton-Raphson step, * if it is too short to have been tried before. */ if(rho<=rhoend) { for(i=0;i<n;i++) { xnew[i] = xopt[i] + d[i] ; x[i] = xbase[i] + xnew[i] ; } f = func(x) ; knew = -2 ; break ; } /* The calculations with the current value of RHO are complete. Pick * the next values of RHO and DELTA. */ ratio = setrhodelta(&rho,rhoend,&delta) ; nfsav = nf ; } if(knew==-2) { cond = 0 ; break ; } /* ---------------------------------------------------------------------- */
/*page*/ /* ---------------------------------------------------------------------- */ /* Shift XBASE if XOPT may be too far from XBASE. First make the * changes to BMAT that do not depend on ZMAT. */ for(xoptsq=i=0;i<n;i++) xoptsq += xopt[i] * xopt[i] ; if(dsq<=xoptsq/1000) { updategopt(n,gq,hq,xopt) ; updategopt2(n,npt,gq,xopt,pq,xp) ; shiftxbase(n,npt,xp,xopt,vlag,b,z,pq,hq,idz) ; for(j=0;j<n;j++) { xbase[j] += xopt[j] ; xopt[j] = 0 ; } xoptsq = 0 ; } /* Pick the model step ifKNEW is positive. A different choice of D * may be made later, ifthe choice of D by BIGLAG causes * substantial cancellation in DENOM. */ if(knew>=0) alpha = biglag(n,npt,xopt,xp,b,z,idz,knew,dstep,d) ; /* Calculate VLAG and BETA for the current choice of D. The first * NPT components of W_check will be held in W. */ for(k=0;k<npt;k++) { for(vlag[k]=suma=sumb=j=0;j<n;j++) { suma += xp[k][j] * d[j] ; sumb += xp[k][j] * xopt[j] ; vlag[k] += b[j][k] * d[j] ; } w[k] = suma * (suma/2+sumb) ; } /* ---------------------------------------------------------------------- */
/*page*/ /* ---------------------------------------------------------------------- */ beta = calcvlagbeta(n,npt,vlag,z,b,w,xopt,d,beta,dsq,xoptsq,idz) ; vlag[kopt] += 1 ; /* If KNEW is positive and if the cancellation in DENOM is unacceptable, * then BIGDEN calculates an alternative model step. */ if(knew>=0&&fabs(1 + alpha*beta / (vlag[knew]*vlag[knew]))<=0.8) beta = bigden(n,npt,xopt,xp,b,z,idz,kopt,knew,d,w,vlag) ; /* Calculate the next value of the objective function. */ if(nf+1>=maxfun) { cond = 2 ; break ; } for(i=0;i<n;i++) { xnew[i] = xopt[i] + d[i] ; x[i] = xbase[i] + xnew[i] ; } nf += 1 ; f = func(x) ; /* Use the quadratic model to predict the change in F due to the * step D, and set DIFF to the error of this prediction. */ for(vquad=ih=j=0;j<n;j++) { vquad += d[j] * gq[j] ; for(i=0;i<=j;ih++,i++) { temp = d[i]*xnew[j] + d[j]*xopt[i] ; if(i==j) temp /= 2 ; vquad += temp * hq[ih] ; } } for(k=0;k<npt;k++) vquad += pq[k] * w[k] ; diff = f - fopt - vquad ; diffc = diffb ; diffb = diffa ; diffa = fabs(diff) ; if(dnorm>rho) nfsav = nf ; /* Update FOPT and XOPT ifthe new F is the least value of the * objective function so far. The branch when KNEW is positive * occurs ifD is not a trust region step. */ fsave = fopt ; if(f<fopt) { fopt = f ; for(i=0;i<n;i++) xopt[i] = xnew[i] ; } ksave = knew ; if(knew<0) { /* Pick the next value of DELTA after a trust region step. */ if(vquad>=0) { cond = 1 ; break ; } ratio = (f-fsave) / vquad ; if(ratio<=0.1) delta = dnorm/2 ; else if(ratio<=.7) delta = fmax(delta/2,dnorm) ; else delta = fmax(delta/2,2*dnorm) ; if(delta<=rho*1.5) delta = rho ; /* Set KNEW to the index of the next interpolation point to be * deleted. */ temp = fmax(delta/10,rho) ; if(f>=fsave) { ktemp = kopt ; detrat = 1 ; } else { ktemp = -1 ; detrat = 0 ; } knew = chooseknew(n,npt,beta,z,vlag,xp,xopt,detrat,temp*temp,ktemp,idz) ; } if(knew>=0) { /* Update BMAT, ZMAT and IDZ, so that the KNEW-th interpolation * point can be moved. Begin the updating of the quadratic model, * starting with the explicit second derivative term. */ idz = update(n,npt,b,z,idz,vlag,beta,knew) ; /* Update the other second derivative parameters, and then the * gradient vector of the model. Also include the new interpolation * point. */ updatehq(n,npt,hq,pq[knew],xp,knew) ; pq[knew] = 0 ; genpqw(n,npt,pqw,z,knew,idz) ; for(k=0;k<npt;k++) pq[k] += diff * pqw[k] ; fval[knew] = f ; for(gqsq=i=0;i<n;i++) { gq[i] += diff * b[i][knew]; gqsq += gq[i] * gq[i]; xp[knew][i] = xnew[i]; } /* If a trust region step makes a small change to the objective * function, then calculate the gradient of the least Frobenius norm * interpolant at XBASE, and store it in W, using VLAG for a vector * of right hand sides. */ if(ksave==-1&&delta==rho&&fabs(ratio)>.01) itest = 0 ; else if(ksave==-1&&delta==rho) { for(k=0;k<npt;k++) vlag[k] = fval[k] - fval[kopt] ; for(gisq=i=0;i<n;i++) { for(w[i]=k=0;k<npt;k++) w[i] += b[i][k] * vlag[k] ; gisq += w[i]*w[i] ; } itest += 1 ; if(gqsq<gisq*100) itest = 0 ; if(itest>=3) { for(i=0;i<n;i++) gq[i] = w[i] ; for(ih=0;ih<nh;ih++) hq[ih] = 0 ; for(j=0;j<nptm;j++) { for(w[j]=k=0;k<npt;k++) w[j] += vlag[k] * z[j][k] ; if(j<idz) w[j] = -w[j] ; } for(k=0;k<npt;k++) for(pq[k]=j=0;j<nptm;j++) pq[k] += z[j][k] * w[j]; itest = 0 ; } } if(f<fsave) kopt = knew ; knew = -1 ; /* If a trust region step has provided a sufficient decrease in F, * then branch for another trust region calculation. The case * KSAVE>0 occurs when the new function value was calculated by a * model step. */ if(f<=fsave+vquad/10||ksave>=0) continue ; /* Alternatively, find out ifthe interpolation points are close * enough to the best point so far. */ } distsq = setknewdistsq(n,npt,delta,&knew,xopt,xp) ; /* If KNEW is positive, then set DSTEP, and branch back for the next * iteration, which will generate a "model step". */ if(knew>=0) { dstep = fmax(rho,fmin(sqrt(distsq)/10,delta/2)) ; dsq = dstep*dstep ; } else if(ratio<=0&&delta<=rho&&dnorm<=rho) /* The calculations with the current value of RHO are complete. Pick * the next values of RHO and DELTA. */ { if(rho<=rhoend) { cond = 0 ; break ; } ratio = setrhodelta(&rho,rhoend,&delta) ; nfsav = nf ; } } /* Return from the calculation, after another Newton-Raphson step, * if it is too short to have been tried before. */ if(fopt<=f) { for(i=0;i<n;i++) x[i] = xbase[i] + xopt[i] ; f = fopt ; } free(xbase,xopt,xnew,w,fval,gq) ; free(hq,pq,d,vlag,pqw) ; freematrix(z,b,xp) ; return f ; } /* -------------------------------------------------------------------------- */ static double(*f)(double*) ; static double myfunc(double *x) { return -f(x) ; } double uoamax(double (*func)(double*),double *x,int n, double xinc,double tol,int max_iter) { f = func ; return -uoamin(myfunc,x,n,xinc,tol,max_iter) ; }

Archived from optim.html

/*
  The original fortran codes are distributed without restrictions. The
  C++ codes are distributed under MIT license.
 */
/* The MIT License

   Copyright (c) 2004, by M.J.D. Powell <mjdp@cam.ac.uk>
                 2008, by Attractive Chaos <attractivechaos@aol.co.uk>
                 2015, by Colin Champion <colin.champion@masterlyinactivity.com>

   Permission is hereby granted, free of charge, to any person obtaining
   a copy of this software and associated documentation files (the
   "Software"), to deal in the Software without restriction, including
   without limitation the rights to use, copy, modify, merge, publish,
   distribute, sublicense, and/or sell copies of the Software, and to
   permit persons to whom the Software is furnished to do so, subject to
   the following conditions:

   The above copyright notice and this permission notice shall be
   included in all copies or substantial portions of the Software.

   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
   MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
   NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
   BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
   ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
   CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
   SOFTWARE.
*/
#include"memory.h"
#include <math.h>
#define max(a,b) ((a)>(b)?(a):(b))
#define min(a,b) ((a)<(b)?(a):(b))

void updategopt(int n,double *gopt,double *hq,double *step)
{ int i,j,ih ; 
  for(ih=j=0;j<n;j++) for(i=0;i<=j;i++,ih++) 
  { if(i<j) gopt[j] += hq[ih] * step[i] ; gopt[i] += hq[ih] * step[j] ; }
}
void updategopt2(int n,int npt,double *gopt,double *xopt,double *pq,double **xp)
{ int i,k ;
  double sum,temp ; 
  for(k=0;k<npt;k++)
  { for(sum=i=0;i<n;i++) sum += xp[k][i] * xopt[i];
    temp = pq[k] * sum ;
    for(i=0;i<n;i++) gopt[i] += temp * xp[k][i] ;
  }
}
/* -------------------------------------------------------------------------- */

int update(int n,int npt,double **b,double **z,int idz,double *vlag,
           double beta,int knew)
{
    /* The arrays BMAT and ZMAT with IDZ are updated, in order to shift
     * the interpolation point that has index KNEW. On entry, VLAG
     * contains the components of the vector Theta*Wcheck+e_b of the
     * updating formula (6.11), and BETA holds the value of the
     * parameter that has this name. */

  int nptm=npt-n-1,i,j,ja,jb,jl,jp,iflag ;
  double tau,temp,scala,scalb,alpha,denom,tempa,tempb,tausq,sqrtdn ; 
  double *w=vector(npt+n) ;

    /* Apply the rotations that put zeros in the KNEW-th row of ZMAT. */

  for(jl=0,j=1;j<npt-n-1;j++)
  { if(j==idz)  jl = j ;
    else if(z[j][knew]!=0) 
    { tempa = z[jl][knew];
      tempb = z[j][knew];
      temp = sqrt(tempa*tempa+tempb*tempb) ;
      tempa /= temp ;
      tempb /= temp ;
      for(i=0;i<npt;i++)
      { temp = tempa*z[jl][i] + tempb*z[j][i] ;
        z[j][i] = tempa*z[j][i] - tempb*z[jl][i] ;
        z[jl][i] = temp ;
      }
      z[j][knew] = 0;
    }
  }

  /* Put the first NPT components of the KNEW-th column of HLAG into
   * W, and calculate the parameters of the updating formula. */
  if(idz<1) tempa = z[0][knew] ; else tempa = -z[0][knew] ;
  for(i=0;i<npt;i++) w[i] = tempa * z[0][i] ; 
  if(jl>0) for(tempb=z[jl][knew],i=0;i<npt;i++) w[i] += tempb * z[jl][i] ; 

  alpha = w[knew] ;
  tau = vlag[knew] ;
  tausq = tau * tau ;
  denom = alpha*beta + tausq ;
  vlag[knew] -= 1 ;

  /* Complete the updating of ZMAT when there is only 1.0 nonzero
   * element in the KNEW-th row of the new matrix ZMAT, but, ifIFLAG
   * is set to 1.0, then the first column of ZMAT will be exchanged
   * with another 1.0 later. */
  iflag = 0 ;
  sqrtdn = sqrt(fabs(denom)) ;

  /* ------------------------------------------------------------------------ */
  /*page*/
  /* ------------------------------------------------------------------------ */

  if(jl==0)
  { tempa = tau / sqrtdn ; 
    tempb = z[0][knew] / sqrtdn ;
    for(i=0;i<npt;i++) z[0][i] = tempa*z[0][i] - tempb*vlag[i] ;
    if(denom<0) { if(idz==0) idz = 1 ; else iflag = 1 ; }
  }
  else 
  { /* Complete the updating of ZMAT in the alternative case. */
    if(beta>=0) ja = jl ; else ja = 0 ; 
    jb = jl - ja ;
    temp = z[jb][knew] / denom ;
    tempa = temp * beta ;
    tempb = temp * tau ;
    temp = z[ja][knew] ;
    scala = 1 / sqrt(fabs(beta)*temp*temp+tausq) ;
    scalb = scala * sqrtdn ;
    for(i=0;i<npt;i++)
    { z[ja][i] = scala * (tau * z[ja][i] - temp * vlag[i]) ;
      z[jb][i] = scalb * (z[jb][i] - tempa * w[i] - tempb * vlag[i]) ;
    }
    if(denom<=0) { if(beta<0) idz += 1 ; else iflag = 1 ; }
  }

  /* IDZ is reduced in the following case, and usually the first
   * column of ZMAT is exchanged with a later 1.0. */
  if(iflag==1) for(idz-=1,i=0;i<npt;i++) swap(z[0][i],z[idz][i]) ; 

  /* Finally, update the matrix BMAT. */
  for(j=0;j<n;j++)
  { jp = npt + j ; 
    w[jp] = b[j][knew] ;
    tempa = (alpha*vlag[jp] - tau*w[jp]) / denom ;
    tempb = -(beta*w[jp] + tau*vlag[jp]) / denom ;
    for(i=0;i<=jp;i++)
    { b[j][i] += tempa*vlag[i] + tempb*w[i] ; 
      if(i>=npt) b[i-npt][jp] = b[j][i];
    }
  }
  free(w) ; 
  return idz ;
}
/* -------------------------------------------------------------------------- */
/*page*/
/* -------------------------------------------------------------------------- */

void shiftxbase(int n,int npt,double **xp,double *xopt,double *vlag,double **b,
                double **z,double *pq,double *hq,int idz)
{ int i,j,k,ih ;
  double tempq,temp,sum,xoptsq,sumz,*w=vector(2*npt) ;

  for(xoptsq=i=0;i<n;i++) xoptsq += xopt[i] * xopt[i] ; 
  tempq = xoptsq/4 ;

  for(k=0;k<npt;k++)
  { for(sum=i=0;i<n;i++) sum += xp[k][i] * xopt[i];
    sum -= xoptsq/2 ;
    w[npt+k] = sum ;
    for(i=0;i<n;i++)
    { xp[k][i] -= xopt[i]/2 ;
      vlag[i] = b[i][k];
      w[i] = sum*xp[k][i] + tempq*xopt[i] ;
      for(j=0;j<=i;j++) b[j][npt+i] += vlag[i]*w[j] + w[i]*vlag[j] ;
    }
  }

  /* Then the revisions of BMAT that depend on ZMAT are calculated. */
  for(k=0;k<npt-n-1;k++)
  { for(sumz=i=0;i<npt;i++) { sumz += z[k][i] ; w[i] = w[npt+i] * z[k][i] ; }
    for(j=0;j<n;j++)
    { sum = tempq * sumz * xopt[j];
      for(i=0;i<npt;i++) sum += w[i] * xp[i][j];
      vlag[j] = sum;
      if(k<idz) sum = -sum;
      for(i=0;i<npt;i++) b[j][i] += sum * z[k][i];
    }
    for(i=0;i<n;i++)
    { if(k>=idz) temp = vlag[i] ; else temp = -vlag[i] ; 
      for(j=0;j<=i;j++) b[j][npt+i] += temp * vlag[j] ;
    }
  }

  /* The following instructions complete the shift of XBASE,
   * including the changes to the parameters of the quadratic
   * model. */

  for(ih=j=0;j<n;j++)
  { for(w[j]=k=0;k<npt;k++)
    { w[j] += pq[k] * xp[k][j] ; xp[k][j] -= xopt[j]/2 ; }
    for(i=0;i<=j;ih++,i++)
    { hq[ih] += w[i]*xopt[j] + xopt[i]*w[j] ; b[j][npt+i] = b[i][npt+j] ; }
  }
  free(w) ; 
}
/* -------------------------------------------------------------------------- */
/*page*/
/* -------------------------------------------------------------------------- */

//C     Calculate VLAG and BETA for the current choice of STEP. The first NPT
//C       elements of VLAG are set to the values of the Lagrange functions at
//C       XPT(KOPT,.)+STEP(.). The first NPT components of W_check are held
//C       in W, where W_check is defined in a paper on the updating method.

double calcvlagbeta(int n,int npt,double *vlag,double **z,double **b,double *w,
                    double *xopt,double *d,double beta,double dsq,double xsq,
                    int idz) 
{ int i,j,k ;
  double sum,dx,bsum ;
  for(beta=k=0;k<npt-n-1;k++)
  { for(sum=i=0;i<npt;i++) sum += z[k][i] * w[i] ;
    if(k<idz) { beta += sum*sum ; sum = -sum ; } else beta -= sum*sum ; 
    for(i=0;i<npt;i++) vlag[i] += sum * z[k][i];
  }

  for(bsum=dx=j=0;j<n;j++)
  { for(sum=i=0;i<npt;i++) sum += w[i] * b[j][i];
    bsum += sum * d[j] ;
    for(k=0;k<n;k++) sum += b[k][npt+j] * d[k];
    vlag[npt+j] = sum ; 
    bsum += sum * d[j] ; 
    dx += d[j] * xopt[j] ; 
  }

  // more numerical instability here? 
  return dx*dx + dsq*((xsq+dx) + dx + dsq/2) + beta - bsum ;
}
/* -------------------------------------------------------------------------- */

int chooseknew(int n,int npt,double beta,double **z,double *vlag,double **xp,
               double *xopt,double detrat,double rhosq,int ktemp,int idz)
{ int i,j,k,knew ;
  double temp,qtemp,distsq,hdiag,q ;

  for(knew=-1,k=0;k<npt;k++)
  { for(hdiag=j=0;j<npt-n-1;j++) 
      if(j<idz) hdiag -= z[j][k]*z[j][k] ; else hdiag += z[j][k]*z[j][k] ; 
    temp = fabs(beta*hdiag+vlag[k]*vlag[k]) ;
    for(distsq=j=0;j<n;j++) 
    { qtemp = xp[k][j] - xopt[j] ; distsq += qtemp*qtemp ; }
    if(distsq>rhosq) 
    { if(rhosq==0) q = temp * distsq * distsq ; 
      else { qtemp = distsq / rhosq ; q = temp * (qtemp*qtemp*qtemp) ; }
    }
    if(q>detrat&&k!=ktemp) { detrat = q ; knew = k ; } 
  }
  return knew ; 
}
/* -------------------------------------------------------------------------- */

double genpqw(int n,int npt,double *pqw,double **z,int knew,int idz)
{ int i,j,k ;
  double temp ; 
  for(k=0;k<npt;k++) pqw[k] = 0 ; 
  for(j=0;j<npt-n-1;j++)
  { if(j<idz) temp = -z[j][knew] ; else temp = z[j][knew] ; 
    if(temp!=0) for(k=0;k<npt;k++) pqw[k] += temp * z[j][k] ;
  }
  return pqw[knew] ; 
}
void updatehq(int n,int npt,double *hq,double pqknew,double **xpt,int knew) 
{ int i,j,ih ; 
  double temp ; 
  for(ih=i=0;i<n;i++) 
  { temp = pqknew * xpt[knew][i] ;
    for(j=0;j<=i;j++,ih++) hq[ih] += temp * xpt[knew][j] ; 
  }
}

Archived from optim.html

/*
  The original fortran codes are distributed without restrictions. The
  C++ codes are distributed under MIT license.
 */
/* The MIT License

   Copyright (c) nd., by M.J.D. Powell <mjdp@cam.ac.uk>
                 2016, by Colin Champion <colin.champion@masterlyinactivity.com>

   Permission is hereby granted, free of charge, to any person obtaining
   a copy of this software and associated documentation files (the
   "Software"), to deal in the Software without restriction, including
   without limitation the rights to use, copy, modify, merge, publish,
   distribute, sublicense, and/or sell copies of the Software, and to
   permit persons to whom the Software is furnished to do so, subject to
   the following conditions:

   The above copyright notice and this permission notice shall be
   included in all copies or substantial portions of the Software.

   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
   MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
   NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
   BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
   ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
   CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
   SOFTWARE.
*/
#include"memory.h"
#include <math.h>
#define max(a,b) ((a)>(b)?(a):(b))
#define min(a,b) ((a)<(b)?(a):(b))

int update(int,int,double **,double **,int,double *,double,int) ;
void shiftxbase(int n,int npt,double **xp,double *xopt,double *vlag,double **b,
                double **z,double *pq,double *hq,int idz) ;
double calcvlagbeta(int n,int npt,double *vlag,double **z,double **b,double *w,
                    double *xopt,double *d,double beta,double dsq,double xsq,
                    int idz) ;
int chooseknew(int n,int npt,double beta,double **z,double *vlag,double **xp,
               double *xopt,double detrat,double rhosq,int ktemp,int idz) ;
void genpqw(int n,int npt,double *pqw,double **z,int knew,int idz) ;
void updatehq(int n,int npt,double *hq,double pqknew,double **xpt,int knew) ;
void updategopt(int n,double *gopt,double *hq,double *step) ;
void updategopt2(int n,int npt,double *gopt,double *xopt,double *pq,double **) ;

//C     These instructions rearrange the active constraints so that the new
//C       value of IACT(NACT) is the old value of IACT(IC). A sequence of
//C       Givens rotations is applied to the current QFAC and RFAC. Then NACT
//C       is reduced by one.

static int rearrange(int ic,int nact,int n,int *iact,double *resact,
                     double *rfac,double **qfac,double *vlam,double *resnew)
{ int jc,i,j,idiag,jw,jdiag ;
  double temp,cval,sval ;
  if((resnew[iact[ic]]=resact[ic])<1e-60) resnew[iact[ic]] = 1e-60 ; 

  for(jc=ic;jc<nact-1;jc++)
  { jdiag = (jc*(jc+1)) / 2 ;
    idiag = ((jc+1)*(jc+2)) / 2 ;
    jw = idiag + jc ; 
    temp = sqrt(rfac[jw]*rfac[jw]+rfac[jw+1]*rfac[jw+1]) ;
    cval = rfac[jw+1] / temp ; 
    sval = rfac[jw] / temp ; 
    rfac[jw] = sval * rfac[idiag-1] ;
    rfac[jw+1] = cval * rfac[idiag-1] ;
    rfac[idiag-1] = temp ;
    for(jw+=2,j=jc+2;j<nact;j++,jw+=j)
    { temp = sval*rfac[jw+jc] + cval*rfac[jw+jc+1] ;
      rfac[jw+jc+1] = cval*rfac[jw+jc] - sval*rfac[jw+jc+1] ;
      rfac[jw+jc] = temp ;
    }
    for(i=0;i<jc;i++) swap(rfac[idiag+i],rfac[jdiag+i]) ; 
    for(i=0;i<n;i++)
    { temp = sval*qfac[jc][i] + cval*qfac[jc+1][i] ;
      qfac[jc+1][i] = cval*qfac[jc][i] - sval*qfac[jc+1][i] ; 
      qfac[jc][i] = temp ; 
    }
    iact[jc] = iact[jc+1] ; 
    resact[jc] = resact[jc+1] ;
    vlam[jc] = vlam[jc+1] ;
  }
  return nact-1 ;
}
/* -------------------------------------------------------------------------- */

double getact(int n,int m,double **amat, double *b,int& nact,
              int *iact,double **qfac,double *rfac,double & snorm,
              double *resnew,double *resact,double *g,double *dw) 
{ 
//C     N, M, AMAT, B, NACT, IACT, QFAC and RFAC are the same as the terms
//C       with these names in SUBROUTINE LINCOB. The current values must be
//C       set on entry. NACT, IACT, QFAC and RFAC are kept up to date when
//C       GETACT changes the current active set.
//C     SNORM, RESNEW, RESACT, G and DW are the same as the terms with these
//C       names in SUBROUTINE TRSTEP. The elements of RESNEW and RESACT are
//C       also kept up to date.
//C     VLAM and W are used for working space, the vector VLAM being reserved
//C       for the Lagrange multipliers of the calculation. Their lengths must
//C       be at least N.
//C     The main purpose of GETACT is to pick the current active set. It is
//C       defined by the property that the projection of -G into the space
//C       orthogonal to the active constraint normals is as large as possible,
//C       subject to this projected steepest descent direction moving no closer
//C       to the boundary of every constraint whose current residual is at most
//C       0.2*SNORM. On return, the settings in NACT, IACT, QFAC and RFAC are
//C       all appropriate to this choice of active set.
//C     Occasionally this projected direction is zero, and then the final value
//C       of W(1) is set to zero. Otherwise, the direction itself is returned
//C       in DW, and W(1) is set to the square of the length of the direction.
//C
//C     Set some constants and a temporary VLAM.

  int i,j,k,ic,idiag,jw,l ;
  double tdel=0.2*snorm,ddsav,violmx,rdiag,sprod,temp,ctol ;
  double cosv,sinv,sum,dd,test,dnorm,vmult ; 
  double *vlam=vector(n),*w=vector(n) ; 

  for(ddsav=i=0;i<n;i++) { ddsav += g[i]*g[i] ; vlam[i] = 0 ; } 
  ddsav *= 2 ; 

//C     Set the initial QFAC to the identity matrix in the case NACT=0.

  if(nact==0) for(i=0;i<n;i++) 
  { for(j=0;j<i;j++) qfac[j][i] = qfac[i][j] = 0 ; qfac[i][i] = 1 ; } 
  else
//C     Remove any constraints from the initial active set whose residuals
//C       exceed TDEL.
  { for(ic=nact-1;ic>=0;ic--) if(resact[ic]>tdel)
      nact = rearrange(ic,nact,n,iact,resact,rfac,qfac,vlam,resnew) ;

//C     Remove any constraints from the initial active set whose Lagrange
//C       multipliers are nonnegative, and set the surviving multipliers.

    for(ic=nact-1;ic>=0;ic--)
    { for(temp=i=0;i<n;i++) temp += qfac[ic][i] * g[i] ; 
      idiag = ((ic+1)*(ic+2))/2 ;
      for(jw=idiag+ic,j=ic+1;j<nact;j++,jw+=j) temp -= rfac[jw] * vlam[j] ;
      if(temp>=0) 
        ic = nact = rearrange(ic,nact,n,iact,resact,rfac,qfac,vlam,resnew) ; 
      else vlam[ic] = temp / rfac[idiag-1] ;
    }
  }

//C     Set the new search direction D. Terminate if the 2-norm of D is zero
//C       or does not decrease, or if NACT=N holds. The situation NACT=N
//C       occurs for sufficiently large SNORM if the origin is in the convex
//C       hull of the constraint gradients.
 
  while(nact<n)
  { for(j=nact;j<n;j++) for(w[j]=i=0;i<n;i++) w[j] += qfac[j][i] * g[i] ; 
    for(dd=i=0;i<n;i++) 
    { for(dw[i]=0,j=nact;j<n;j++) dw[i] -= w[j] * qfac[j][i] ; 
      dd += dw[i] * dw[i] ; 
    }
    if(dd>=ddsav||dd==0) { free(vlam,w) ; return 0 ; }
    ddsav = dd ; 
    dnorm = sqrt(dd) ; 

//C     Pick the next integer L or terminate, a positive value of L being
//C       the index of the most violated constraint. The purpose of CTOL
//C       below is to estimate whether a positive value of VIOLMX may be
//C       due to computer rounding errors.

    l = -1 ; 
    if(m>0)
    { test = dnorm / snorm ; 
      for(violmx=j=0;j<m;j++) if(resnew[j]>0&&resnew[j]<=tdel)
      { for(sum=i=0;i<n;i++) sum += amat[j][i] * dw[i] ;
        if(sum>test*resnew[j]&&sum>violmx) { l = j ; violmx = sum ; }
      }
      ctol= 0 ; 
      temp = 0.01 * dnorm ; 
      if(violmx>0&&violmx<temp) for(k=0;k<nact;k++)
      { for(j=iact[k],sum=i=0;i<n;i++) sum += dw[i] * amat[j][i] ; 
        if(fabs(sum)>ctol) ctol = fabs(sum) ; 
      }
    }
    if(l<0||violmx<=10*ctol) { free(vlam,w) ; return dd ; } 

//C     Apply Givens rotations to the last (N-NACT) columns of QFAC so that
//C       the first (NACT+1) columns of QFAC are the ones required for the
//C       addition of the L-th constraint, and add the appropriate column
//C       to RFAC.

    idiag = (nact*(nact+1))/2 ;
    for(rdiag=0,j=n-1;j>=0;j--) 
    { for(sprod=i=0;i<n;i++) sprod += qfac[j][i] * amat[l][i] ; 
      if(j<nact) rfac[idiag+j] = sprod ; 
      else if(fabs(rdiag)<=1e-20*fabs(sprod)) rdiag = sprod ; 
      else
      { temp = sqrt(sprod*sprod+rdiag*rdiag) ;
        cosv = sprod / temp ; 
        sinv = rdiag / temp ; 
        rdiag = temp ; 
        for(i=0;i<n;i++) 
        { temp = cosv*qfac[j][i] + sinv*qfac[j+1][i] ;
          qfac[j+1][i] = -sinv*qfac[j][i] + cosv*qfac[j+1][i] ; 
          qfac[j][i] = temp ; 
        }
      }
    }

    if(rdiag<0) for(i=0;i<n;i++) qfac[nact][i] = -qfac[nact][i] ; 
    rfac[idiag+nact] = fabs(rdiag) ; 
    iact[nact] = l ; 
    resact[nact] = resnew[l] ; 
    vlam[nact] = resnew[l] = 0 ; 
    nact += 1 ; 

//C     Set the components of the vector VMU in W.

    for(ic=0;violmx>0||ic>=0;) // while(violmx>0) but with at least 1 iteration
    { temp = rfac[(nact*(nact+1))/2-1] ;
      w[nact-1] = 1/(temp*temp) ;
      for(i=nact-2;i>=0;i--)
      { idiag = ((i+1)*(i+2))/2 ; 
        for(sum=0,jw=idiag+i,j=i+1;j<nact;j++,jw+=j) sum -= rfac[jw] * w[j] ;
        w[i] = sum / rfac[idiag-1] ;
      }

//C     Calculate the multiple of VMU to subtract from VLAM, and update VLAM.

      vmult = violmx ; 
      for(ic=-1,j=0;j<nact-1;j++)
        if(vlam[j]>=vmult*w[j]) { ic = j ; vmult = vlam[j] / w[j] ; }
      for(j=0;j<nact;j++) vlam[j] -= vmult * w[j] ; 
      if(ic>=0) vlam[ic] = 0 ;
      violmx -= vmult ;
      if(ic<0||violmx<0) violmx = 0 ;

//C     Reduce the active set if necessary, so that all components of the
//C       new VLAM are negative, with resetting of the residuals of the
//C       constraints that become inactive.

      for(ic=nact-1;ic>=0;ic--) if(vlam[ic]>=0)
        nact = rearrange(ic,nact,n,iact,resact,rfac,qfac,vlam,resnew) ; 

//C     Calculate the next VMU if VIOLMX is positive. Return if NACT=N holds,
//C       as then the active constraints imply D=0. Otherwise, go to label
//C       100, to calculate the new D and to test for termination.
   } // end while(violmx>0)
  } // end while(nact<n)
  free(vlam,w) ; 
  return 0 ; 
}
/* -------------------------------------------------------------------------- */

int trstep(int n,int npt,int m,double **amat,double *b,double **xpt,double *hq,
           double *pq,int& nact,int *iact,double *rescon,double **qfac,
           double *rfac,double& snorm,double *step,double *g)
{
//C     N, NPT, M, AMAT, B, XPT, HQ, PQ, NACT, IACT, RESCON, QFAC and RFAC
//C       are the same as the terms with these names in LINCOB. If RESCON(J)
//C       is negative, then |RESCON(J)| must be no less than the trust region
//C       radius, so that the J-th constraint can be ignored.
//C     SNORM is set to the trust region radius DELTA initially. On the
//C       return, however, it is the length of the calculated STEP, which is
//C       set to zero if the constraints do not allow a long enough step.
//C     STEP is the total calculated step so far from the trust region centre,
//C       its final value being given by the sequence of CG iterations, which
//C       terminate if the trust region boundary is reached.
//C     G must be set on entry to the gradient of the quadratic model at the
//C       trust region centre. It is used as working space, however, and is
//C       always the gradient of the model at the current STEP, except that
//C       on return the value of G(1) is set to ONE instead of to ZERO if
//C       and only if GETACT is called more than once.
//C     RESNEW, RESACT, D, DW and W are used for working space. A negative
//C       value of RESNEW(J) indicates that the J-th constraint does not
//C       restrict the CG steps of the current trust region calculation, a
//C       zero value of RESNEW(J) indicates that the J-th constraint is active,
//C       and otherwise RESNEW(J) is set to the greater of TINY and the actual
//C       residual of the J-th constraint for the current STEP. RESACT holds
//C       the residuals of the active constraints, which may be positive.
//C       D is the search direction of each line search. DW is either another
//C       search direction or the change in gradient along D. The length of W
//C       must be at least MAX[M,2*N].

  int i,j,k,continuing,ncall,ir,jsav,icount,ih ;
  double ctest=0.01,snsq=snorm*snorm,tiny=1e-60,dnorm,scale,resmax,ss,sum,rhs ;
  double temp,gamma,sumrhs,dd,ds,ad,adw,dg,dgd,alpha,alphm,alpht,alpbd,reduct ;
  double wgd,beta ; 
  double *resnew=vector(m),*resact=vector(n),*d=vector(n),*dw=vector(n) ;
  double *w=vector(max(m,2*n)) ;

//C     Set the initial elements of RESNEW, RESACT and STEP.

  for(j=0;j<m;j++)
  { resnew[j] = rescon[j] ; 
    if(resnew[j]>=snorm) resnew[j] = -1 ; 
    else if(resnew[j]>=0&&resnew[j]<tiny) resnew[j] = tiny ; 
  }
  if(m>0) for(k=0;k<nact;k++) 
  { resact[k] = rescon[iact[k]] ; resnew[iact[k]] = 0 ; } 
  for(i=0;i<n;i++) step[i] = 0 ; 

//C     GETACT picks the active set for the current STEP. It also sets DW to
//C       the vector closest to -G that is orthogonal to the normals of the
//C       active constraints. DW is scaled to have length 0.2*SNORM, as then
//C       a move of DW from STEP is allowed by the linear constraints.

  for(ss=reduct=ncall=0,continuing=1;continuing;)
  { dnorm = getact(n,m,amat,b,nact,iact,qfac,rfac,snorm,resnew,resact,g,dw) ;
    ncall += 1 ; 
    if(dnorm==0) break ; 
    scale = 0.2*snorm/sqrt(dnorm) ;
    for(i=0;i<n;i++) dw[i] *= scale ; 

//C     If the modulus of the residual of an active constraint is substantial,
//C       then set D to the shortest move from STEP to the boundaries of the
//C       active constraints.

    for(resmax=k=0;k<nact;k++) if(resact[k]>resmax) resmax = resact[k] ;
    gamma = 0 ; 

    if(resmax>snorm*1e-4)
    { for(ir=k=0;k<nact;k++,ir++)
      { for(temp=resact[k],i=0;i<k;ir++,i++) temp -= rfac[ir] * w[i] ; 
        w[k] = temp / rfac[ir] ;
      }
      for(i=0;i<n;i++) for(d[i]=k=0;k<nact;k++) d[i] += w[k] * qfac[k][i] ;

//C     The vector D that has just been calculated is also the shortest move
//C       from STEP+DW to the boundaries of the active constraints. Set GAMMA
//C       to the greatest steplength of this move that satisfies the trust
//C       region bound.

      for(rhs=snsq,ds=dd=i=0;i<n;i++)
      { sum = step[i] + dw[i] ; 
        rhs -= sum * sum ; 
        ds += d[i] * sum ; 
        dd += d[i] * d[i] ;
      }
      if(rhs>0)
      { temp = sqrt(ds*ds+dd*rhs) ; 
        if(ds<=0) gamma = (temp-ds) / dd ; else gamma = rhs / (temp+ds) ; 
      }

//C     Reduce the steplength GAMMA if necessary so that the move along D
//C       also satisfies the linear constraints.

      for(j=0;j<m&&gamma>0;j++) if(resnew[j]>0)
      { for(ad=adw=i=0;i<n;i++) 
        { ad += amat[j][i] * d[i] ; adw += amat[j][i] * dw[i] ; }
        if(ad>0)
        { if(0>(temp=(resnew[j]-adw)/ad)) temp = 0 ; 
          if(temp<gamma) gamma = temp ; 
        }
      }
      if(gamma>1) gamma = 1 ; 
    } // end if(resmax>snorm*1e-4)

//C     Set the next direction for seeking a reduction in the model function
//C       subject to the trust region bound and the linear constraints.

    if(gamma<=0) { for(i=0;i<n;i++) d[i] = dw[i] ; icount = nact ; }
    else { for(i=0;i<n;i++) d[i] = dw[i] + gamma*d[i] ; icount = nact-1 ; }

//C     Set ALPHA to the steplength from STEP along D to the trust region
//C       boundary. Return if the first derivative term of this step is
//C       sufficiently small or if no further progress is possible.

    for(alpbd=1;;alpbd=0)
    { icount += 1 ; 
      rhs = snsq - ss ; 
      if(rhs<=0) { continuing = 0 ; break ; } // break both loops
      for(dg=ds=dd=i=0;i<n;i++)
      { dg += d[i] * g[i] ; ds += d[i] * step[i] ; dd += d[i] * d[i] ; }
      if(dg>=0) { continuing = 0 ; break ; } 
      temp = sqrt(rhs*dd+ds*ds) ; 
      if(ds<=0) alpha = (temp-ds)/dd ; else alpha = rhs/(temp+ds) ; 
      if(-alpha*dg<=ctest*reduct) { continuing = 0 ; break ; } 

//C     Set DW to the change in gradient along D.

      for(j=0;j<n;j++) dw[j] = 0 ; 
      updategopt(n,dw,hq,d) ; 
      updategopt2(n,npt,dw,d,pq,xpt) ;

//C     Set DGD to the curvature of the model along D. Then reduce ALPHA if
//C       necessary to the value that minimizes the model.

      for(dgd=i=0;i<n;i++) dgd += d[i] * dw[i] ; 
      alpht = alpha ;
      if(dg+alpha*dgd>0) alpha = -dg/dgd ;

//C     Make a further reduction in ALPHA if necessary to preserve feasibility,
//C       and put some scalar products of D with constraint gradients in W.

      alphm = alpha ; 
      for(jsav=-1,j=0;j<m;j++) 
      { w[j] = 0 ; 
        if(resnew[j]>0) 
        { for(i=0;i<n;i++) w[j] += amat[j][i] * d[i] ; 
          if(alpha*w[j]>resnew[j]) { alpha = resnew[j] / w[j] ; jsav = j ; } 
        }
      }

      if(alpha<alpbd) alpha = alpbd ; 
      if(alpha>alphm) alpha = alphm ; 
      if(icount==nact&&alpha>1) alpha = 1 ; 

//C     Update STEP, G, RESNEW, RESACT and REDUCT.

      for(ss=i=0;i<n;i++)
      { step[i] += alpha*d[i] ; ss += step[i]*step[i] ; g[i] += alpha*dw[i] ; }
      for(j=0;j<m;j++) if(resnew[j]>0)
        if((resnew[j]-=alpha*w[j])<tiny) resnew[j] = tiny ;

      if(icount==nact) for(k=0;k<nact;k++) resact[k] *= (1-gamma) ;
      reduct -= alpha * (dg+0.5*alpha*dgd) ;

//C     Test for termination. Branch to label 40 if there is a new active
//C       constraint and if the distance from STEP to the trust region
//C       boundary is at least 0.2*SNORM.

      if( alpha==alpht || -alphm*(dg+0.5*alphm*dgd)<=ctest*reduct ) 
      { continuing = 0 ; break ; } 
      if(jsav>=0) { continuing = (ss<=0.64*snsq) ; break ; } 
      if(icount==n) { continuing = 0 ; break ; } 

//C     Calculate the next search direction, which is conjugate to the
//C       previous one except in the case ICOUNT=NACT.

      if(nact>0)
      { for(j=nact;j<n;j++) for(w[j]=i=0;i<n;i++) w[j] += g[i] * qfac[j][i] ; 
        for(i=0;i<n;i++)
        { for(temp=0,j=nact;j<n;j++) temp += qfac[j][i]*w[j] ; w[n+i] = temp ; }
      }
      else for(i=0;i<n;i++) w[n+i] = g[i] ; 

      if(icount==nact) beta = 0 ; 
      else { for(wgd=i=0;i<n;i++) wgd += w[n+i]*dw[i] ; beta = wgd / dgd ; }

      for(i=0;i<n;i++) d[i] = -w[n+i] + beta*d[i] ; 
    }
  }

//C     Return from the subroutine.

  if(reduct>0) snorm = sqrt(ss) ; else snorm = 0 ; 
  free(resnew,resact,dw,d,w) ; 
  return ncall ;
}
/* -------------------------------------------------------------------------- */

int qmstep(int n,int npt,int m,double **amat,double **xpt,double *xopt,int nact,
           int *iact,double *rescon,double **qfac,int kopt,int knew,double del,
           double *step,double *gl,double *pqw)
{
//C     N, NPT, M, AMAT, B, XPT, XOPT, NACT, IACT, RESCON, QFAC, KOPT are the
//C       same as the terms with these names in SUBROUTINE LINCOB.
//C     KNEW is the index of the interpolation point that is going to be moved.
//C     DEL is the current restriction on the length of STEP, which is never
//C       greater than the current trust region radius DELTA.
//C     STEP will be set to the required step from XOPT to the new point.
//C     GL must be set on entry to the gradient of LFUNC at XBASE, where LFUNC
//C       is the KNEW-th Lagrange function. It is used also for some other
//C       gradients of LFUNC.
//C     PQW provides the second derivative parameters of LFUNC.
//C     RSTAT and W are used for working space. Their lengths must be at least
//C       M and N, respectively. RSTAT(J) is set to -1.0, 0.0, or 1.0 if the
//C       J-th constraint is irrelevant, active, or both inactive and relevant,
//C       respectively.
//C     IFEAS will be set to 0 or 1 if XOPT+STEP is infeasible or feasible.
//C
//C     STEP is chosen to provide a relatively large value of the modulus of
//C       LFUNC(XOPT+STEP), subject to ||STEP|| .LE. DEL. A projected STEP is
//C       calculated too, within the trust region, that does not alter the
//C       residuals of the active constraints. The projected step is preferred
//C       if its value of | LFUNC(XOPT+STEP) | is at least one fifth of the
//C       original one, but the greatest violation of a linear constraint must
//C       be at least 0.2*DEL, in order to keep the interpolation points apart.
//C       The remedy when the maximum constraint violation is too small is to
//C       restore the original step, which is perturbed if necessary so that
//C       its maximum constraint violation becomes 0.2*DEL.
  
  int i,j,k,ksav,jsav,ifeas,*istat=ivector(m) ;
  double test=0.2*del,temp,vbig,ss,sp,stp,vlag,stpsav,gg,vgrad,ghg,ww,ctol ;
  double sum,vnew,resmax,bigv,*w=vector(n) ; 

//C     Replace GL by the gradient of LFUNC at the trust region centre, and
//C       set the elements of RSTAT.

  for(k=0;k<npt;k++)
  { for(temp=j=0;j<n;j++) temp += xpt[k][j] * xopt[j] ;
    temp *= pqw[k] ;
    for(i=0;i<n;i++) gl[i] += temp*xpt[k][i] ;
  }

  for(j=0;j<m;j++)
  { if(fabs(rescon[j])>=del) istat[j] = -1 ; else istat[j] = 1 ; }
  if(m>0) for(k=0;k<nact;k++) istat[iact[k]] = 0 ; 

//C     Find the greatest modulus of LFUNC on a line through XOPT and
//C       another interpolation point within the trust region.

  for(vbig=k=0;k<npt;k++) if(k!=kopt)
  { for(sp=ss=i=0;i<n;i++) 
    { temp = xpt[k][i] - xopt[i] ; ss += temp * temp ; sp += gl[i] * temp ; }
    stp = -del / sqrt(ss) ; 
    if(k==knew)
    { if(sp*(sp-1)<0) stp = -stp ; vlag = fabs(stp*sp) + stp*stp*fabs(sp-1) ; }
    else vlag = fabs(stp*(1-stp)*sp) ;
    if(vlag>vbig) { ksav = k ; stpsav = stp ; vbig = vlag ; } 
  }

//C     Set STEP to the move that gives the greatest modulus calculated above.
//C       This move may be replaced by a steepest ascent step from XOPT.

  for(gg=i=0;i<n;i++) 
  { gg += gl[i]*gl[i] ; step[i] = stpsav*(xpt[ksav][i]-xopt[i]) ; } 
  vgrad = del * sqrt(gg) ; 

  if(vgrad>vbig/10) 
  { 
//C     Make the replacement if it provides a larger value of VBIG.
    for(ghg=k=0;k<npt;k++)
    { for(temp=j=0;j<n;j++) temp += xpt[k][j] * gl[j] ; 
      ghg += pqw[k] * temp * temp ;
    }
    vnew = vgrad + fabs(0.5*del*del*ghg/gg) ; 
    if(vnew>vbig)
    { vbig = vnew ; 
      stp = del / sqrt(gg) ; 
      if(ghg<0) stp = -stp ; 
      for(i=0;i<n;i++) step[i] = stp * gl[i] ; 
    }

    if(nact!=0&&nact!=n)
    { 
//C     Overwrite GL by its projection. Then set VNEW to the greatest
//C       value of |LFUNC| on the projected gradient from XOPT subject to
//C       the trust region bound. If VNEW is sufficiently large, then STEP
//C       may be changed to a move along the projected gradient.

      for(k=nact;k<n;k++)
        for(w[k]=i=0;i<n;i++) w[k] += gl[i] * qfac[k][i] ; 
      for(gg=i=0;i<n;i++) 
      { for(gl[i]=0,k=nact;k<n;k++) gl[i] += qfac[k][i] * w[k] ; 
        gg += gl[i] * gl[i] ; 
      }
      vgrad = del * sqrt(gg) ; 

      if(vgrad>vbig/10)
      { for(ghg=k=0;k<npt;k++) 
        { for(temp=j=0;j<n;j++) temp += xpt[k][j] * gl[j] ; 
          ghg += pqw[k] * temp * temp ; 
        }
        vnew = vgrad + fabs(0.5*del*del*ghg/gg) ;

//C     Set W to the possible move along the projected gradient.

        stp = del / sqrt(gg) ; 
        if(ghg<0) stp = -stp ; 
        for(ww=i=0;i<n;i++) { w[i] = stp * gl[i] ; ww += w[i]*w[i] ; }

//C     Set STEP to W if W gives a sufficiently large value of the modulus
//C       of the Lagrange function, and if W either preserves feasibility
//C       or gives a constraint violation of at least 0.2*DEL. The purpose
//C       of CTOL below is to provide a check on feasibility that includes
//C       a tolerance for contributions from computer rounding errors.

        if(vnew/vbig>=0.2)
        { for(ifeas=1,bigv=j=0;j<m;j++) 
          { if(istat[j]==1)
            { for(temp=-rescon[j],i=0;i<n;i++) temp += w[i] * amat[j][i] ; 
              if(temp>bigv) bigv = temp ; 
            }
            if(bigv>=test) { ifeas = 0 ; break ; }
          }

          ctol = 0 ; 
          temp = sqrt(ww) / 100 ; 
          if(bigv>0&&bigv<temp) for(k=0;k<nact;k++) 
          { for(j=iact[k],sum=i=0;i<n;i++) sum += w[i] * amat[j][i] ; 
             if(fabs(sum)>ctol) ctol = fabs(sum) ; 
          }
          if(bigv<=10*ctol||bigv>=test)
          { for(i=0;i<n;i++) step[i] = w[i] ; free(istat,w) ; return ifeas ; } 
        }
      }
    }
  }

//C     Calculate the greatest constraint violation at XOPT+STEP with STEP at
//C       its original value. Modify STEP if this violation is unacceptable.

  for(ifeas=1,bigv=resmax=jsav=j=0;j<m;j++) if(istat[j]>=0) 
  { for(temp=-rescon[j],i=0;i<n;i++) temp += step[i] * amat[j][i] ; 
    if(resmax<temp) resmax = temp ; 
    if(temp>=test) { ifeas = 0 ; break ; } 
    if(temp>bigv) { bigv = temp ; jsav = j ; ifeas = -1 ; }
  }
  if(ifeas<0) 
  { for(i=0;i<n;i++) step[i] += (test-bigv) * amat[jsav][i] ; ifeas = 0 ; }
  free(istat,w) ;
  return ifeas ;
}
/* -------------------------------------------------------------------------- */

void lincupdate(int n,int npt,double **xpt,double **bmat,double **zmat,int &idz,
            int ndim,double *sp,double *step,int kopt,int &knew)
{ int i,j,k,nptm=npt-n-1,jp ; 
  double sum,ssq,temp,tempa,tempb,distsq,hdiag,denom,denabs,denmax ;
  double beta,*vlag=vector(npt+n),*w=vector(npt) ;

//C     The arguments N, NPT, XPT, BMAT, ZMAT, IDZ, NDIM ,SP and STEP are
//C       identical to the corresponding arguments in SUBROUTINE LINCOB.
//C     KOPT is such that XPT(KOPT,.) is the current trust region centre.
//C     KNEW on exit is usually positive, and then it is the index of an
//C       interpolation point to be moved to the position XPT(KOPT,.)+STEP(.).
//C       It is set on entry either to its final value or to 0. In the latter
//C       case, the final value of KNEW is chosen to maximize the denominator
//C       of the matrix updating formula times a weighting factor.
//C     VLAG and W are used for working space, the first NPT+N elements of
//C       both of these vectors being required.
//C
//C     The arrays BMAT and ZMAT with IDZ are updated, the new matrices being
//C       the ones that are suitable after the shift of the KNEW-th point to
//C       the new position XPT(KOPT,.)+STEP(.). A return with KNEW set to zero
//C       occurs if the calculation fails due to a zero denominator in the
//C       updating formula, which should never happen.

  for(k=0;k<npt;k++)
  { w[k] = sp[npt+k] * (sp[npt+k]/2+sp[k]) ;
    for(vlag[k]=j=0;j<n;j++) vlag[k] += bmat[j][k] * step[j] ;
  }

  for(ssq=i=0;i<n;i++) ssq += step[i] * step[i] ; 
  beta = 
    calcvlagbeta(n,npt,vlag,zmat,bmat,w,xpt[kopt],step,beta,ssq,sp[kopt],idz) ;
  vlag[kopt] += 1 ; 

//C     If KNEW is zero initially, then pick the index of the interpolation
//C       point to be deleted, by maximizing the absolute value of the
//C       denominator of the updating formula times a weighting factor.

  if(knew<0) knew = chooseknew(n,npt,beta,zmat,vlag,xpt,xpt[kopt],0,0,-1,idz) ; 

//C and call the NEWUOA update to do the rest
  idz = update(n,npt,bmat,zmat,idz,vlag,beta,knew) ; 
  free(vlag,w) ; 
}
/* -------------------------------------------------------------------------- */

int prelim(int n,int npt,int m,double **amat,double *b,double *x,double rhobeg,
           double *xbase,double **xpt,double *fval,double *xsav,double *gopt,
           int &kopt,double *pq,double **bmat,double **zmat,int ndim,
           double *sp,double *rescon,int maxflag,double (*func)(double *),
           double (*gunc)(double *,double *),double *aux,int naux)
{ int nptm=npt-n-1,kbase=0,i,j,k,itemp,ipt,jpt,jsav,jp,nf,idz ;
  double rhosq=rhobeg*rhobeg,recip=1/rhosq,test=0.2*rhobeg,feas,bigv,resid,f ;
  double temp,reciq=sqrt(0.5)/rhosq ; 
  double *step=vector(n),*w=vector(n+npt),*auxtemp ; 
  if(func) auxtemp = 0 ; else auxtemp = vector(naux) ; 

//C     The arguments N, NPT, M, AMAT, B, X, RHOBEG, IPRINT, XBASE, XPT, FVAL,
//C       XSAV, XOPT, GOPT, HQ, PQ, BMAT, ZMAT, NDIM, SP and RESCON are the
//C       same as the corresponding arguments in SUBROUTINE LINCOB.
//C     KOPT is set to the integer such that XPT(KOPT,.) is the initial trust
//C       region centre.
//C     IDZ is going to be set to one, so that every element of Diag(DZ) is
//C       one in the product ZMAT times Diag(DZ) times ZMAT^T, which is the
//C       factorization of the leading NPT by NPT submatrix of H.
//C     STEP, PQW and W are used for working space, the arrays STEP and PQW
//C       being taken from LINCOB. The length of W must be at least N+NPT.
//C
//C     SUBROUTINE PRELIM provides the elements of XBASE, XPT, BMAT and ZMAT
//C       for the first iteration, an important feature being that, if any of
//C       of the columns of XPT is an infeasible point, then the largest of
//C       the constraint violations there is at least 0.2*RHOBEG. It also sets
//C       the initial elements of FVAL, XOPT, GOPT, HQ, PQ, SP and RESCON.

//C     Set the initial elements of XPT, BMAT, SP and ZMAT to zero. 

  idz = 0 ; 
  for(j=0;j<n;j++) 
  { xbase[j] = x[j] ;
    for(k=0;k<npt;k++) xpt[k][j] = 0 ;
    for(i=0;i<ndim;i++) bmat[j][i] = 0 ; 
  }
  for(k=0;k<npt;k++) { sp[k] = 0 ; for(j=0;j<nptm;j++) zmat[j][k] = 0 ; }

//C     Set the nonzero coordinates of XPT(K,.), K=1,2,...,min[2*N+1,NPT],
//C       but they may be altered later to make a constraint violation
//C       sufficiently large. The initial nonzero elements of BMAT and of
//C       the first min[N,NPT-N-1] columns of ZMAT are set also.
//C

  for(j=0;j<n;j++)
  { xpt[j+1][j] = rhobeg ;
    if(j<nptm)
    { jp = n + j + 1 ; 
      xpt[jp][j] = -rhobeg ;
      bmat[j][j+1] = rhobeg/2 ;
      bmat[j][jp] = -rhobeg/2 ;
      zmat[j][0] = -2*reciq ;
      zmat[j][j+1] = zmat[j][jp] = reciq ;
    }
    else 
    { bmat[j][0] = -(bmat[j][j+1] = 1/rhobeg) ; bmat[j][npt+j] = -rhosq/2 ; }
  }

//C     Set the remaining initial nonzero elements of XPT and ZMAT when the
//C       number of interpolation points exceeds 2*N+1.

  for(k=n;k<nptm;k++)
  { itemp = k / n ;
    ipt = k - itemp*n ;
    jpt = ipt + itemp ; 
    if(jpt>=n) jpt -= n ; 
    xpt[n+k+1][ipt] = xpt[n+k+1][jpt] = rhobeg ; 
    zmat[k][0] = zmat[k][n+k+1] = recip ; 
    zmat[k][ipt+1] = zmat[k][jpt+1] = -recip ; 
  }

//C     Update the constraint right hand sides to allow for the shift XBASE.

  for(j=0;j<m;j++) 
  { for(temp=i=0;i<n;i++) temp += amat[j][i] * xbase[i] ; b[j] -= temp ; }  

//C     Go through the initial points, shifting every infeasible point if
//C       necessary so that its constraint violation is at least 0.2*RHOBEG.

  for(nf=0;nf<npt;nf++)
  { for(feas=1,bigv=j=0;j<m&&nf>0;j++)
    { for(resid=-b[j],i=0;i<n;i++) resid += xpt[nf][i] * amat[j][i] ; 
      if(resid<=bigv) continue ;
      bigv = resid ; 
      jsav = j ; 
      if(resid>test) { feas = 0 ; break ; } else feas = -1 ;
    }
    if(feas<0)
    { for(i=0;i<n;i++) step[i] = xpt[nf][i] + (test-bigv)*amat[jsav][i] ; 
      for(k=0;k<npt;k++) for(sp[npt+k]=j=0;j<n;j++) 
        sp[npt+k] += xpt[k][j]*step[j] ; 
      lincupdate(n,npt,xpt,bmat,zmat,idz,ndim,sp,step,kbase,nf) ;
      for(i=0;i<n;i++) xpt[nf][i] = step[i] ; 
    }

//C     Calculate the objective function at the current interpolation point,
//C       and set KOPT to the index of the first trust region centre.

    for(j=0;j<n;j++) x[j] = xbase[j] + xpt[nf][j] ; 
    f = feas ;
    if(func) f = func(x) ; 
    else { for(j=0;j<naux;j++) auxtemp[j] = aux[j] ; f = gunc(x,auxtemp) ; } 
    if(maxflag) f = -f ; 
    if(nf==0||(f<fval[kopt]&&feas>0)) 
    { kopt = nf ; for(j=0;j<naux;j++) xsav[n+j] = auxtemp[j] ; }
    fval[nf] = f ; 
  }

//C     Set PQ for the first quadratic model.

  for(j=0;j<nptm;j++) for(w[j]=k=0;k<npt;k++) w[j] += zmat[j][k]*fval[k] ;
  for(k=0;k<npt;k++) for(pq[k]=j=0;j<nptm;j++) pq[k] += zmat[j][k] * w[j] ; 

//C     Set XOPT, SP, GOPT and HQ for the first quadratic model.

  for(j=0;j<n;j++) { xsav[j] = xbase[j] + xpt[kopt][j] ; gopt[j] = 0 ; }
  for(k=0;k<npt;k++) 
  { for(sp[k]=j=0;j<n;j++) sp[k] += xpt[k][j] * xpt[kopt][j] ;
    temp = pq[k] * sp[k] ; 
    for(j=0;j<n;j++) gopt[j] += fval[k]*bmat[j][k] + temp*xpt[k][j] ; 
  }

//C     Set the initial elements of RESCON.

  for(j=0;j<m;j++)
  { for(rescon[j]=b[j],i=0;i<n;i++) rescon[j] -= xpt[kopt][i] * amat[j][i] ; 
    if(rescon[j]<0) rescon[j] = 0 ; 
    if(rescon[j]>=rhobeg) rescon[j] = -rescon[j] ; 
  }
  free(step,w,auxtemp) ; 
  return idz ; 
}
/* -------------------------------------------------------------------------- */

static int cond=-7 ; 
int lincoacond() { return cond ; } 

double lincob(int n,int npt,int m,double **amat,double *b,double *x,
              double rhobeg,double rhoend,int maxfun,int ndim,int maxflag,
              double (*func)(double *),
              double (*gunc)(double *,double *),double *aux,int naux)
{
//C     The arguments N, NPT, M, X, RHOBEG, RHOEND, IPRINT and MAXFUN are
//C       identical to the corresponding arguments in SUBROUTINE LINCOA.
//C     AMAT is a matrix whose columns are the constraint gradients, scaled
//C       so that they have unit length.
//C     B contains on entry the right hand sides of the constraints, scaled
//C       as above, but later B is modified for variables relative to XBASE.
//C     XBASE holds a shift of origin that should reduce the contributions
//C       from rounding errors to values of the model and Lagrange functions.
//C     XPT contains the interpolation point coordinates relative to XBASE.
//C     FVAL holds the values of F at the interpolation points.
//C     XSAV holds the best feasible vector of variables so far, without any
//C       shift of origin.
//C     XOPT is set to XSAV-XBASE, which is the displacement from XBASE of
//C       the feasible vector of variables that provides the least calculated
//C       F so far, this vector being the current trust region centre.
//C     GOPT holds the gradient of the quadratic model at XSAV = XBASE+XOPT.
//C     HQ holds the explicit second derivatives of the quadratic model.
//C     PQ contains the parameters of the implicit second derivatives of the
//C       quadratic model.
//C     BMAT holds the last N columns of the big inverse matrix H.
//C     ZMAT holds the factorization of the leading NPT by NPT submatrix
//C       of H, this factorization being ZMAT times Diag(DZ) times ZMAT^T,
//C       where the elements of DZ are plus or minus one, as specified by IDZ.
//C     NDIM is the first dimension of BMAT and has the value NPT+N.
//C     STEP is employed for trial steps from XOPT. It is also used for working
//C       space when XBASE is shifted and in PRELIM.
//C     SP is reserved for the scalar products XOPT^T XPT(K,.), K=1,2,...,NPT,
//C       followed by STEP^T XPT(K,.), K=1,2,...,NPT.
//C     XNEW is the displacement from XBASE of the vector of variables for
//C       the current calculation of F, except that SUBROUTINE TRSTEP uses it
//C       for working space.
//C     IACT is an integer array for the indices of the active constraints.
//C     RESCON holds useful information about the constraint residuals. Every
//C       nonnegative RESCON(J) is the residual of the J-th constraint at the
//C       current trust region centre. Otherwise, if RESCON(J) is negative, the
//C       J-th constraint holds as a strict inequality at the trust region
//C       centre, its residual being at least |RESCON(J)|; further, the value
//C       of |RESCON(J)| is at least the current trust region radius DELTA.
//C     QFAC is the orthogonal part of the QR factorization of the matrix of
//C       active constraint gradients, these gradients being ordered in
//C       accordance with IACT. When NACT is less than N, columns are added
//C       to QFAC to complete an N by N orthogonal matrix, which is important
//C       for keeping calculated steps sufficiently close to the boundaries
//C       of the active constraints.
//C     RFAC is the upper triangular part of this QR factorization, beginning
//C       with the first diagonal element, followed by the two elements in the
//C       upper triangular part of the second column and so on.
//C     PQW is used for working space, mainly for storing second derivative
//C       coefficients of quadratic functions. Its length is NPT+N.
//C     The array W is also used for working space. The required number of
//C       elements, namely MAX[M+3*N,2*M+N,2*NPT], is set in LINCOA.

  int i,j,k,np=n+1,nptm=npt-n-1,ncall,nf,nvala,nvalb,knew,iflag,iter,loopflag ;
  int itest,kopt,ksave,ifeas,nact,idz=-1,ih,wlen=max(m+3*n,max(2*m+n,2*npt)) ;
  double delta,fopt,rho,fsave,ratio,xoptsq,sum,sumz,qoptsq,temp,delsav,snorm ;
  double vquad,xdiff,del,vqalt,diff,f,ssq,dffalt,distsq ;
  
  double *xbase=vector(n),**xpt=matrix(npt,n),*fval=vector(npt) ;
  double *xsav=vector(n+naux),*xopt=vector(n),*gopt=vector(n),*auxtemp ; 
  double *hq=vector((n*(n+1))/2),*pq=vector(npt),**bmat=matrix(n,ndim) ;
  double **zmat=matrix(nptm,npt),*step=vector(n),*sp=vector(2*npt) ; 
  double *xnew=vector(n),*rescon=vector(m),**qfac=matrix(n,n) ; 
  double *rfac=vector((n*(n+1))/2),*pqw=vector(npt),*w=vector(wlen) ;
  int *iact=ivector(n) ;
  if(func) auxtemp = 0 ; else auxtemp = vector(naux) ; 

//C     Set the elements of XBASE, XPT, FVAL, XSAV, XOPT, GOPT, HQ, PQ, BMAT,
//C       ZMAT and SP for the first iteration. An important feature is that,
//C       if the interpolation point XPT(K,.) is not feasible, where K is any
//C       integer from [1,NPT], then a change is made to XPT(K,.) if necessary
//C       so that the constraint violation is at least 0.2*RHOBEG. Also KOPT
//C       is set so that XPT(KOPT,.) is the initial trust region centre.

  idz = prelim(n,npt,m,amat,b,x,rhobeg,xbase,xpt,fval,xsav,gopt,kopt,pq,bmat,
               zmat,ndim,sp,rescon,maxflag,func,gunc,aux,naux) ;
  for(j=0;j<n;j++) xopt[j] = xpt[kopt][j] ; 

//C     Begin the iterative procedure.

  fopt = fval[kopt] ;
  delta = rho = rhobeg ;
  itest = 3 ; 
  ifeas = nact = nvala = nvalb = loopflag = 0 ;
  cond = knew = -1 ; 
  nf = npt ; 

  for(iter=0;;iter++) // begin main loop
  { if(iter>=n*maxfun) throw up("max iters %d*%d exceeded by lincoa",n,maxfun) ;
    fsave = fopt ; 

//C     Shift XBASE if XOPT may be too far from XBASE. First make the changes
//C       to BMAT that do not depend on ZMAT.

    for(xoptsq=i=0;i<n;i++) xoptsq += xopt[i] * xopt[i] ; 
    if(xoptsq>=1e4*delta*delta)
    { shiftxbase(n,npt,xpt,xopt,step,bmat,zmat,pq,hq,idz) ; 
      for(k=0;k<npt;k++) sp[k] = 0 ; 
//C     Update the right hand sides of the constraints.
      for(j=0;j<m;j++) 
      { for(temp=i=0;i<n;i++) temp += amat[j][i] * xopt[i] ; b[j] -= temp ; }
      for(j=0;j<n;j++) { xbase[j] += xopt[j] ; xopt[j] = xpt[kopt][j] = 0 ; } 
    }

//C     In the case KNEW=0, generate the next trust region step by calling
//C       TRSTEP, where SNORM is the current trust region radius initially.
//C       The final value of SNORM is the length of the calculated step,
//C       except that SNORM is zero on return if the projected gradient is
//C       unsuitable for starting the conjugate gradient iterations.

    delsav = delta ; 
    ksave = knew ; 
    iflag = 0 ; 
    if(knew==-1)
    { snorm = delta ; 
      for(i=0;i<n;i++) xnew[i] = gopt[i] ; 
      ncall = trstep(n,npt,m,amat,b,xpt,hq,pq,nact,iact,rescon,qfac,rfac,
                     snorm,step,xnew) ; 
//C     A trust region step is applied whenever its length, namely SNORM, is at
//C       least HALF*DELTA. It is also applied if its length is at least 0.1999
//C       times DELTA and if a line search of TRSTEP has caused a change to the
//C       active set. Otherwise there is a branch below to label 530 or 560.
//        [coded here by setting iflag according to the branch chosen - cjc]

      if(ncall>1) temp = 0.1999 * delta ; else temp = delta / 2 ; 
      if(snorm<=temp)
      { if(delta<=2.8*rho) delta = rho ; else delta /= 2 ; 
        nvala += 1 ; 
        nvalb += 1 ; 
        temp = snorm / rho ; 
        if(delsav>rho) temp = 1 ; 
        if(temp>=0.5) nvala = 0 ; 
        if(temp>=0.1) nvalb = 0 ; 

        if(delsav>rho||(nvala<5&&nvalb<3)) iflag = 1 ; 
        else if(snorm>0) iflag = 2 ; 
        else iflag = 3 ; 
      }
      else nvala = nvalb = 0 ; 
    }
    else
    {
//C     Alternatively, KNEW is positive. Then the model step is calculated
//C       within a trust region of radius DEL, after setting the gradient at
//C       XBASE and the second derivative parameters of the KNEW-th Lagrange
//C       function in W(1) to W(N) and in PQW(1) to PQW(NPT), respectively.

      if(0.1*delta>rho) del = 0.1*delta ; else del = rho ; 
      for(i=0;i<n;i++) w[i] = bmat[i][knew] ;
      genpqw(n,npt,pqw,zmat,knew,idz) ; 
      ifeas = qmstep(n,npt,m,amat,xpt,xopt,nact,iact,rescon,qfac,kopt,knew,del,
                     step,w,pqw) ;
    }

//C     Set VQUAD to the change to the quadratic model when the move STEP is
//C       made from XOPT. If STEP is a trust region step, then VQUAD should be
//C       negative. If it is nonnegative due to rounding errors in this case,
//C       there is a branch to label 530 to try to improve the model.

    if(iflag==0)
    { for(vquad=ih=j=0;j<n;j++) for(vquad+=step[j]*gopt[j],i=0;i<=j;i++,ih++)
      { temp = step[i] * step[j] ; 
        if(i==j) temp /= 2 ; 
        vquad += temp * hq[ih] ;
      }

      for(k=0;k<npt;k++)
      { for(temp=j=0;j<n;j++) temp += xpt[k][j] * step[j] ; 
        sp[npt+k] = temp ; 
        vquad += 0.5 * pq[k] * temp * temp ;
      }

      if(ksave==-1&&vquad>=0) iflag = 1 ; 
    }

//C     Calculate the next value of the objective function. The difference
//C       between the actual new value of F and the value predicted by the
//C       model is recorded in DIFF.

    if(iflag==0)
    { nf += 1 ; 
      if(nf>maxfun) { cond = 2 ; break ; } 
      for(xdiff=i=0;i<n;i++) 
      { xnew[i] = xopt[i] + step[i] ; 
        x[i] = xbase[i] + xnew[i] ; 
        temp = x[i] - xsav[i] ; 
        xdiff += temp * temp ; 
      }
      if(ksave==-2) xdiff = rho ; else xdiff = sqrt(xdiff) ; 
      if(xdiff<=0.1*rho||xdiff>=2*delta) { ifeas = 0 ; cond = 1 ; break ; }
      if(ksave<0) ifeas = 1 ; 
      if(func) f = func(x) ; 
      else { for(i=0;i<naux;i++) auxtemp[i] = aux[i] ; f = gunc(x,auxtemp) ; }
      if(maxflag) f = -f ; 
      if(ksave==-2) { cond = 0 ; break ; } 
      diff = f - fopt - vquad ; 

//C     If X is feasible, then set DFFALT to the difference between the new
//C       value of F and the value predicted by the alternative model.

      if(ifeas==1&&itest<3)
      { for(k=0;k<npt;k++) { pqw[k] = 0 ; w[k] = fval[k] - fval[kopt] ; }
        for(j=0;j<nptm;j++)
        { for(sum=k=0;k<npt;k++) sum += w[k] * zmat[j][k] ; 
          if(j<idz) sum = -sum ; 
          for(k=0;k<npt;k++) pqw[k] += sum * zmat[j][k] ; 
        }
        for(vqalt=k=0;k<npt;k++)
        { for(sum=j=0;j<n;j++) sum += bmat[j][k] * step[j] ; 
          vqalt += sum * w[k] ;
          vqalt += pqw[k] * sp[npt+k] * (sp[k]+sp[npt+k]/2) ; 
        }
        dffalt = f - fopt - vqalt ; 
      }
      else if(itest==3) { dffalt = diff ; itest = 0 ; } 

//C     Pick the next value of DELTA after a trust region step.

      if(ksave==-1)
      { ratio = (f-fopt) / vquad ; 
        if(ratio<0.1) delta /= 2 ; 
        else if(ratio<0.7) 
        { if(delta/2>snorm) delta /= 2 ; else delta = snorm ; }
        else
        { temp = sqrt(2)*delta ;
          if(delta/2>2*snorm) delta /= 2 ; else delta = 2*snorm ; 
          if(delta>temp) delta = temp ; 
        }
        if(delta<=1.4*rho) delta = rho ; 
      }

//C     Update BMAT, ZMAT and IDZ, so that the KNEW-th interpolation point
//C       can be moved. If STEP is a trust region step, then KNEW is zero at
//C       present, but a positive value is picked by subroutine UPDATE.

      lincupdate(n,npt,xpt,bmat,zmat,idz,ndim,sp,step,kopt,knew) ;
      if(knew==-1) { cond = 3 ; break ; } 

//C     If ITEST is increased to 3, then the next quadratic model is the
//C       one whose second derivative matrix is least subject to the new
//C       interpolation conditions. Otherwise the new model is constructed
//C       by the symmetric Broyden method in the usual way.

      if(ifeas==1) 
      { if(fabs(dffalt)>=0.1*fabs(diff)) itest = 0 ; else itest += 1 ; } 

//C     Update the second derivatives of the model by the symmetric Broyden
//C       method, using PQW for the second derivative parameters of the new
//C       KNEW-th Lagrange function. The contribution from the old parameter
//C       PQ(KNEW) is included in the second derivative matrix HQ. W is used
//C       later for the gradient of the new KNEW-th Lagrange function.       

      if(itest<3)
      { updatehq(n,npt,hq,pq[knew],xpt,knew) ;
        pq[knew] = 0 ; 
        genpqw(n,npt,pqw,zmat,knew,idz) ; 
        for(k=0;k<npt;k++) pq[k] += diff * pqw[k] ; 
      }

//C     Include the new interpolation point with the corresponding updates of
//C       SP. Also make the changes of the symmetric Broyden method to GOPT at
//C       the old XOPT if ITEST is less than 3.

      fval[knew] = f ; 
      sp[knew] = sp[kopt] + sp[npt+kopt] ; 
      for(ssq=i=0;i<n;i++) { xpt[knew][i] = xnew[i] ; ssq += step[i]*step[i] ; }
      sp[npt+knew] = sp[npt+kopt] + ssq ; 
      if(itest<3) 
      { for(i=0;i<n;i++) w[i] = bmat[i][knew] ; 
        for(k=0;k<npt;k++) 
          for(temp=pqw[k]*sp[k],i=0;i<n;i++) w[i] += temp * xpt[k][i] ;
        for(i=0;i<n;i++) gopt[i] += diff * w[i] ; 
      }

//C     Update FOPT, XSAV, XOPT, KOPT, RESCON and SP if the new F is the
//C       least calculated value so far with a feasible vector of variables.

      if(f<fopt&&ifeas==1)
      { fopt = f ; 
        for(j=0;j<n;j++) { xsav[j] = x[j] ; xopt[j] = xnew[j] ; } 
        for(j=0;j<naux;j++) xsav[n+j] = auxtemp[j] ; 
        kopt = knew ; 
        snorm = sqrt(ssq) ; 
        for(j=0;j<m;j++)
          if(rescon[j]>=delta+snorm) rescon[j] = snorm - rescon[j] ; 
          else
        { rescon[j] += snorm ; 
          if(rescon[j]+delta>0)
          { for(temp=b[j],i=0;i<n;i++) temp -= xopt[i] * amat[j][i] ; 
            if(temp<0) temp = 0 ;
            if(temp>=delta) rescon[j] = -temp ; else rescon[j] = temp ; 
          }
        }
        for(k=0;k<npt;k++) sp[k] += sp[npt+k] ; 

//C     Also revise GOPT when symmetric Broyden updating is applied.

        if(itest<3)
        { updategopt(n,gopt,hq,step) ; 
          for(k=0;k<npt;k++) 
            for(temp=pq[k]*sp[npt+k],i=0;i<n;i++) gopt[i] += temp * xpt[k][i] ;
        }
      } // end if(f<fopt&&ifeas==1)

      if(itest==3) 
      { 
//C     Replace the current model by the least Frobenius norm interpolant if
//C       this interpolant gives substantial reductions in the predictions
//C       of values of F at feasible points.

        for(k=0;k<npt;k++) { pq[k] = 0 ; w[k] = fval[k] - fval[kopt] ; }
        for(j=0;j<nptm;j++) 
        { for(sum=k=0;k<npt;k++) sum += w[k] * zmat[j][k] ; 
          if(j<idz) sum = -sum ; 
          for(k=0;k<npt;k++) pq[k] += sum * zmat[j][k] ; 
        }
        for(j=0;j<n;j++) 
          for(gopt[j]=i=0;i<npt;i++) gopt[j] += w[i] * bmat[j][i] ;
        for(k=0;k<npt;k++) 
          for(temp=pq[k]*sp[k],i=0;i<n;i++) gopt[i] += temp * xpt[k][i] ; 
        for(i=0;i<(n*(n+1))/2;i++) hq[i] = 0 ; 
      }

//C     If a trust region step has provided a sufficient decrease in F, then
//C       branch for another trust region calculation. Every iteration that
//C       takes a model step is followed by an attempt to take a trust region
//C       step.

      knew = -1 ; 
      if(ksave>=0||ratio>=0.1) { loopflag = 0 ; continue ; }
    } // end if(iflag==0)

//C     Alternatively, find out if the interpolation points are close enough
//C       to the best point so far (old label 530)

    if(iflag<2)
    { distsq = max(delta*delta,4*rho*rho) ; 
      for(k=0;k<npt;k++)
      { for(sum=j=0;j<n;j++) 
        { temp = xpt[k][j] - xopt[j] ; sum += temp * temp ; }
        if(sum>distsq) { knew = k ; distsq = sum ; }
      }

//C     If KNEW is positive, then branch back for the next iteration, which
//C       will generate a "model step". Otherwise, if the current iteration
//C       has reduced F, or if DELTA was above its lower bound when the last
//C       trust region step was calculated, then try a "trust region" step
//C       instead.

      if(knew>=0||fopt<fsave) { loopflag = 0 ; continue ; } 
      else if(loopflag==n) { cond = 4 ; break ; } 
      else if(delsav>rho) { loopflag += 1 ; continue ; }
    } // end if(iflag<2)
    loopflag = 0 ; 

//C     The calculations with the current value of RHO are complete.
//C       Pick the next value of RHO.

    if(rho<=rhoend) { cond = 0 ; break ; }
    delta = rho /2 ; 
    if(rho>250*rhoend) rho *= 0.1 ;
    else if(rho<16*rhoend) rho = rhoend ;  
    else rho = sqrt(rho*rhoend) ; 
    delta = max(delta,rho) ; 
    knew = -1 ; 
    nvala = nvalb = 0 ; 
  } // end for(iter=0;;iter++)

//C     Return from the calculation, after branching to label 220 for another
//C       Newton-Raphson step if it has not been tried before.

  if(iflag==2)
  { for(nf++,i=0;i<n;i++) x[i] = xbase[i] + xopt[i] + step[i]  ; 
    if(func) f = func(x) ; 
    else { for(i=0;i<naux;i++) auxtemp[i] = aux[i] ; f = gunc(x,auxtemp) ; }
    if(maxflag) f = -f ; 
  }
  if(fopt<=f||(ifeas==0&&iflag!=2)) 
  { for(f=fopt,i=0;i<n;i++) x[i] = xsav[i] ; 
    for(i=0;i<naux;i++) aux[i] = xsav[n+i] ; 
  }
  else for(i=0;i<naux;i++) aux[i] = auxtemp[i] ; 

  free(xbase,fval,xsav,xopt,gopt,hq) ; free(pq,step,sp,xnew,rescon,rfac) ; 
  free(pqw,w,iact,auxtemp) ; freematrix(bmat,zmat,qfac,xpt) ; 
  if(maxflag) return -f ; else return f ; 
}
/* -------------------------------------------------------------------------- */

int quadprog(double **Q,double *A,double *x,int n0,int n1,
             double **le,double *lerhs,int nle,
             double **ge,double *gerhs,int nge,
             double **eq,double *eqrhs,int neq) ;

double lincoastub(double (*func)(double *),double *x,int n,
                  double (*gunc)(double *,double *),double *aux,int naux,
                  double **a,double *b,int m,
                  double rhobeg,double rhoend,int maxfun,int npt,int maxflag)
{
  //C     This subroutine seeks the least value of a function of many variables,
  //C       subject to general linear inequality constraints, by a trust region
  //C       method that forms quadratic models by interpolation. Usually there
  //C       is much freedom in each new model after satisfying the interpolation
  //C       conditions, which is taken up by minimizing the Frobenius norm of
  //C       the change to the second derivative matrix of the model. One new
  //C       function value is calculated on each iteration, usually at a point
  //C       where the current model predicts a reduction in the least value so
  //C       far of the objective function subject to the linear constraints.
  //C       Alternatively, a new vector of variables may be chosen to replace
  //C       an interpolation point that may be too far away for reliability, and
  //C       then the new point does not have to satisfy the linear constraints.
  //C       The arguments of the subroutine are as follows.
  //C
  //C     N must be set to the number of variables and must be at least two.
  //C     NPT must be set to the number of interpolation conditions, which is
  //C       required to be in the interval [N+2,(N+1)(N+2)/2]. Typical choices
  //C       of the author are NPT=N+6 and NPT=2*N+1. Larger values tend to be
  //C       highly inefficent when the number of variables is substantial, due
  //C       to the amount of work and extra difficulty of adjusting more points.
  //C     M must be set to the number of linear inequality constraints.
  //C     A is a matrix whose columns are the constraint gradients, which are
  //C       required to be nonzero.
  //C     IA is the first dimension of the array A, which must be at least N.
  //C     B is the vector of right hand sides of the constraints, the J-th
  //C       constraint being that the scalar product of A(.,J) with X(.) is at
  //C       most B(J). The initial vector X(.) is made feasible by increasing
  //C       the value of B(J) if necessary.
  //C     X is the vector of variables. Initial values of X(1),X(2),...,X(N)
  //C       must be supplied. If they do not satisfy the constraints, then B
  //C       is increased as mentioned above. X contains on return the variables
  //C       that have given the least calculated F subject to the constraints.
  //C     RHOBEG and RHOEND must be set to the initial and final values of a
  //C       trust region radius, so both must be positive with RHOEND<=RHOBEG.
  //C       Typically, RHOBEG should be about one tenth of the greatest expected
  //C       change to a variable, and RHOEND should indicate the accuracy that
  //C       is required in the final values of the variables.
  //C     The value of IPRINT should be set to 0, 1, 2 or 3, which controls the
  //C       amount of printing. Specifically, there is no output if IPRINT=0 and
  //C       there is output only at the return if IPRINT=1. Otherwise, the best
  //C       feasible vector of variables so far and the corresponding value of
  //C       the objective function are printed whenever RHO is reduced, where
  //C       RHO is the current lower bound on the trust region radius. Further,
  //C       each new value of F with its variables are output if IPRINT=3.
  //C     MAXFUN must be set to an upper bound on the number of calls of CALFUN,
  //C       its value being at least NPT+1.
  //C     W is an array used for working space. Its length must be at least
  //C       M*(2+N) + NPT*(4+N+NPT) + N*(9+3*N) + MAX [ M+3*N, 2*M+N, 2*NPT ].
  //C       On return, W(1) is set to the final value of F, and W(2) is set to
  //C       the total number of function evaluations plus 0.5.
  //C
  //C     SUBROUTINE CALFUN (N,X,F) has to be provided by the user. It must set
  //C       F to the value of the objective function for the variables X(1),
  //C       X(2),...,X(N). The value of the argument F is positive when CALFUN
  //C       is called if and only if the current X satisfies the constraints
  //C       to working accuracy.
  //C
  //C     Check that N, NPT and MAXFUN are acceptable.

  double **amat,*bnorm,temp,sum,res,**Q,*A ; 
  int i,j,iflag,u ; 
  if(n<2) { cond = -1 ; return 0 ; } 
  if(npt<n+2||npt>((n+2)*(n+1))/2) { cond = -2 ; return 0 ; } 
  if(maxfun<npt) { cond = -3 ; return 0 ; } 

  amat = matrix(m,n) ; 
  bnorm = vector(m) ; 

  //C     Normalize the constraints, and copy the resultant constraint matrix
  //C       and right hand sides into working space, after increasing the right
  //C       hand sides if necessary so that the starting point is feasible.

  for(iflag=i=0;i<m;i++)
  { for(sum=temp=j=0;j<n;j++)
    { sum += a[i][j]*x[j] ; temp += a[i][j]*a[i][j] ; }
    if(temp==0) { cond = -4 ; freematrix(amat) ; free(bnorm) ; return 0 ; } 
    if(sum>b[i]) iflag = 1 ;
    temp = sqrt(temp) ; 
    bnorm[i] = b[i] / temp ; 
    for(j=0;j<n;j++) amat[i][j] = a[i][j] / temp ; 
  }

  if(iflag)
  { Q = matrix(n,n) ; 
    A = vector(n) ; 
    for(i=0;i<n;i++) { Q[i][i] = -1 ; A[i] = x[i] ; } 
    u = quadprog(Q,A,x,0,n, a,b,m, 0,0,0, 0,0,0) ; 
    freematrix(Q) ; 
    free(A) ; 
    if(u==-2) { cond = -5 ; freematrix(amat) ; free(bnorm) ; return 0 ; } 
    else if(u<0) { cond = -6 ; freematrix(amat) ; free(bnorm) ; return 0 ; } 
  }

  res = lincob(n,npt,m,amat,bnorm,x,rhobeg,rhoend,maxfun,npt+n,maxflag,
               func,gunc,aux,naux) ;
  freematrix(amat) ; free(bnorm) ; 
  return res ; 
}
/* -------------------------------------------------------------------------- */

double lincoa(double (*func)(double *),double *x,int n,double **a,double *b,
              int m,double rhobeg,double rhoend,int maxfun)
{ return lincoastub(func,x,n,0,0,0,a,b,m,rhobeg,rhoend,maxfun,2*n+1,0) ; }

double lincoa(double (*gunc)(double *,double *),double *x,int n,
              double *aux,int naux,double **a,double *b,int m,
              double rhobeg,double rhoend,int maxfun)
{ return lincoastub(0,x,n,gunc,aux,naux,a,b,m,rhobeg,rhoend,maxfun,2*n+1,0) ; }

double lincoamax(double (*func)(double *),double *x,int n,double **a,double *b,
                 int m,double rhobeg,double rhoend,int maxfun)
{ return lincoastub(func,x,n,0,0,0,a,b,m,rhobeg,rhoend,maxfun,2*n+1,1) ; }

double lincoamax(double (*gunc)(double *,double *),double *x,int n,
                 double *aux,int naux,double **a,double *b,int m,
                 double rhobeg,double rhoend,int maxfun)
{ return lincoastub(0,x,n,gunc,aux,naux,a,b,m,rhobeg,rhoend,maxfun,2*n+1,1) ; }

Archived from utils.html

#ifndef MEMORY_H #define MEMORY_H #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdarg.h> struct complex { double re,im ; complex() { re = im = 0 ; } complex(double x) { re = x ; im = 0 ; } complex(double x,double y) { re = x ; im = y ; } } ; #define max(a,b) ((a)>(b)?(a):(b)) #define min(a,b) ((a)<(b)?(a):(b)) /* --------------------- define up ------------------- */ static int cjcupline=-1,cjcperror=0 ; static const char *cjcupfile="",*cjcupfunc="" ; static int cjcup(const char *m,...) { va_list vl ; fprintf(stderr,"*** Error at line %d of %s [function %s]:\n", cjcupline,cjcupfile,cjcupfunc) ; if(cjcperror) perror(0) ; va_start(vl,m) ; vfprintf(stderr,m,vl) ; va_end(vl) ; fprintf(stderr,"\n") ; fflush(0) ; return 0 ; } ; static void cjcuplog(const char *f,int l,const char *ff) { cjcupfile = f ; cjcupline = l ; cjcupfunc = ff ; } #define up (cjcuplog(__FILE__,__LINE__,__PRETTY_FUNCTION__),cjcup) /* -------------------------- define variadic free() ------------------------ */ static void free(void *a,void *b) { free(a) ; free(b) ; } static void free(void *a,void *b,void *c) { free(a) ; free(b) ; free(c) ; } static void free(void *a,void *b,void *c,void *d) { free(a) ; free(b) ; free(c) ; free(d) ; } static void free(void *a,void *b,void *c,void *d,void *e) { free(a) ; free(b) ; free(c) ; free(d) ; free(e) ; } static void free(void *a,void *b,void *c,void *d,void *e,void *f) { free(a) ; free(b) ; free(c) ; free(d) ; free(e) ; free(f) ; } static void free(void *a,void *b,void *c,void *d,void *e,void *f,void *g) { free(a) ; free(b) ; free(c) ; free(d) ; free(e) ; free(f) ; free(g) ; } static void free(void *a,void *b,void *c,void *d, void *e,void *f,void *g,void *h) { free(a) ; free(b) ; free(c) ; free(d) ; free(e) ; free(f) ; free(g) ; free(h) ; } /* ------------------------- robust allocs ---------------------------- */ static void *cjcupalloc(int a,int b) { if(a<0||b<0) throw cjcup("negative length %d requested from cjcalloc.",a*b) ; void *p=calloc(a,b) ; if(p==0) throw cjcup("cjcalloc unable to allocate %d bytes of memory.",b) ; memset(p,0,a*b) ; return p ; } static void *cjcuprealloc(void *a,int b) { if(b<0) throw cjcup("negative length %d requested from cjcrealloc.",b) ; if(a==0&&b==0) return 0 ; void *p=realloc(a,b) ; if(b>0&&p==0) throw cjcup("cjcrealloc unable to reallocate %x to %d bytes.",a,b) ; return p ; } #define cjcalloc (cjcuplog(__FILE__,__LINE__,__PRETTY_FUNCTION__),cjcupalloc) #define cjcrealloc (cjcuplog(__FILE__,__LINE__,__PRETTY_FUNCTION__),cjcuprealloc) /* vector allocators - free by calling 'free' */ // -- vector static double *vector(int n) { return (double *) cjcalloc(n,sizeof(double)) ; } static double *vector(double *a,int n) { return (double *) cjcrealloc(a,n*sizeof(double)) ; } // -- ivector static int *ivector(int n) { return (int *) cjcalloc(n,sizeof(int)) ; } static int *ivector(int *a,int n) { return (int *) cjcrealloc(a,n*sizeof(int)) ; } // -- charvector static char *charvector(int n) { return (char *) cjcalloc(n,sizeof(char)) ; } static char *charvector(char *c) { char *ret=charvector(1+strlen(c)) ; strcpy(ret,c) ; return ret ; } static char *charvector(char *a,int n) { return (char *) cjcrealloc(a,n*sizeof(char)) ; } // -- strvector static char **strvector(int n) { return (char **) cjcalloc(n,sizeof(char*)) ; } static char **strvector(char **a,int n) { return (char **) cjcrealloc(a,n*sizeof(char*)) ; } // -- shortvector static short *shortvector(int n) { return (short *) cjcalloc(n,sizeof(short)) ; } static short *shortvector(short *a,int n) { return (short *) cjcrealloc(a,n*sizeof(short)) ; } /* 2- and 3- dimensional matrices of doubles */ static double **matrix(int m,int n) { double **a = (double **) cjcalloc(m,sizeof(double *)) ; a[0] = vector(m*n) ; for(int i=1;i<m;i++) a[i] = a[i-1] + n ; return a ; } static double ***matrix(int m,int n,int l) { int i ; double ***a = (double ***) cjcalloc(m,sizeof(double **)) ; a[0] = (double **) cjcalloc(m*n,sizeof(double *)) ; for(i=1;i<m;i++) a[i] = a[i-1] + n ; a[0][0] = vector(m*n*l) ; for(i=1;i<m*n;i++) a[0][i] = a[0][i-1] + l ; return a ; } /* free them */ static void freematrix(double **a) { if(a) free(a[0],a) ; } static void freematrix(double **a,double **b) { freematrix(a) ; freematrix(b) ; } static void freematrix(double **a,double **b,double **c) { freematrix(a) ; freematrix(b) ; freematrix(c) ; } static void freematrix(double **a,double **b,double **c,double **d) { freematrix(a) ; freematrix(b) ; freematrix(c) ; freematrix(d) ; } static void freematrix(double **a,double **b,double **c,double **d, double **e) { freematrix(a) ; freematrix(b) ; freematrix(c) ; freematrix(d) ; freematrix(e) ; } static void freematrix(double **a,double **b,double **c,double **d, double **e,double **f) { freematrix(a) ; freematrix(b) ; freematrix(c) ; freematrix(d) ; freematrix(e) ; freematrix(f) ; } static void freematrix(double ***a) { if(a) free(a[0][0],a[0],a) ; } /* 2- and 3- dimensional matrices of ints */ static int **imatrix(int m,int n) { int **a = (int **) cjcalloc(m,sizeof(int *)) ; a[0] = ivector(m*n) ; for(int i=1;i<m;i++) a[i] = a[i-1] + n ; return a ; } static int ***imatrix(int m,int n,int l) { int i ; int ***a = (int ***) cjcalloc(m,sizeof(int **)) ; a[0] = (int **) cjcalloc(m*n,sizeof(int *)) ; for(i=1;i<m;i++) a[i] = a[i-1] + n ; a[0][0] = ivector(m*n*l) ; for(i=1;i<m*n;i++) a[0][i] = a[0][i-1] + l ; return a ; } /* free them */ static void freeimatrix(int **a) { if(a) free(a[0],a) ; } static void freeimatrix(int ***a) { if(a) free(a[0][0],a[0],a) ; } /* swaps */ static void swap(int &a,int &b) { int c=b ; b = a ; a = c ; } static void swap(double &a,double &b) { double c=b ; b = a ; a = c ; } static void swap(char &a,char &b) { char c=b ; b = a ; a = c ; } static void swap(int &a,double &b) { int c=(int) b ; b = a ; a = c ; } static void swap(double &a,int &b) { int c=b ; b = (int) a ; a = c ; } /* ----------------------------- generic sort ------------------------------- */ #define shellsortup(u,n,b,x) \ { int i,j,incr ; b v ; \ for(incr=1;1+3*incr<(n);incr=1+3*incr) ; \ for(;incr>0;incr/=3) for(i=incr;i<(n);i++) \ { v = u[i] ; \ for(j=i;j>=incr;j-=incr) \ { if(v.x<u[j-incr].x) u[j] = u[j-incr] ; else break ; } \ u[j] = v ; \ } \ } #define shellsortdown(u,n,b,x) \ { int i,j,incr ; b v ; \ for(incr=1;1+3*incr<(n);incr=1+3*incr) ; \ for(;incr>0;incr/=3) for(i=incr;i<(n);i++) \ { v = u[i] ; \ for(j=i;j>=incr;j-=incr) \ { if(v.x>u[j-incr].x) u[j] = u[j-incr] ; else break ; } \ u[j] = v ; \ } \ } /* ----------------------------- integer sort ------------------------------- */ static void isortup(int *u,int n) { int i,j,incr,v ; for(incr=1;1+3*incr<n;incr=1+3*incr) ; for(;incr>0;incr/=3) for(i=incr;i<n;i++) { for(v=u[i],j=i;j>=incr;j-=incr) if(v<u[j-incr]) u[j] = u[j-incr] ; else break ; u[j] = v ; } } /* --- complexes ---- */ static complex *cvector(int n) { return (complex *) cjcalloc(n,sizeof(complex)) ; } static complex *cvector(complex *a,int n) { return (complex *) cjcrealloc(a,n*sizeof(complex)) ; } /* 2- and 3- dimensional matrices of doubles */ static complex **cmatrix(int m,int n) { complex **a = (complex **) cjcalloc(m,sizeof(complex *)) ; a[0] = cvector(m*n) ; for(int i=1;i<m;i++) a[i] = a[i-1] + n ; return a ; } static complex ***cmatrix(int m,int n,int l) { int i ; complex ***a = (complex ***) cjcalloc(m,sizeof(complex **)) ; a[0] = (complex **) cjcalloc(m*n,sizeof(complex *)) ; for(i=1;i<m;i++) a[i] = a[i-1] + n ; a[0][0] = cvector(m*n*l) ; for(i=1;i<m*n;i++) a[0][i] = a[0][i-1] + l ; return a ; } /* free them */ static void freecmatrix(complex **a) { if(a) free(a[0],a) ; } static void freecmatrix(complex ***a) { if(a) free(a[0][0],a[0],a) ; } /* -------------------------- define robust fopens -------------------------- */ static FILE *fupopenread(char *name) { FILE *f ; if(name[0]=='-'&&name[1]=='-'&&name[2]==0) f = stdin ; else f = fopen(name,"r") ; if(f==0) { cjcperror = 1 ; throw cjcup("Your input file %s could not be found.",name) ; } return f ; } static FILE *fupopenwrite(char *name) { FILE *f ; if(name[0]=='-'&&name[1]=='-'&&name[2]==0) f = stdout ; else f = fopen(name,"w") ; if(f==0) { cjcperror = 1 ; throw cjcup("Unable to write to your file %s.",name) ; } return f ; } #define fopenread (cjcuplog(__FILE__,__LINE__,__PRETTY_FUNCTION__),fupopenread) #define fopenwrite (cjcuplog(__FILE__,__LINE__,__PRETTY_FUNCTION__),fupopenwrite) static char *freadline(FILE *ifl) { char *s=0 ; int slen,ns,c,i ; for(slen=ns=0;;) { c = fgetc(ifl) ; if(c==EOF||c=='\n') { if(slen>ns+1||(ns==0&&c=='\n')) s = charvector(s,ns+1) ; return s ; } if(ns>=slen-1) { slen += 20 + slen/2 ; s = charvector(s,slen) ; for(i=ns;i<slen;i++) s[i] = 0 ; } s[ns++] = (char) c ; } } static char *readline() { return freadline(stdin) ; } #endif

Archived from optim.html

#include "memory.h" #include <math.h> #define max(a,b) ((a)>(b)?(a):(b)) #define min(a,b) ((a)<(b)?(a):(b)) double lincoa(double (*func)(double *),double *x,int n,double **a,double *b, int m,double rhobeg,double rhoend,int maxfun,int npt) ; int lincoacond() ; #define pi 3.141592653589793 static double flim ; static int ncall ; double func(double *x) { double v12,v13,v14,v23,v24,v34,del1,del2,del3,del4,f ; ncall += 1 ; v12 = x[0] * x[4] - x[3] * x[1] ; v13 = x[0] * x[7] - x[6] * x[1] ; v14 = x[0] * x[10] - x[9] * x[1] ; v23 = x[3] * x[7] - x[6] * x[4] ; v24 = x[3] * x[10] - x[9] * x[4] ; v34 = x[6] * x[10] - x[9] * x[7] ; del1 = v23 * x[11] - v24 * x[8] + v34 * x[5] ; del2 = -v34 * x[2] - v13 * x[11] + v14 * x[8] ; del3 = -v14 * x[5] + v24 * x[2] + v12 * x[11] ; del4 = -v12 * x[8] + v13 * x[5] - v23 * x[2] ; if(del1>0&&del2>0&&del3>0&&del4>0) { f = del1 + del2 + del3 + del4 ; f = (f*f*f) / (del1*del2*del3*del4) ; return min(f/6,flim) ; } else return flim ; } //C Calculate the tetrahedron of least volume that encloses the points //C (XP(J),YP(J),ZP(J)), J=1,2,...,NP. Our method requires the origin //C to be strictly inside the convex hull of these points. There are //C twelve variables that define the four faces of each tetrahedron //C that is considered. Each face has the form ALPHA*X+BETA*Y+GAMMA*Z=1, //C the variables X(3K-2), X(3K-1) and X(3K) being the values of ALPHA, //C BETA and GAMMA for the K-th face, K=1,2,3,4. Let the set T contain //C all points in three dimensions that can be reached from the origin //C without crossing a face. Because the volume of T may be infinite, //C the objective function is the smaller of FMAX and the volume of T, //C where FMAX is set to an upper bound on the final volume initially. //C There are 4*NP linear constraints on the variables, namely that each //C of the given points (XP(J),YP(J),ZP(J)) shall be in T. Let XS = min //C XP(J), YS = min YP(J), ZS = min ZP(J) and SS = max XP(J)+YP(J)+ZP(J), //C where J runs from 1 to NP. The initial values of the variables are //C X(1)=1/XS, X(5)=1/YS, X(9)=1/ZS, X(2)=X(3)=X(4)=X(6)=X(7) =X(8)=0 //C and X(10)=X(11)=X(12)=1/SS, which satisfy the linear constraints, //C and which provide the bound FMAX=(SS-XS-YS-ZS)**3/6. Other details //C of the test calculation are given below, including the choice of //C the data points (XP(J),YP(J),ZP(J)), J=1,2,...,NP. The smaller final //C value of the objective function in the case NPT=35 shows that the //C problem has local minima. //C int main(int argc,char const* argv[]) { int np=50,ia=12,n=12,m=200,i,j,k ; double xp[50],yp[50],zp[50],theta,sumx,sumy,sumz,xs,ys,zs,ss,res ; double b[200],x[12],**a=matrix(m,n) ; for(sumx=sumy=sumz=j=0;j<np;j++) { theta = j * pi / (np-1) ; sumx += xp[j] = cos(theta) * cos(2*theta) ; sumy += yp[j] = sin(theta) * cos(2*theta) ; sumz += zp[j] = sin(2*theta) ; } sumx /= np ; sumy /= np ; sumz /= np ; for(j=0;j<np;j++) { xp[j] -= sumx ; yp[j] -= sumy ; zp[j] -= sumz ; } for(k=0;k<m;k++) b[k] = 1 ; for(i=0;i<n;i++) for(k=0;k<m;k++) a[k][i] = 0 ; for(j=0;j<np;j++) for(i=0;i<4;i++) { a[4*j+i][3*i ] = xp[j] ; a[4*j+i][3*i+1] = yp[j] ; a[4*j+i][3*i+2] = zp[j] ; } //C Set the initial vector of variables. The JCASE=1,6 loop gives six //C different choices of NPT when LINCOA is called. for(xs=ys=zs=ss=j=0;j<np;j++) { xs = min(xs,xp[j]) ; ys = min(ys,yp[j]) ; zs = min(zs,zp[j]) ; ss = max(ss,xp[j]+yp[j]+zp[j]) ; } flim = ss - xs - ys - zs ; flim *= flim * flim / 6 ; for(j=0;j<6;j++) { for(i=0;i<12;i++) x[i] = 0 ; x[0] = 1 / xs ; x[4] = 1 / ys ; x[8] = 1 / zs ; x[9] = x[10] = x[11] = 1 / ss ; ncall = 0 ; res = lincoa(&func,x,n,a,b,m,1,1e-6,10000,5*j+15) ; printf("optimum = %.15e from %d calls; cond=%d\n",res,ncall,lincoacond()) ; } freematrix(a) ; }

Archived from optim.html

/* The original fortran codes are distributed without restrictions. The C++ codes are distributed under MIT license. */ /* The MIT License Copyright (c) nd., by M.J.D. Powell <mjdp@cam.ac.uk> 2016, by Colin Champion <colin.champion@masterlyinactivity.com> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include"memory.h" #include <math.h> #define max(a,b) ((a)>(b)?(a):(b)) #define min(a,b) ((a)<(b)?(a):(b)) int update(int,int,double **,double **,int,double *,double,int) ; void shiftxbase(int n,int npt,double **xp,double *xopt,double *vlag,double **b, double **z,double *pq,double *hq,int idz) ; double calcvlagbeta(int n,int npt,double *vlag,double **z,double **b,double *w, double *xopt,double *d,double beta,double dsq,double xsq, int idz) ; int chooseknew(int n,int npt,double beta,double **z,double *vlag,double **xp, double *xopt,double detrat,double rhosq,int ktemp,int idz) ; void genpqw(int n,int npt,double *pqw,double **z,int knew,int idz) ; void updatehq(int n,int npt,double *hq,double pqknew,double **xpt,int knew) ; void updategopt(int n,double *gopt,double *hq,double *step) ; void updategopt2(int n,int npt,double *gopt,double *xopt,double *pq,double **) ; //C These instructions rearrange the active constraints so that the new //C value of IACT(NACT) is the old value of IACT(IC). A sequence of //C Givens rotations is applied to the current QFAC and RFAC. Then NACT //C is reduced by one. static int rearrange(int ic,int nact,int n,int *iact,double *resact, double *rfac,double **qfac,double *vlam,double *resnew) { int jc,i,j,idiag,jw,jdiag ; double temp,cval,sval ; if((resnew[iact[ic]]=resact[ic])<1e-60) resnew[iact[ic]] = 1e-60 ; for(jc=ic;jc<nact-1;jc++) { jdiag = (jc*(jc+1)) / 2 ; idiag = ((jc+1)*(jc+2)) / 2 ; jw = idiag + jc ; temp = sqrt(rfac[jw]*rfac[jw]+rfac[jw+1]*rfac[jw+1]) ; cval = rfac[jw+1] / temp ; sval = rfac[jw] / temp ; rfac[jw] = sval * rfac[idiag-1] ; rfac[jw+1] = cval * rfac[idiag-1] ; rfac[idiag-1] = temp ; for(jw+=2,j=jc+2;j<nact;j++,jw+=j) { temp = sval*rfac[jw+jc] + cval*rfac[jw+jc+1] ; rfac[jw+jc+1] = cval*rfac[jw+jc] - sval*rfac[jw+jc+1] ; rfac[jw+jc] = temp ; } for(i=0;i<jc;i++) swap(rfac[idiag+i],rfac[jdiag+i]) ; for(i=0;i<n;i++) { temp = sval*qfac[jc][i] + cval*qfac[jc+1][i] ; qfac[jc+1][i] = cval*qfac[jc][i] - sval*qfac[jc+1][i] ; qfac[jc][i] = temp ; } iact[jc] = iact[jc+1] ; resact[jc] = resact[jc+1] ; vlam[jc] = vlam[jc+1] ; } return nact-1 ; } /* -------------------------------------------------------------------------- */ double getact(int n,int m,double **amat, double *b,int& nact, int *iact,double **qfac,double *rfac,double & snorm, double *resnew,double *resact,double *g,double *dw) { //C N, M, AMAT, B, NACT, IACT, QFAC and RFAC are the same as the terms //C with these names in SUBROUTINE LINCOB. The current values must be //C set on entry. NACT, IACT, QFAC and RFAC are kept up to date when //C GETACT changes the current active set. //C SNORM, RESNEW, RESACT, G and DW are the same as the terms with these //C names in SUBROUTINE TRSTEP. The elements of RESNEW and RESACT are //C also kept up to date. //C VLAM and W are used for working space, the vector VLAM being reserved //C for the Lagrange multipliers of the calculation. Their lengths must //C be at least N. //C The main purpose of GETACT is to pick the current active set. It is //C defined by the property that the projection of -G into the space //C orthogonal to the active constraint normals is as large as possible, //C subject to this projected steepest descent direction moving no closer //C to the boundary of every constraint whose current residual is at most //C 0.2*SNORM. On return, the settings in NACT, IACT, QFAC and RFAC are //C all appropriate to this choice of active set. //C Occasionally this projected direction is zero, and then the final value //C of W(1) is set to zero. Otherwise, the direction itself is returned //C in DW, and W(1) is set to the square of the length of the direction. //C //C Set some constants and a temporary VLAM. int i,j,k,ic,idiag,jw,l ; double tdel=0.2*snorm,ddsav,violmx,rdiag,sprod,temp,ctol ; double cosv,sinv,sum,dd,test,dnorm,vmult ; double *vlam=vector(n),*w=vector(n) ; for(ddsav=i=0;i<n;i++) { ddsav += g[i]*g[i] ; vlam[i] = 0 ; } ddsav *= 2 ; //C Set the initial QFAC to the identity matrix in the case NACT=0. if(nact==0) for(i=0;i<n;i++) { for(j=0;j<i;j++) qfac[j][i] = qfac[i][j] = 0 ; qfac[i][i] = 1 ; } else //C Remove any constraints from the initial active set whose residuals //C exceed TDEL. { for(ic=nact-1;ic>=0;ic--) if(resact[ic]>tdel) nact = rearrange(ic,nact,n,iact,resact,rfac,qfac,vlam,resnew) ; //C Remove any constraints from the initial active set whose Lagrange //C multipliers are nonnegative, and set the surviving multipliers. for(ic=nact-1;ic>=0;ic--) { for(temp=i=0;i<n;i++) temp += qfac[ic][i] * g[i] ; idiag = ((ic+1)*(ic+2))/2 ; for(jw=idiag+ic,j=ic+1;j<nact;j++,jw+=j) temp -= rfac[jw] * vlam[j] ; if(temp>=0) ic = nact = rearrange(ic,nact,n,iact,resact,rfac,qfac,vlam,resnew) ; else vlam[ic] = temp / rfac[idiag-1] ; } } //C Set the new search direction D. Terminate if the 2-norm of D is zero //C or does not decrease, or if NACT=N holds. The situation NACT=N //C occurs for sufficiently large SNORM if the origin is in the convex //C hull of the constraint gradients. while(nact<n) { for(j=nact;j<n;j++) for(w[j]=i=0;i<n;i++) w[j] += qfac[j][i] * g[i] ; for(dd=i=0;i<n;i++) { for(dw[i]=0,j=nact;j<n;j++) dw[i] -= w[j] * qfac[j][i] ; dd += dw[i] * dw[i] ; } if(dd>=ddsav||dd==0) { free(vlam,w) ; return 0 ; } ddsav = dd ; dnorm = sqrt(dd) ; //C Pick the next integer L or terminate, a positive value of L being //C the index of the most violated constraint. The purpose of CTOL //C below is to estimate whether a positive value of VIOLMX may be //C due to computer rounding errors. l = -1 ; if(m>0) { test = dnorm / snorm ; for(violmx=j=0;j<m;j++) if(resnew[j]>0&&resnew[j]<=tdel) { for(sum=i=0;i<n;i++) sum += amat[j][i] * dw[i] ; if(sum>test*resnew[j]&&sum>violmx) { l = j ; violmx = sum ; } } ctol= 0 ; temp = 0.01 * dnorm ; if(violmx>0&&violmx<temp) for(k=0;k<nact;k++) { for(j=iact[k],sum=i=0;i<n;i++) sum += dw[i] * amat[j][i] ; if(fabs(sum)>ctol) ctol = fabs(sum) ; } } if(l<0||violmx<=10*ctol) { free(vlam,w) ; return dd ; } //C Apply Givens rotations to the last (N-NACT) columns of QFAC so that //C the first (NACT+1) columns of QFAC are the ones required for the //C addition of the L-th constraint, and add the appropriate column //C to RFAC. idiag = (nact*(nact+1))/2 ; for(rdiag=0,j=n-1;j>=0;j--) { for(sprod=i=0;i<n;i++) sprod += qfac[j][i] * amat[l][i] ; if(j<nact) rfac[idiag+j] = sprod ; else if(fabs(rdiag)<=1e-20*fabs(sprod)) rdiag = sprod ; else { temp = sqrt(sprod*sprod+rdiag*rdiag) ; cosv = sprod / temp ; sinv = rdiag / temp ; rdiag = temp ; for(i=0;i<n;i++) { temp = cosv*qfac[j][i] + sinv*qfac[j+1][i] ; qfac[j+1][i] = -sinv*qfac[j][i] + cosv*qfac[j+1][i] ; qfac[j][i] = temp ; } } } if(rdiag<0) for(i=0;i<n;i++) qfac[nact][i] = -qfac[nact][i] ; rfac[idiag+nact] = fabs(rdiag) ; iact[nact] = l ; resact[nact] = resnew[l] ; vlam[nact] = resnew[l] = 0 ; nact += 1 ; //C Set the components of the vector VMU in W. for(ic=0;violmx>0||ic>=0;) // while(violmx>0) but with at least 1 iteration { temp = rfac[(nact*(nact+1))/2-1] ; w[nact-1] = 1/(temp*temp) ; for(i=nact-2;i>=0;i--) { idiag = ((i+1)*(i+2))/2 ; for(sum=0,jw=idiag+i,j=i+1;j<nact;j++,jw+=j) sum -= rfac[jw] * w[j] ; w[i] = sum / rfac[idiag-1] ; } //C Calculate the multiple of VMU to subtract from VLAM, and update VLAM. vmult = violmx ; for(ic=-1,j=0;j<nact-1;j++) if(vlam[j]>=vmult*w[j]) { ic = j ; vmult = vlam[j] / w[j] ; } for(j=0;j<nact;j++) vlam[j] -= vmult * w[j] ; if(ic>=0) vlam[ic] = 0 ; violmx -= vmult ; if(ic<0||violmx<0) violmx = 0 ; //C Reduce the active set if necessary, so that all components of the //C new VLAM are negative, with resetting of the residuals of the //C constraints that become inactive. for(ic=nact-1;ic>=0;ic--) if(vlam[ic]>=0) nact = rearrange(ic,nact,n,iact,resact,rfac,qfac,vlam,resnew) ; //C Calculate the next VMU if VIOLMX is positive. Return if NACT=N holds, //C as then the active constraints imply D=0. Otherwise, go to label //C 100, to calculate the new D and to test for termination. } // end while(violmx>0) } // end while(nact<n) free(vlam,w) ; return 0 ; } /* -------------------------------------------------------------------------- */ int trstep(int n,int npt,int m,double **amat,double *b,double **xpt,double *hq, double *pq,int& nact,int *iact,double *rescon,double **qfac, double *rfac,double& snorm,double *step,double *g) { //C N, NPT, M, AMAT, B, XPT, HQ, PQ, NACT, IACT, RESCON, QFAC and RFAC //C are the same as the terms with these names in LINCOB. If RESCON(J) //C is negative, then |RESCON(J)| must be no less than the trust region //C radius, so that the J-th constraint can be ignored. //C SNORM is set to the trust region radius DELTA initially. On the //C return, however, it is the length of the calculated STEP, which is //C set to zero if the constraints do not allow a long enough step. //C STEP is the total calculated step so far from the trust region centre, //C its final value being given by the sequence of CG iterations, which //C terminate if the trust region boundary is reached. //C G must be set on entry to the gradient of the quadratic model at the //C trust region centre. It is used as working space, however, and is //C always the gradient of the model at the current STEP, except that //C on return the value of G(1) is set to ONE instead of to ZERO if //C and only if GETACT is called more than once. //C RESNEW, RESACT, D, DW and W are used for working space. A negative //C value of RESNEW(J) indicates that the J-th constraint does not //C restrict the CG steps of the current trust region calculation, a //C zero value of RESNEW(J) indicates that the J-th constraint is active, //C and otherwise RESNEW(J) is set to the greater of TINY and the actual //C residual of the J-th constraint for the current STEP. RESACT holds //C the residuals of the active constraints, which may be positive. //C D is the search direction of each line search. DW is either another //C search direction or the change in gradient along D. The length of W //C must be at least MAX[M,2*N]. int i,j,k,continuing,ncall,ir,jsav,icount,ih ; double ctest=0.01,snsq=snorm*snorm,tiny=1e-60,dnorm,scale,resmax,ss,sum,rhs ; double temp,gamma,sumrhs,dd,ds,ad,adw,dg,dgd,alpha,alphm,alpht,alpbd,reduct ; double wgd,beta ; double *resnew=vector(m),*resact=vector(n),*d=vector(n),*dw=vector(n) ; double *w=vector(m>2*n?m:2*n) ; //C Set the initial elements of RESNEW, RESACT and STEP. for(j=0;j<m;j++) { resnew[j] = rescon[j] ; if(resnew[j]>=snorm) resnew[j] = -1 ; else if(resnew[j]>=0&&resnew[j]<tiny) resnew[j] = tiny ; } if(m>0) for(k=0;k<nact;k++) { resact[k] = rescon[iact[k]] ; resnew[iact[k]] = 0 ; } for(i=0;i<n;i++) step[i] = 0 ; //C GETACT picks the active set for the current STEP. It also sets DW to //C the vector closest to -G that is orthogonal to the normals of the //C active constraints. DW is scaled to have length 0.2*SNORM, as then //C a move of DW from STEP is allowed by the linear constraints. for(ss=reduct=ncall=0,continuing=1;continuing;) { dnorm = getact(n,m,amat,b,nact,iact,qfac,rfac,snorm,resnew,resact,g,dw) ; ncall += 1 ; if(dnorm==0) break ; scale = 0.2*snorm/sqrt(dnorm) ; for(i=0;i<n;i++) dw[i] *= scale ; //C If the modulus of the residual of an active constraint is substantial, //C then set D to the shortest move from STEP to the boundaries of the //C active constraints. for(resmax=k=0;k<nact;k++) if(resact[k]>resmax) resmax = resact[k] ; gamma = 0 ; if(resmax>snorm*1e-4) { for(ir=k=0;k<nact;k++,ir++) { for(temp=resact[k],i=0;i<k;ir++,i++) temp -= rfac[ir] * w[i] ; w[k] = temp / rfac[ir] ; } for(i=0;i<n;i++) for(d[i]=k=0;k<nact;k++) d[i] += w[k] * qfac[k][i] ; //C The vector D that has just been calculated is also the shortest move //C from STEP+DW to the boundaries of the active constraints. Set GAMMA //C to the greatest steplength of this move that satisfies the trust //C region bound. for(rhs=snsq,ds=dd=i=0;i<n;i++) { sum = step[i] + dw[i] ; rhs -= sum * sum ; ds += d[i] * sum ; dd += d[i] * d[i] ; } if(rhs>0) { temp = sqrt(ds*ds+dd*rhs) ; if(ds<=0) gamma = (temp-ds) / dd ; else gamma = rhs / (temp+ds) ; } //C Reduce the steplength GAMMA if necessary so that the move along D //C also satisfies the linear constraints. for(j=0;j<m&&gamma>0;j++) if(resnew[j]>0) { for(ad=adw=i=0;i<n;i++) { ad += amat[j][i] * d[i] ; adw += amat[j][i] * dw[i] ; } if(ad>0) { if(0>(temp=(resnew[j]-adw)/ad)) temp = 0 ; if(temp<gamma) gamma = temp ; } } if(gamma>1) gamma = 1 ; } // end if(resmax>snorm*1e-4) //C Set the next direction for seeking a reduction in the model function //C subject to the trust region bound and the linear constraints. if(gamma<=0) { for(i=0;i<n;i++) d[i] = dw[i] ; icount = nact ; } else { for(i=0;i<n;i++) d[i] = dw[i] + gamma*d[i] ; icount = nact-1 ; } //C Set ALPHA to the steplength from STEP along D to the trust region //C boundary. Return if the first derivative term of this step is //C sufficiently small or if no further progress is possible. for(alpbd=1;;alpbd=0) { icount += 1 ; rhs = snsq - ss ; if(rhs<=0) { continuing = 0 ; break ; } // break both loops for(dg=ds=dd=i=0;i<n;i++) { dg += d[i] * g[i] ; ds += d[i] * step[i] ; dd += d[i] * d[i] ; } if(dg>=0) { continuing = 0 ; break ; } temp = sqrt(rhs*dd+ds*ds) ; if(ds<=0) alpha = (temp-ds)/dd ; else alpha = rhs/(temp+ds) ; if(-alpha*dg<=ctest*reduct) { continuing = 0 ; break ; } //C Set DW to the change in gradient along D. for(j=0;j<n;j++) dw[j] = 0 ; updategopt(n,dw,hq,d) ; updategopt2(n,npt,dw,d,pq,xpt) ; //C Set DGD to the curvature of the model along D. Then reduce ALPHA if //C necessary to the value that minimizes the model. for(dgd=i=0;i<n;i++) dgd += d[i] * dw[i] ; alpht = alpha ; if(dg+alpha*dgd>0) alpha = -dg/dgd ; //C Make a further reduction in ALPHA if necessary to preserve feasibility, //C and put some scalar products of D with constraint gradients in W. alphm = alpha ; for(jsav=-1,j=0;j<m;j++) { w[j] = 0 ; if(resnew[j]>0) { for(i=0;i<n;i++) w[j] += amat[j][i] * d[i] ; if(alpha*w[j]>resnew[j]) { alpha = resnew[j] / w[j] ; jsav = j ; } } } if(alpha<alpbd) alpha = alpbd ; if(alpha>alphm) alpha = alphm ; if(icount==nact&&alpha>1) alpha = 1 ; //C Update STEP, G, RESNEW, RESACT and REDUCT. for(ss=i=0;i<n;i++) { step[i] += alpha*d[i] ; ss += step[i]*step[i] ; g[i] += alpha*dw[i] ; } for(j=0;j<m;j++) if(resnew[j]>0) if((resnew[j]-=alpha*w[j])<tiny) resnew[j] = tiny ; if(icount==nact) for(k=0;k<nact;k++) resact[k] *= (1-gamma) ; reduct -= alpha * (dg+0.5*alpha*dgd) ; //C Test for termination. Branch to label 40 if there is a new active //C constraint and if the distance from STEP to the trust region //C boundary is at least 0.2*SNORM. if( alpha==alpht || -alphm*(dg+0.5*alphm*dgd)<=ctest*reduct ) { continuing = 0 ; break ; } if(jsav>=0) { continuing = (ss<=0.64*snsq) ; break ; } if(icount==n) { continuing = 0 ; break ; } //C Calculate the next search direction, which is conjugate to the //C previous one except in the case ICOUNT=NACT. if(nact>0) { for(j=nact;j<n;j++) for(w[j]=i=0;i<n;i++) w[j] += g[i] * qfac[j][i] ; for(i=0;i<n;i++) { for(temp=0,j=nact;j<n;j++) temp += qfac[j][i]*w[j] ; w[n+i] = temp ; } } else for(i=0;i<n;i++) w[n+i] = g[i] ; if(icount==nact) beta = 0 ; else { for(wgd=i=0;i<n;i++) wgd += w[n+i]*dw[i] ; beta = wgd / dgd ; } for(i=0;i<n;i++) d[i] = -w[n+i] + beta*d[i] ; } } //C Return from the subroutine. if(reduct>0) snorm = sqrt(ss) ; else snorm = 0 ; free(resnew,resact,dw,d,w) ; return ncall ; } /* -------------------------------------------------------------------------- */ int qmstep(int n,int npt,int m,double **amat,double **xpt,double *xopt,int nact, int *iact,double *rescon,double **qfac,int kopt,int knew,double del, double *step,double *gl,double *pqw) { //C N, NPT, M, AMAT, B, XPT, XOPT, NACT, IACT, RESCON, QFAC, KOPT are the //C same as the terms with these names in SUBROUTINE LINCOB. //C KNEW is the index of the interpolation point that is going to be moved. //C DEL is the current restriction on the length of STEP, which is never //C greater than the current trust region radius DELTA. //C STEP will be set to the required step from XOPT to the new point. //C GL must be set on entry to the gradient of LFUNC at XBASE, where LFUNC //C is the KNEW-th Lagrange function. It is used also for some other //C gradients of LFUNC. //C PQW provides the second derivative parameters of LFUNC. //C RSTAT and W are used for working space. Their lengths must be at least //C M and N, respectively. RSTAT(J) is set to -1.0, 0.0, or 1.0 if the //C J-th constraint is irrelevant, active, or both inactive and relevant, //C respectively. //C IFEAS will be set to 0 or 1 if XOPT+STEP is infeasible or feasible. //C //C STEP is chosen to provide a relatively large value of the modulus of //C LFUNC(XOPT+STEP), subject to ||STEP|| .LE. DEL. A projected STEP is //C calculated too, within the trust region, that does not alter the //C residuals of the active constraints. The projected step is preferred //C if its value of | LFUNC(XOPT+STEP) | is at least one fifth of the //C original one, but the greatest violation of a linear constraint must //C be at least 0.2*DEL, in order to keep the interpolation points apart. //C The remedy when the maximum constraint violation is too small is to //C restore the original step, which is perturbed if necessary so that //C its maximum constraint violation becomes 0.2*DEL. int i,j,k,ksav,jsav,ifeas,*istat=ivector(m) ; double test=0.2*del,temp,vbig,ss,sp,stp,vlag,stpsav,gg,vgrad,ghg,ww,ctol ; double sum,vnew,resmax,bigv,*w=vector(n) ; //C Replace GL by the gradient of LFUNC at the trust region centre, and //C set the elements of RSTAT. for(k=0;k<npt;k++) { for(temp=j=0;j<n;j++) temp += xpt[k][j] * xopt[j] ; temp *= pqw[k] ; for(i=0;i<n;i++) gl[i] += temp*xpt[k][i] ; } for(j=0;j<m;j++) { if(fabs(rescon[j])>=del) istat[j] = -1 ; else istat[j] = 1 ; } if(m>0) for(k=0;k<nact;k++) istat[iact[k]] = 0 ; //C Find the greatest modulus of LFUNC on a line through XOPT and //C another interpolation point within the trust region. for(vbig=k=0;k<npt;k++) if(k!=kopt) { for(sp=ss=i=0;i<n;i++) { temp = xpt[k][i] - xopt[i] ; ss += temp * temp ; sp += gl[i] * temp ; } stp = -del / sqrt(ss) ; if(k==knew) { if(sp*(sp-1)<0) stp = -stp ; vlag = fabs(stp*sp) + stp*stp*fabs(sp-1) ; } else vlag = fabs(stp*(1-stp)*sp) ; if(vlag>vbig) { ksav = k ; stpsav = stp ; vbig = vlag ; } } //C Set STEP to the move that gives the greatest modulus calculated above. //C This move may be replaced by a steepest ascent step from XOPT. for(gg=i=0;i<n;i++) { gg += gl[i]*gl[i] ; step[i] = stpsav*(xpt[ksav][i]-xopt[i]) ; } vgrad = del * sqrt(gg) ; if(vgrad>vbig/10) { //C Make the replacement if it provides a larger value of VBIG. for(ghg=k=0;k<npt;k++) { for(temp=j=0;j<n;j++) temp += xpt[k][j] * gl[j] ; ghg += pqw[k] * temp * temp ; } vnew = vgrad + fabs(0.5*del*del*ghg/gg) ; if(vnew>vbig) { vbig = vnew ; stp = del / sqrt(gg) ; if(ghg<0) stp = -stp ; for(i=0;i<n;i++) step[i] = stp * gl[i] ; } if(nact!=0&&nact!=n) { //C Overwrite GL by its projection. Then set VNEW to the greatest //C value of |LFUNC| on the projected gradient from XOPT subject to //C the trust region bound. If VNEW is sufficiently large, then STEP //C may be changed to a move along the projected gradient. for(k=nact;k<n;k++) for(w[k]=i=0;i<n;i++) w[k] += gl[i] * qfac[k][i] ; for(gg=i=0;i<n;i++) { for(gl[i]=0,k=nact;k<n;k++) gl[i] += qfac[k][i] * w[k] ; gg += gl[i] * gl[i] ; } vgrad = del * sqrt(gg) ; if(vgrad>vbig/10) { for(ghg=k=0;k<npt;k++) { for(temp=j=0;j<n;j++) temp += xpt[k][j] * gl[j] ; ghg += pqw[k] * temp * temp ; } vnew = vgrad + fabs(0.5*del*del*ghg/gg) ; //C Set W to the possible move along the projected gradient. stp = del / sqrt(gg) ; if(ghg<0) stp = -stp ; for(ww=i=0;i<n;i++) { w[i] = stp * gl[i] ; ww += w[i]*w[i] ; } //C Set STEP to W if W gives a sufficiently large value of the modulus //C of the Lagrange function, and if W either preserves feasibility //C or gives a constraint violation of at least 0.2*DEL. The purpose //C of CTOL below is to provide a check on feasibility that includes //C a tolerance for contributions from computer rounding errors. if(vnew/vbig>=0.2) { for(ifeas=1,bigv=j=0;j<m;j++) { if(istat[j]==1) { for(temp=-rescon[j],i=0;i<n;i++) temp += w[i] * amat[j][i] ; if(temp>bigv) bigv = temp ; } if(bigv>=test) { ifeas = 0 ; break ; } } ctol = 0 ; temp = sqrt(ww) / 100 ; if(bigv>0&&bigv<temp) for(k=0;k<nact;k++) { for(j=iact[k],sum=i=0;i<n;i++) sum += w[i] * amat[j][i] ; if(fabs(sum)>ctol) ctol = fabs(sum) ; } if(bigv<=10*ctol||bigv>=test) { for(i=0;i<n;i++) step[i] = w[i] ; free(istat,w) ; return ifeas ; } } } } } //C Calculate the greatest constraint violation at XOPT+STEP with STEP at //C its original value. Modify STEP if this violation is unacceptable. for(ifeas=1,bigv=resmax=jsav=j=0;j<m;j++) if(istat[j]>=0) { for(temp=-rescon[j],i=0;i<n;i++) temp += step[i] * amat[j][i] ; if(resmax<temp) resmax = temp ; if(temp>=test) { ifeas = 0 ; break ; } if(temp>bigv) { bigv = temp ; jsav = j ; ifeas = -1 ; } } if(ifeas<0) { for(i=0;i<n;i++) step[i] += (test-bigv) * amat[jsav][i] ; ifeas = 0 ; } free(istat,w) ; return ifeas ; } /* -------------------------------------------------------------------------- */ void lincupdate(int n,int npt,double **xpt,double **bmat,double **zmat,int &idz, int ndim,double *sp,double *step,int kopt,int &knew) { int i,j,k,nptm=npt-n-1,jp ; double sum,ssq,temp,tempa,tempb,distsq,hdiag,denom,denabs,denmax ; double beta,*vlag=vector(npt+n),*w=vector(npt) ; //C The arguments N, NPT, XPT, BMAT, ZMAT, IDZ, NDIM ,SP and STEP are //C identical to the corresponding arguments in SUBROUTINE LINCOB. //C KOPT is such that XPT(KOPT,.) is the current trust region centre. //C KNEW on exit is usually positive, and then it is the index of an //C interpolation point to be moved to the position XPT(KOPT,.)+STEP(.). //C It is set on entry either to its final value or to 0. In the latter //C case, the final value of KNEW is chosen to maximize the denominator //C of the matrix updating formula times a weighting factor. //C VLAG and W are used for working space, the first NPT+N elements of //C both of these vectors being required. //C //C The arrays BMAT and ZMAT with IDZ are updated, the new matrices being //C the ones that are suitable after the shift of the KNEW-th point to //C the new position XPT(KOPT,.)+STEP(.). A return with KNEW set to zero //C occurs if the calculation fails due to a zero denominator in the //C updating formula, which should never happen. for(k=0;k<npt;k++) { w[k] = sp[npt+k] * (sp[npt+k]/2+sp[k]) ; for(vlag[k]=j=0;j<n;j++) vlag[k] += bmat[j][k] * step[j] ; } for(ssq=i=0;i<n;i++) ssq += step[i] * step[i] ; beta = calcvlagbeta(n,npt,vlag,zmat,bmat,w,xpt[kopt],step,beta,ssq,sp[kopt],idz) ; vlag[kopt] += 1 ; //C If KNEW is zero initially, then pick the index of the interpolation //C point to be deleted, by maximizing the absolute value of the //C denominator of the updating formula times a weighting factor. if(knew<0) knew = chooseknew(n,npt,beta,zmat,vlag,xpt,xpt[kopt],0,0,-1,idz) ; //C and call the NEWUOA update to do the rest idz = update(n,npt,bmat,zmat,idz,vlag,beta,knew) ; free(vlag,w) ; } /* -------------------------------------------------------------------------- */ int prelim(int n,int npt,int m,double **amat,double *b,double *x,double rhobeg, double *xbase,double **xpt,double *fval,double *xsav, double *gopt,int &kopt,double *pq,double **bmat,double **zmat, int ndim,double *sp,double *rescon,double (*func)(double *)) { int nptm=npt-n-1,kbase=0,i,j,k,itemp,ipt,jpt,jsav,jp,nf,idz ; double rhosq=rhobeg*rhobeg,recip=1/rhosq,test=0.2*rhobeg,feas,bigv,resid,f ; double temp,reciq=sqrt(0.5)/rhosq ; double *step=vector(n),*w=vector(n+npt) ; //C The arguments N, NPT, M, AMAT, B, X, RHOBEG, IPRINT, XBASE, XPT, FVAL, //C XSAV, XOPT, GOPT, HQ, PQ, BMAT, ZMAT, NDIM, SP and RESCON are the //C same as the corresponding arguments in SUBROUTINE LINCOB. //C KOPT is set to the integer such that XPT(KOPT,.) is the initial trust //C region centre. //C IDZ is going to be set to one, so that every element of Diag(DZ) is //C one in the product ZMAT times Diag(DZ) times ZMAT^T, which is the //C factorization of the leading NPT by NPT submatrix of H. //C STEP, PQW and W are used for working space, the arrays STEP and PQW //C being taken from LINCOB. The length of W must be at least N+NPT. //C //C SUBROUTINE PRELIM provides the elements of XBASE, XPT, BMAT and ZMAT //C for the first iteration, an important feature being that, if any of //C of the columns of XPT is an infeasible point, then the largest of //C the constraint violations there is at least 0.2*RHOBEG. It also sets //C the initial elements of FVAL, XOPT, GOPT, HQ, PQ, SP and RESCON. //C Set the initial elements of XPT, BMAT, SP and ZMAT to zero. idz = 0 ; for(j=0;j<n;j++) { xbase[j] = x[j] ; for(k=0;k<npt;k++) xpt[k][j] = 0 ; for(i=0;i<ndim;i++) bmat[j][i] = 0 ; } for(k=0;k<npt;k++) { sp[k] = 0 ; for(j=0;j<nptm;j++) zmat[j][k] = 0 ; } //C Set the nonzero coordinates of XPT(K,.), K=1,2,...,min[2*N+1,NPT], //C but they may be altered later to make a constraint violation //C sufficiently large. The initial nonzero elements of BMAT and of //C the first min[N,NPT-N-1] columns of ZMAT are set also. //C for(j=0;j<n;j++) { xpt[j+1][j] = rhobeg ; if(j<nptm) { jp = n + j + 1 ; xpt[jp][j] = -rhobeg ; bmat[j][j+1] = rhobeg/2 ; bmat[j][jp] = -rhobeg/2 ; zmat[j][0] = -2*reciq ; zmat[j][j+1] = zmat[j][jp] = reciq ; } else { bmat[j][0] = -(bmat[j][j+1] = 1/rhobeg) ; bmat[j][npt+j] = -rhosq/2 ; } } //C Set the remaining initial nonzero elements of XPT and ZMAT when the //C number of interpolation points exceeds 2*N+1. for(k=n;k<nptm;k++) { itemp = k / n ; ipt = k - itemp*n ; jpt = ipt + itemp ; if(jpt>=n) jpt -= n ; xpt[n+k+1][ipt] = xpt[n+k+1][jpt] = rhobeg ; zmat[k][0] = zmat[k][n+k+1] = recip ; zmat[k][ipt+1] = zmat[k][jpt+1] = -recip ; } //C Update the constraint right hand sides to allow for the shift XBASE. for(j=0;j<m;j++) { for(temp=i=0;i<n;i++) temp += amat[j][i] * xbase[i] ; b[j] -= temp ; } //C Go through the initial points, shifting every infeasible point if //C necessary so that its constraint violation is at least 0.2*RHOBEG. for(nf=0;nf<npt;nf++) { for(feas=1,bigv=j=0;j<m&&nf>0;j++) { for(resid=-b[j],i=0;i<n;i++) resid += xpt[nf][i] * amat[j][i] ; if(resid<=bigv) continue ; bigv = resid ; jsav = j ; if(resid>test) { feas = 0 ; break ; } else feas = -1 ; } if(feas<0) { for(i=0;i<n;i++) step[i] = xpt[nf][i] + (test-bigv)*amat[jsav][i] ; for(k=0;k<npt;k++) for(sp[npt+k]=j=0;j<n;j++) sp[npt+k] += xpt[k][j]*step[j] ; lincupdate(n,npt,xpt,bmat,zmat,idz,ndim,sp,step,kbase,nf) ; for(i=0;i<n;i++) xpt[nf][i] = step[i] ; } //C Calculate the objective function at the current interpolation point, //C and set KOPT to the index of the first trust region centre. for(j=0;j<n;j++) x[j] = xbase[j] + xpt[nf][j] ; f = feas ; f = func(x) ; if(nf==0) kopt = 0 ; else if(f<fval[kopt]&&feas>0) kopt = nf ; fval[nf] = f ; } //C Set PQ for the first quadratic model. for(j=0;j<nptm;j++) for(w[j]=k=0;k<npt;k++) w[j] += zmat[j][k]*fval[k] ; for(k=0;k<npt;k++) for(pq[k]=j=0;j<nptm;j++) pq[k] += zmat[j][k] * w[j] ; //C Set XOPT, SP, GOPT and HQ for the first quadratic model. for(j=0;j<n;j++) { xsav[j] = xbase[j] + xpt[kopt][j] ; gopt[j] = 0 ; } for(k=0;k<npt;k++) { for(sp[k]=j=0;j<n;j++) sp[k] += xpt[k][j] * xpt[kopt][j] ; temp = pq[k] * sp[k] ; for(j=0;j<n;j++) gopt[j] += fval[k]*bmat[j][k] + temp*xpt[k][j] ; } //C Set the initial elements of RESCON. for(j=0;j<m;j++) { for(rescon[j]=b[j],i=0;i<n;i++) rescon[j] -= xpt[kopt][i] * amat[j][i] ; if(rescon[j]<0) rescon[j] = 0 ; if(rescon[j]>=rhobeg) rescon[j] = -rescon[j] ; } free(step,w) ; return idz ; } /* -------------------------------------------------------------------------- */ static int cond=-6 ; int lincoacond() { return cond ; } double lincob(int n,int npt,int m,double **amat,double *b,double *x, double rhobeg,double rhoend,int maxfun,int ndim, double (*func)(double *)) { //C The arguments N, NPT, M, X, RHOBEG, RHOEND, IPRINT and MAXFUN are //C identical to the corresponding arguments in SUBROUTINE LINCOA. //C AMAT is a matrix whose columns are the constraint gradients, scaled //C so that they have unit length. //C B contains on entry the right hand sides of the constraints, scaled //C as above, but later B is modified for variables relative to XBASE. //C XBASE holds a shift of origin that should reduce the contributions //C from rounding errors to values of the model and Lagrange functions. //C XPT contains the interpolation point coordinates relative to XBASE. //C FVAL holds the values of F at the interpolation points. //C XSAV holds the best feasible vector of variables so far, without any //C shift of origin. //C XOPT is set to XSAV-XBASE, which is the displacement from XBASE of //C the feasible vector of variables that provides the least calculated //C F so far, this vector being the current trust region centre. //C GOPT holds the gradient of the quadratic model at XSAV = XBASE+XOPT. //C HQ holds the explicit second derivatives of the quadratic model. //C PQ contains the parameters of the implicit second derivatives of the //C quadratic model. //C BMAT holds the last N columns of the big inverse matrix H. //C ZMAT holds the factorization of the leading NPT by NPT submatrix //C of H, this factorization being ZMAT times Diag(DZ) times ZMAT^T, //C where the elements of DZ are plus or minus one, as specified by IDZ. //C NDIM is the first dimension of BMAT and has the value NPT+N. //C STEP is employed for trial steps from XOPT. It is also used for working //C space when XBASE is shifted and in PRELIM. //C SP is reserved for the scalar products XOPT^T XPT(K,.), K=1,2,...,NPT, //C followed by STEP^T XPT(K,.), K=1,2,...,NPT. //C XNEW is the displacement from XBASE of the vector of variables for //C the current calculation of F, except that SUBROUTINE TRSTEP uses it //C for working space. //C IACT is an integer array for the indices of the active constraints. //C RESCON holds useful information about the constraint residuals. Every //C nonnegative RESCON(J) is the residual of the J-th constraint at the //C current trust region centre. Otherwise, if RESCON(J) is negative, the //C J-th constraint holds as a strict inequality at the trust region //C centre, its residual being at least |RESCON(J)|; further, the value //C of |RESCON(J)| is at least the current trust region radius DELTA. //C QFAC is the orthogonal part of the QR factorization of the matrix of //C active constraint gradients, these gradients being ordered in //C accordance with IACT. When NACT is less than N, columns are added //C to QFAC to complete an N by N orthogonal matrix, which is important //C for keeping calculated steps sufficiently close to the boundaries //C of the active constraints. //C RFAC is the upper triangular part of this QR factorization, beginning //C with the first diagonal element, followed by the two elements in the //C upper triangular part of the second column and so on. //C PQW is used for working space, mainly for storing second derivative //C coefficients of quadratic functions. Its length is NPT+N. //C The array W is also used for working space. The required number of //C elements, namely MAX[M+3*N,2*M+N,2*NPT], is set in LINCOA. int i,j,k,np=n+1,nptm=npt-n-1,ncall,nf,nvala,nvalb,knew,iflag,iter ; int itest,kopt,ksave,ifeas,nact,idz=-1,ih,wlen=max(m+3*n,max(2*m+n,2*npt)) ; double delta,fopt,rho,fsave,ratio,xoptsq,sum,sumz,qoptsq,temp,delsav,snorm ; double vquad,xdiff,del,vqalt,diff,f,ssq,dffalt,distsq ; double *xbase=vector(n),**xpt=matrix(npt,n),*fval=vector(npt) ; double *xsav=vector(n),*xopt=vector(n),*gopt=vector(n) ; double *hq=vector((n*(n+1))/2),*pq=vector(npt),**bmat=matrix(n,ndim) ; double **zmat=matrix(nptm,npt),*step=vector(n),*sp=vector(2*npt) ; double *xnew=vector(n),*rescon=vector(m),**qfac=matrix(n,n) ; double *rfac=vector((n*(n+1))/2),*pqw=vector(npt),*w=vector(wlen) ; int *iact=ivector(n) ; //C Set the elements of XBASE, XPT, FVAL, XSAV, XOPT, GOPT, HQ, PQ, BMAT, //C ZMAT and SP for the first iteration. An important feature is that, //C if the interpolation point XPT(K,.) is not feasible, where K is any //C integer from [1,NPT], then a change is made to XPT(K,.) if necessary //C so that the constraint violation is at least 0.2*RHOBEG. Also KOPT //C is set so that XPT(KOPT,.) is the initial trust region centre. idz = prelim(n,npt,m,amat,b,x,rhobeg,xbase,xpt,fval,xsav,gopt,kopt,pq,bmat, zmat,ndim,sp,rescon,func) ; for(j=0;j<n;j++) xopt[j] = xpt[kopt][j] ; //C Begin the iterative procedure. fopt = fval[kopt] ; delta = rho = rhobeg ; itest = 3 ; ifeas = nact = nvala = nvalb = 0 ; cond = knew = -1 ; nf = npt ; for(iter=0;;iter++) // begin main loop { fsave = fopt ; //C Shift XBASE if XOPT may be too far from XBASE. First make the changes //C to BMAT that do not depend on ZMAT. for(xoptsq=i=0;i<n;i++) xoptsq += xopt[i] * xopt[i] ; if(xoptsq>=1e4*delta*delta) { shiftxbase(n,npt,xpt,xopt,step,bmat,zmat,pq,hq,idz) ; for(k=0;k<npt;k++) sp[k] = 0 ; //C Update the right hand sides of the constraints. for(j=0;j<m;j++) { for(temp=i=0;i<n;i++) temp += amat[j][i] * xopt[i] ; b[j] -= temp ; } for(j=0;j<n;j++) { xbase[j] += xopt[j] ; xopt[j] = xpt[kopt][j] = 0 ; } } //C In the case KNEW=0, generate the next trust region step by calling //C TRSTEP, where SNORM is the current trust region radius initially. //C The final value of SNORM is the length of the calculated step, //C except that SNORM is zero on return if the projected gradient is //C unsuitable for starting the conjugate gradient iterations. delsav = delta ; ksave = knew ; iflag = 0 ; if(knew==-1) { snorm = delta ; for(i=0;i<n;i++) xnew[i] = gopt[i] ; ncall = trstep(n,npt,m,amat,b,xpt,hq,pq,nact,iact,rescon,qfac,rfac, snorm,step,xnew) ; //C A trust region step is applied whenever its length, namely SNORM, is at //C least HALF*DELTA. It is also applied if its length is at least 0.1999 //C times DELTA and if a line search of TRSTEP has caused a change to the //C active set. Otherwise there is a branch below to label 530 or 560. if(ncall>1) temp = 0.1999 * delta ; else temp = delta / 2 ; if(snorm<=temp) { if(delta<=2.8*rho) delta = rho ; else delta /= 2 ; nvala += 1 ; nvalb += 1 ; temp = snorm / rho ; if(delsav>rho) temp = 1 ; if(temp>=0.5) nvala = 0 ; if(temp>=0.1) nvalb = 0 ; if(delsav>rho||(nvala<5&&nvalb<3)) iflag = 1 ; else if(snorm>0) iflag = 2 ; else iflag = 3 ; } else nvala = nvalb = 0 ; } else { //C Alternatively, KNEW is positive. Then the model step is calculated //C within a trust region of radius DEL, after setting the gradient at //C XBASE and the second derivative parameters of the KNEW-th Lagrange //C function in W(1) to W(N) and in PQW(1) to PQW(NPT), respectively. if(0.1*delta>rho) del = 0.1*delta ; else del = rho ; for(i=0;i<n;i++) w[i] = bmat[i][knew] ; genpqw(n,npt,pqw,zmat,knew,idz) ; ifeas = qmstep(n,npt,m,amat,xpt,xopt,nact,iact,rescon,qfac,kopt,knew,del, step,w,pqw) ; } //C Set VQUAD to the change to the quadratic model when the move STEP is //C made from XOPT. If STEP is a trust region step, then VQUAD should be //C negative. If it is nonnegative due to rounding errors in this case, //C there is a branch to label 530 to try to improve the model. if(iflag==0) { for(vquad=ih=j=0;j<n;j++) for(vquad+=step[j]*gopt[j],i=0;i<=j;i++,ih++) { temp = step[i] * step[j] ; if(i==j) temp /= 2 ; vquad += temp * hq[ih] ; } for(k=0;k<npt;k++) { for(temp=j=0;j<n;j++) temp += xpt[k][j] * step[j] ; sp[npt+k] = temp ; vquad += 0.5 * pq[k] * temp * temp ; } if(ksave==-1&&vquad>=0) iflag = 1 ; } //C Calculate the next value of the objective function. The difference //C between the actual new value of F and the value predicted by the //C model is recorded in DIFF. if(iflag==0) { nf += 1 ; if(nf>maxfun) { cond = 2 ; break ; } for(xdiff=i=0;i<n;i++) { xnew[i] = xopt[i] + step[i] ; x[i] = xbase[i] + xnew[i] ; temp = x[i] - xsav[i] ; xdiff += temp * temp ; } if(ksave==-2) xdiff = rho ; else xdiff = sqrt(xdiff) ; if(xdiff<=0.1*rho||xdiff>=2*delta) { ifeas = 0 ; cond = 1 ; break ; } if(ksave<0) ifeas = 1 ; f = func(x) ; if(ksave==-2) { cond = 0 ; break ; } diff = f - fopt - vquad ; //C If X is feasible, then set DFFALT to the difference between the new //C value of F and the value predicted by the alternative model. if(ifeas==1&&itest<3) { for(k=0;k<npt;k++) { pqw[k] = 0 ; w[k] = fval[k] - fval[kopt] ; } for(j=0;j<nptm;j++) { for(sum=k=0;k<npt;k++) sum += w[k] * zmat[j][k] ; if(j<idz) sum = -sum ; for(k=0;k<npt;k++) pqw[k] += sum * zmat[j][k] ; } for(vqalt=k=0;k<npt;k++) { for(sum=j=0;j<n;j++) sum += bmat[j][k] * step[j] ; vqalt += sum * w[k] ; vqalt += pqw[k] * sp[npt+k] * (sp[k]+sp[npt+k]/2) ; } dffalt = f - fopt - vqalt ; } else if(itest==3) { dffalt = diff ; itest = 0 ; } //C Pick the next value of DELTA after a trust region step. if(ksave==-1) { ratio = (f-fopt) / vquad ; if(ratio<0.1) delta /= 2 ; else if(ratio<0.7) { if(delta/2>snorm) delta /= 2 ; else delta = snorm ; } else { temp = sqrt(2)*delta ; if(delta/2>2*snorm) delta /= 2 ; else delta = 2*snorm ; if(delta>temp) delta = temp ; } if(delta<=1.4*rho) delta = rho ; } //C Update BMAT, ZMAT and IDZ, so that the KNEW-th interpolation point //C can be moved. If STEP is a trust region step, then KNEW is zero at //C present, but a positive value is picked by subroutine UPDATE. lincupdate(n,npt,xpt,bmat,zmat,idz,ndim,sp,step,kopt,knew) ; if(knew==-1) { cond = 3 ; break ; } //C If ITEST is increased to 3, then the next quadratic model is the //C one whose second derivative matrix is least subject to the new //C interpolation conditions. Otherwise the new model is constructed //C by the symmetric Broyden method in the usual way. if(ifeas==1) { if(fabs(dffalt)>=0.1*fabs(diff)) itest = 0 ; else itest += 1 ; } //C Update the second derivatives of the model by the symmetric Broyden //C method, using PQW for the second derivative parameters of the new //C KNEW-th Lagrange function. The contribution from the old parameter //C PQ(KNEW) is included in the second derivative matrix HQ. W is used //C later for the gradient of the new KNEW-th Lagrange function. if(itest<3) { updatehq(n,npt,hq,pq[knew],xpt,knew) ; pq[knew] = 0 ; genpqw(n,npt,pqw,zmat,knew,idz) ; for(k=0;k<npt;k++) pq[k] += diff * pqw[k] ; } //C Include the new interpolation point with the corresponding updates of //C SP. Also make the changes of the symmetric Broyden method to GOPT at //C the old XOPT if ITEST is less than 3. fval[knew] = f ; sp[knew] = sp[kopt] + sp[npt+kopt] ; for(ssq=i=0;i<n;i++) { xpt[knew][i] = xnew[i] ; ssq += step[i]*step[i] ; } sp[npt+knew] = sp[npt+kopt] + ssq ; if(itest<3) { for(i=0;i<n;i++) w[i] = bmat[i][knew] ; for(k=0;k<npt;k++) for(temp=pqw[k]*sp[k],i=0;i<n;i++) w[i] += temp * xpt[k][i] ; for(i=0;i<n;i++) gopt[i] += diff * w[i] ; } //C Update FOPT, XSAV, XOPT, KOPT, RESCON and SP if the new F is the //C least calculated value so far with a feasible vector of variables. if(f<fopt&&ifeas==1) { fopt = f ; for(j=0;j<n;j++) { xsav[j] = x[j] ; xopt[j] = xnew[j] ; } kopt = knew ; snorm = sqrt(ssq) ; for(j=0;j<m;j++) if(rescon[j]>=delta+snorm) rescon[j] = snorm - rescon[j] ; else { rescon[j] += snorm ; if(rescon[j]+delta>0) { for(temp=b[j],i=0;i<n;i++) temp -= xopt[i] * amat[j][i] ; if(temp<0) temp = 0 ; if(temp>=delta) rescon[j] = -temp ; else rescon[j] = temp ; } } for(k=0;k<npt;k++) sp[k] += sp[npt+k] ; //C Also revise GOPT when symmetric Broyden updating is applied. if(itest<3) { updategopt(n,gopt,hq,step) ; for(k=0;k<npt;k++) for(temp=pq[k]*sp[npt+k],i=0;i<n;i++) gopt[i] += temp * xpt[k][i] ; } } // end if(f<fopt&&ifeas==1) if(itest==3) { //C Replace the current model by the least Frobenius norm interpolant if //C this interpolant gives substantial reductions in the predictions //C of values of F at feasible points. for(k=0;k<npt;k++) { pq[k] = 0 ; w[k] = fval[k] - fval[kopt] ; } for(j=0;j<nptm;j++) { for(sum=k=0;k<npt;k++) sum += w[k] * zmat[j][k] ; if(j<idz) sum = -sum ; for(k=0;k<npt;k++) pq[k] += sum * zmat[j][k] ; } for(j=0;j<n;j++) for(gopt[j]=i=0;i<npt;i++) gopt[j] += w[i] * bmat[j][i] ; for(k=0;k<npt;k++) for(temp=pq[k]*sp[k],i=0;i<n;i++) gopt[i] += temp * xpt[k][i] ; for(i=0;i<(n*(n+1))/2;i++) hq[i] = 0 ; } //C If a trust region step has provided a sufficient decrease in F, then //C branch for another trust region calculation. Every iteration that //C takes a model step is followed by an attempt to take a trust region //C step. knew = -1 ; if(ksave>=0||ratio>=0.1) continue ; } // end if(iflag==0) //C Alternatively, find out if the interpolation points are close enough //C to the best point so far. if(iflag<2) { distsq = max(delta*delta,4*rho*rho) ; for(k=0;k<npt;k++) { for(sum=j=0;j<n;j++) { temp = xpt[k][j] - xopt[j] ; sum += temp * temp ; } if(sum>distsq) { knew = k ; distsq = sum ; } } //C If KNEW is positive, then branch back for the next iteration, which //C will generate a "model step". Otherwise, if the current iteration //C has reduced F, or if DELTA was above its lower bound when the last //C trust region step was calculated, then try a "trust region" step //C instead. if(knew>=0||fopt<fsave||delsav>rho) continue ; } // end if(iflag<2) //C The calculations with the current value of RHO are complete. //C Pick the next value of RHO. if(rho<=rhoend) { cond = 0 ; break ; } delta = rho /2 ; if(rho>250*rhoend) rho *= 0.1 ; else if(rho<16*rhoend) rho = rhoend ; else rho = sqrt(rho*rhoend) ; delta = max(delta,rho) ; knew = -1 ; nvala = nvalb = 0 ; } // end for(iter=0;;iter++) //C Return from the calculation, after branching to label 220 for another //C Newton-Raphson step if it has not been tried before. if(iflag==2) { for(nf++,i=0;i<n;i++) x[i] = xbase[i] + xopt[i] + step[i] ; f = func(x) ; } if(fopt<=f||(ifeas==0&&iflag!=2)) for(f=fopt,i=0;i<n;i++) x[i] = xsav[i] ; free(xbase,fval,xsav,xopt,gopt,hq) ; free(pq,step,sp,xnew,rescon,rfac) ; free(pqw,w,iact) ; freematrix(bmat,zmat,qfac,xpt) ; return f ; } /* -------------------------------------------------------------------------- */ int quadprog(double **Q,double *A,double *x,int n0,int n1, double **le,double *lerhs,int nle, double **ge,double *gerhs,int nge, double **eq,double *eqrhs,int neq) ; double lincoa(double (*func)(double *),double *x,int n,double **a,double *b, int m,double rhobeg,double rhoend,int maxfun,int npt) { //C This subroutine seeks the least value of a function of many variables, //C subject to general linear inequality constraints, by a trust region //C method that forms quadratic models by interpolation. Usually there //C is much freedom in each new model after satisfying the interpolation //C conditions, which is taken up by minimizing the Frobenius norm of //C the change to the second derivative matrix of the model. One new //C function value is calculated on each iteration, usually at a point //C where the current model predicts a reduction in the least value so //C far of the objective function subject to the linear constraints. //C Alternatively, a new vector of variables may be chosen to replace //C an interpolation point that may be too far away for reliability, and //C then the new point does not have to satisfy the linear constraints. //C The arguments of the subroutine are as follows. //C //C N must be set to the number of variables and must be at least two. //C NPT must be set to the number of interpolation conditions, which is //C required to be in the interval [N+2,(N+1)(N+2)/2]. Typical choices //C of the author are NPT=N+6 and NPT=2*N+1. Larger values tend to be //C highly inefficent when the number of variables is substantial, due //C to the amount of work and extra difficulty of adjusting more points. //C M must be set to the number of linear inequality constraints. //C A is a matrix whose columns are the constraint gradients, which are //C required to be nonzero. //C IA is the first dimension of the array A, which must be at least N. //C B is the vector of right hand sides of the constraints, the J-th //C constraint being that the scalar product of A(.,J) with X(.) is at //C most B(J). The initial vector X(.) is made feasible by increasing //C the value of B(J) if necessary. //C X is the vector of variables. Initial values of X(1),X(2),...,X(N) //C must be supplied. If they do not satisfy the constraints, then B //C is increased as mentioned above. X contains on return the variables //C that have given the least calculated F subject to the constraints. //C RHOBEG and RHOEND must be set to the initial and final values of a //C trust region radius, so both must be positive with RHOEND<=RHOBEG. //C Typically, RHOBEG should be about one tenth of the greatest expected //C change to a variable, and RHOEND should indicate the accuracy that //C is required in the final values of the variables. //C The value of IPRINT should be set to 0, 1, 2 or 3, which controls the //C amount of printing. Specifically, there is no output if IPRINT=0 and //C there is output only at the return if IPRINT=1. Otherwise, the best //C feasible vector of variables so far and the corresponding value of //C the objective function are printed whenever RHO is reduced, where //C RHO is the current lower bound on the trust region radius. Further, //C each new value of F with its variables are output if IPRINT=3. //C MAXFUN must be set to an upper bound on the number of calls of CALFUN, //C its value being at least NPT+1. //C W is an array used for working space. Its length must be at least //C M*(2+N) + NPT*(4+N+NPT) + N*(9+3*N) + MAX [ M+3*N, 2*M+N, 2*NPT ]. //C On return, W(1) is set to the final value of F, and W(2) is set to //C the total number of function evaluations plus 0.5. //C //C SUBROUTINE CALFUN (N,X,F) has to be provided by the user. It must set //C F to the value of the objective function for the variables X(1), //C X(2),...,X(N). The value of the argument F is positive when CALFUN //C is called if and only if the current X satisfies the constraints //C to working accuracy. //C //C Check that N, NPT and MAXFUN are acceptable. double **amat,*bnorm,temp,sum,res,**Q,*A ; int i,j,iflag,u ; if(n<2) { cond = -1 ; return 0 ; } if(npt<n+2||npt>((n+2)*(n+1))/2) { cond = -2 ; return 0 ; } if(maxfun<npt) { cond = -3 ; return 0 ; } amat = matrix(m,n) ; bnorm = vector(m) ; //C Normalize the constraints, and copy the resultant constraint matrix //C and right hand sides into working space, after increasing the right //C hand sides if necessary so that the starting point is feasible. for(iflag=i=0;i<m;i++) { for(sum=temp=j=0;j<n;j++) { sum += a[i][j]*x[j] ; temp += a[i][j]*a[i][j] ; } if(temp==0) { cond = -4 ; freematrix(amat) ; free(bnorm) ; return 0 ; } if(sum>b[i]) iflag = 1 ; temp = sqrt(temp) ; bnorm[i] = b[i] / temp ; for(j=0;j<n;j++) amat[i][j] = a[i][j] / temp ; } if(iflag) { Q = matrix(n,n) ; A = vector(n) ; for(i=0;i<n;i++) { Q[i][i] = -1 ; A[i] = x[i] ; } u = quadprog(Q,A,x,0,n, a,b,m, 0,0,0, 0,0,0) ; freematrix(Q) ; free(A) ; if(u==-2) { cond = -5 ; freematrix(amat) ; free(bnorm) ; return 0 ; } else if(u<0) { cond = -6 ; freematrix(amat) ; free(bnorm) ; return 0 ; } } res = lincob(n,npt,m,amat,bnorm,x,rhobeg,rhoend,maxfun,npt+n,func) ; freematrix(amat) ; free(bnorm) ; return res ; } double lincoa(double (*func)(double *),double *x,int n,double **a,double *b, int m,double rhobeg,double rhoend,int maxfun) { return lincoa(func,x,n,a,b,m,rhobeg,rhoend,maxfun,2*n+1) ; }

Archived from optim.html

/* The original fortran codes are distributed without restrictions. The C++ codes are distributed under MIT license. */ /* The MIT License Copyright (c) 2004, by M.J.D. Powell <mjdp@cam.ac.uk> 2008, by Attractive Chaos <attractivechaos@aol.co.uk> 2015, by Colin Champion <colin.champion@masterlyinactivity.com> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include"memory.h" #include <math.h> #define max(a,b) ((a)>(b)?(a):(b)) #define min(a,b) ((a)<(b)?(a):(b)) void updategopt(int n,double *gopt,double *hq,double *step) { int i,j,ih ; for(ih=j=0;j<n;j++) for(i=0;i<=j;i++,ih++) { if(i<j) gopt[j] += hq[ih] * step[i] ; gopt[i] += hq[ih] * step[j] ; } } void updategopt2(int n,int npt,double *gopt,double *xopt,double *pq,double **xp) { int i,k ; double sum,temp ; for(k=0;k<npt;k++) { for(sum=i=0;i<n;i++) sum += xp[k][i] * xopt[i]; temp = pq[k] * sum ; for(i=0;i<n;i++) gopt[i] += temp * xp[k][i] ; } } /* -------------------------------------------------------------------------- */ int update(int n,int npt,double **b,double **z,int idz,double *vlag, double beta,int knew) { /* The arrays BMAT and ZMAT with IDZ are updated, in order to shift * the interpolation point that has index KNEW. On entry, VLAG * contains the components of the vector Theta*Wcheck+e_b of the * updating formula (6.11), and BETA holds the value of the * parameter that has this name. */ int nptm=npt-n-1,i,j,ja,jb,jl,jp,iflag ; double tau,temp,scala,scalb,alpha,denom,tempa,tempb,tausq,sqrtdn ; double *w=vector(npt+n) ; /* Apply the rotations that put zeros in the KNEW-th row of ZMAT. */ for(jl=0,j=1;j<npt-n-1;j++) { if(j==idz) jl = j ; else if(z[j][knew]!=0) { tempa = z[jl][knew]; tempb = z[j][knew]; temp = sqrt(tempa*tempa+tempb*tempb) ; tempa /= temp ; tempb /= temp ; for(i=0;i<npt;i++) { temp = tempa*z[jl][i] + tempb*z[j][i] ; z[j][i] = tempa*z[j][i] - tempb*z[jl][i] ; z[jl][i] = temp ; } z[j][knew] = 0; } } /* Put the first NPT components of the KNEW-th column of HLAG into * W, and calculate the parameters of the updating formula. */ if(idz<1) tempa = z[0][knew] ; else tempa = -z[0][knew] ; for(i=0;i<npt;i++) w[i] = tempa * z[0][i] ; if(jl>0) for(tempb=z[jl][knew],i=0;i<npt;i++) w[i] += tempb * z[jl][i] ; alpha = w[knew] ; tau = vlag[knew] ; tausq = tau * tau ; denom = alpha*beta + tausq ; vlag[knew] -= 1 ; /* Complete the updating of ZMAT when there is only 1.0 nonzero * element in the KNEW-th row of the new matrix ZMAT, but, ifIFLAG * is set to 1.0, then the first column of ZMAT will be exchanged * with another 1.0 later. */ iflag = 0 ; sqrtdn = sqrt(fabs(denom)) ; /* ------------------------------------------------------------------------ */
/*page*/ /* ------------------------------------------------------------------------ */ if(jl==0) { tempa = tau / sqrtdn ; tempb = z[0][knew] / sqrtdn ; for(i=0;i<npt;i++) z[0][i] = tempa*z[0][i] - tempb*vlag[i] ; if(denom<0) { if(idz==0) idz = 1 ; else iflag = 1 ; } } else { /* Complete the updating of ZMAT in the alternative case. */ if(beta>=0) ja = jl ; else ja = 0 ; jb = jl - ja ; temp = z[jb][knew] / denom ; tempa = temp * beta ; tempb = temp * tau ; temp = z[ja][knew] ; scala = 1 / sqrt(fabs(beta)*temp*temp+tausq) ; scalb = scala * sqrtdn ; for(i=0;i<npt;i++) { z[ja][i] = scala * (tau * z[ja][i] - temp * vlag[i]) ; z[jb][i] = scalb * (z[jb][i] - tempa * w[i] - tempb * vlag[i]) ; } if(denom<=0) { if(beta<0) idz += 1 ; else iflag = 1 ; } } /* IDZ is reduced in the following case, and usually the first * column of ZMAT is exchanged with a later 1.0. */ if(iflag==1) for(idz-=1,i=0;i<npt;i++) swap(z[0][i],z[idz][i]) ; /* Finally, update the matrix BMAT. */ for(j=0;j<n;j++) { jp = npt + j ; w[jp] = b[j][knew] ; tempa = (alpha*vlag[jp] - tau*w[jp]) / denom ; tempb = -(beta*w[jp] + tau*vlag[jp]) / denom ; for(i=0;i<=jp;i++) { b[j][i] += tempa*vlag[i] + tempb*w[i] ; if(i>=npt) b[i-npt][jp] = b[j][i]; } } free(w) ; return idz ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------------------------------------------------- */ void shiftxbase(int n,int npt,double **xp,double *xopt,double *vlag,double **b, double **z,double *pq,double *hq,int idz) { int i,j,k,ih ; double tempq,temp,sum,xoptsq,sumz,*w=vector(2*npt) ; for(xoptsq=i=0;i<n;i++) xoptsq += xopt[i] * xopt[i] ; tempq = xoptsq/4 ; for(k=0;k<npt;k++) { for(sum=i=0;i<n;i++) sum += xp[k][i] * xopt[i]; sum -= xoptsq/2 ; w[npt+k] = sum ; for(i=0;i<n;i++) { xp[k][i] -= xopt[i]/2 ; vlag[i] = b[i][k]; w[i] = sum*xp[k][i] + tempq*xopt[i] ; for(j=0;j<=i;j++) b[j][npt+i] += vlag[i]*w[j] + w[i]*vlag[j] ; } } /* Then the revisions of BMAT that depend on ZMAT are calculated. */ for(k=0;k<npt-n-1;k++) { for(sumz=i=0;i<npt;i++) { sumz += z[k][i] ; w[i] = w[npt+i] * z[k][i] ; } for(j=0;j<n;j++) { sum = tempq * sumz * xopt[j]; for(i=0;i<npt;i++) sum += w[i] * xp[i][j]; vlag[j] = sum; if(k<idz) sum = -sum; for(i=0;i<npt;i++) b[j][i] += sum * z[k][i]; } for(i=0;i<n;i++) { if(k>=idz) temp = vlag[i] ; else temp = -vlag[i] ; for(j=0;j<=i;j++) b[j][npt+i] += temp * vlag[j] ; } } /* The following instructions complete the shift of XBASE, * including the changes to the parameters of the quadratic * model. */ for(ih=j=0;j<n;j++) { for(w[j]=k=0;k<npt;k++) { w[j] += pq[k] * xp[k][j] ; xp[k][j] -= xopt[j]/2 ; } for(i=0;i<=j;ih++,i++) { hq[ih] += w[i]*xopt[j] + xopt[i]*w[j] ; b[j][npt+i] = b[i][npt+j] ; } } free(w) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------------------------------------------------- */ //C Calculate VLAG and BETA for the current choice of STEP. The first NPT //C elements of VLAG are set to the values of the Lagrange functions at //C XPT(KOPT,.)+STEP(.). The first NPT components of W_check are held //C in W, where W_check is defined in a paper on the updating method. double calcvlagbeta(int n,int npt,double *vlag,double **z,double **b,double *w, double *xopt,double *d,double beta,double dsq,double xsq, int idz) { int i,j,k ; double sum,dx,bsum ; for(beta=k=0;k<npt-n-1;k++) { for(sum=i=0;i<npt;i++) sum += z[k][i] * w[i] ; if(k<idz) { beta += sum*sum ; sum = -sum ; } else beta -= sum*sum ; for(i=0;i<npt;i++) vlag[i] += sum * z[k][i]; } for(bsum=dx=j=0;j<n;j++) { for(sum=i=0;i<npt;i++) sum += w[i] * b[j][i]; bsum += sum * d[j] ; for(k=0;k<n;k++) sum += b[k][npt+j] * d[k]; vlag[npt+j] = sum ; bsum += sum * d[j] ; dx += d[j] * xopt[j] ; } // more numerical instability here? return dx*dx + dsq*((xsq+dx) + dx + dsq/2) + beta - bsum ; } /* -------------------------------------------------------------------------- */ int chooseknew(int n,int npt,double beta,double **z,double *vlag,double **xp, double *xopt,double detrat,double rhosq,int ktemp,int idz) { int i,j,k,knew ; double temp,qtemp,distsq,hdiag,q ; for(knew=-1,k=0;k<npt;k++) { for(hdiag=j=0;j<npt-n-1;j++) if(j<idz) hdiag -= z[j][k]*z[j][k] ; else hdiag += z[j][k]*z[j][k] ; temp = fabs(beta*hdiag+vlag[k]*vlag[k]) ; for(distsq=j=0;j<n;j++) { qtemp = xp[k][j] - xopt[j] ; distsq += qtemp*qtemp ; } if(distsq>rhosq) { if(rhosq==0) q = temp * distsq * distsq ; else { qtemp = distsq / rhosq ; q = temp * (qtemp*qtemp*qtemp) ; } } if(q>detrat&&k!=ktemp) { detrat = q ; knew = k ; } } return knew ; } /* -------------------------------------------------------------------------- */ double genpqw(int n,int npt,double *pqw,double **z,int knew,int idz) { int i,j,k ; double temp ; for(k=0;k<npt;k++) pqw[k] = 0 ; for(j=0;j<npt-n-1;j++) { if(j<idz) temp = -z[j][knew] ; else temp = z[j][knew] ; if(temp!=0) for(k=0;k<npt;k++) pqw[k] += temp * z[j][k] ; } return pqw[knew] ; } void updatehq(int n,int npt,double *hq,double pqknew,double **xpt,int knew) { int i,j,ih ; double temp ; for(ih=i=0;i<n;i++) { temp = pqknew * xpt[knew][i] ; for(j=0;j<=i;j++,ih++) hq[ih] += temp * xpt[knew][j] ; } }

Archived from pix.html

// www.masterlyinactivity.com/software/pix.html
/* The MIT License

   Copyright (c) 2016, by Colin Champion <colin.champion@masterlyinactivity.com>

   Permission is hereby granted, free of charge, to any person obtaining
   a copy of this software and associated documentation files (the
   "Software"), to deal in the Software without restriction, including
   without limitation the rights to use, copy, modify, merge, publish,
   distribute, sublicense, and/or sell copies of the Software, and to
   permit persons to whom the Software is furnished to do so, subject to
   the following conditions:

   The above copyright notice and this permission notice shall be
   included in all copies or substantial portions of the Software.

   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
   MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
   NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
   BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
   ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
   CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
   SOFTWARE.
*/
document.write( '<style>a:link{color:#66aaaa}' + 
                'a:visited{color:#cc3388}a:active{color:#404040}</style>' ) ; 
if((typeof pixlib)=="undefined") document.write
  ('<scri'+'pt src="http://www.masterlyinactivity.com/pixlib.js"></scri'+'pt>');
var thispage,full=0,nload=0,here,lind,rind,blink,pagetitle,retpage ;
var nitem=[],thumbshape=[],maxthumb,imagedir=null,infodiv=null ;
var llink,rlink,ulink,dlink,prevind,fromnotes,fullname ;

function uninfodiv()
{ if(infodiv!=null) 
  { document.getElementsByTagName("body")[0].removeChild(infodiv) ;
    infodiv = null ; 
  }
}
/* -------------------------------------------------------------------------- */

// pixresize responds to a window resize as follows:
// o. update 'full' if necessary
// o. for a table view, if the number of columns can change, change it;
// o. for an image view, if the new size permits a larger or smaller image, 
//    enlarge or reduce;
// o. or if the window is expanding or contracting but hasn't changed size 
//    enough to change the desired image, anticipatively preload a larger or
//    smaller one.

function pixresize()
{ if((full=queryfullscreen())==0&&here.full!=0&&fullname!=here.name)
    location.href = piclink(here.ind) ;

  if(here.name!=null) resize()
  else if(full!=here.full||here.ncol!=(k=getncol())) tabulate() ;
}
/* ------------------- construct a link to an image page -------------------- */

function piclink(ind)
{ if(full) return 'javascript:display("' + list[ind].name + '")' ;
  else return thispage + '?image=' + list[ind].name + (fromnotes?'&mode=n':'') ;
}
/* ----- getncol finds the number of table columns which fit the screen ----- */

function getncol()
{ var maxcol,ncol,k,ind ; // 17 pix for scrollbar, 19 pix for margin, border...
  maxcol = Math.floor((window.innerWidth-17)/(maxthumb[0]+19)) ; 
  if(maxcol<1) maxcol = 1 ;  
  for(ncol=ind=0;ind<nitem.length;ind++) 
  { k = Math.ceil(nitem[ind]/Math.ceil(nitem[ind]/maxcol)) ;
    if(k>ncol) ncol = k ; 
  }
  return ncol ;
}
/* -------------------------------------------------------------------------- */
/*page*/
/* -------------------- create links line of the table page ----------------- */

function linkp()
{ var p,a,span,astyle,i,flag=0 ; 
  astyle = 'font-family:arial;font-weight:normal;text-decoration:none;' ;
  

  p = document.createElement("p") ; 
  p.setAttribute('style',"text-align:center;font-size:100%;margin:6px;"+
                         "font-family:arial;color:silver") ; 

  if(retpage!=null)
  { a = document.createElement('a') ; 
    a.setAttribute('href',list[retpage].retpage+'.html') ; 
    a.setAttribute('style',astyle) ; 
    a.appendChild(document.createTextNode('notes')) ; 
    p.appendChild(a) ; 
    flag = 1 ;
  }

  if(links!=null) for(i=0;i<links.length;flag=1,i++)
  { if(flag) p.appendChild(document.createTextNode(' : ')) ; 
    a = document.createElement('a') ; 
    a.setAttribute('href',links[i].href) ; 
    a.setAttribute('style',astyle) ; 
    a.appendChild(document.createTextNode(links[i].name)) ; 
    p.appendChild(a) ; 
  }

  if(full==0&&querycanfullscreen())
  { if(flag) p.appendChild(document.createTextNode(' : ')) ; 
    a = document.createElement('a') ; 
    a.setAttribute('style',astyle) ; 
    a.setAttribute('href','javascript:{}') ; 
    a.setAttribute('onclick','enterfullscreen()') ; 
    a.appendChild(document.createTextNode('full screen')) ; 
    p.appendChild(a) ; 
    span = document.createElement('span') ;
    span.setAttribute('style',"color:gray") ; 
    span.appendChild(document.createTextNode(' [f key] ')) ;
    p.appendChild(span) ;
  }

  p.appendChild(document.createTextNode(' : ')) ; 
  a = document.createElement('a') ; 
  a.setAttribute('style',astyle) ; 
  a.setAttribute('href','javascript:info()') ; 
  a.appendChild(document.createTextNode('info')) ; 
  p.appendChild(a) ; 
  span = document.createElement('span') ;
  span.setAttribute('style',"color:gray") ; 
  span.appendChild(document.createTextNode(' [i key] ')) ;
  p.appendChild(span) ;

  return p ;
}
/* -------------------------------------------------------------------------- */

function display(name)
{ var titlestr,body,title,i,k,fetchitem,s,ind=findimage(list,name) ;
  uninfodiv() ; 
  body = document.getElementsByTagName("body")[0] ; 

  if(name!=here.name) 
  { title = document.getElementsByTagName("title")[0] ; 
    while(title.childNodes.length>1) title.removeChild(title.firstChild) ;
    titlestr = document.createTextNode(list[ind].title)
    if(title.childNodes.length==0) title.appendChild(titlestr) ;
    else title.replaceChild(titlestr,title.childNodes[0]) ;
  }

  here = { name:name , ind:ind , ncol:0 , full:full } ;

  // navigation links: <
  for(lind=ind-1;
      lind>=0&&(list[lind].name==undefined||list[lind].display=='none');
      lind--) ; 
  if(lind<0) llink = null ; else llink = piclink(lind) ;

  // navigation links: >
  for(rind=ind+1;
      rind<list.length &&
        (list[rind].name==undefined||list[rind].display=='none');
      rind++) ; 
  if(rind==list.length) rlink = null ; else rlink = piclink(rind) ;

  // navigation links: return
  if(fromnotes==0) 
  { if(full) blink = 'javascript:tabulate()' ; else blink = thispage ; 
    s = "table" ;
  }
  else
  { blink = retlink(list,ind) ;
    if(blink==null) alert('no return page for '+list[ind].name) ; 
    s = "notes" ;
  }

  // decide which image if any to prefetch 
  if(prevind!=null&&prevind>ind&&lind>=0) k = lind ; 
  else if((prevind==null||prevind<=ind)&&rind<list.length) k = rind ; 
  else k = null ;
  if(k==null) fetchitem = null ; else fetchitem = list[k] ;

  gendisplay(body,list[ind],sizes,llink,blink,rlink,s,fetchitem) ; 
}
/* ------------------------ navigate using the arrow keys ------------------- */

function navigate(e) 
{ var d ; 
  if(infodiv!=null) { uninfodiv() ; if(e.keyCode==73) return ; }

  if(e.keyCode==37&&lind>=0)          // left arrow key
  { e.preventDefault() ; 
    if(full==0) location.href= llink ;
    else { prevind = lind+1 ; display(list[lind].name) ; }
  }
  else if(e.keyCode==39&&rind<list.length) // right arrow key
  { e.preventDefault() ; 
    if(full==0) location.href= rlink ;
    else { prevind = rind-1 ; display(list[rind].name) ; }
  }
  else if(e.keyCode==13&&here.ncol==0)  // return
  { e.preventDefault() ; 
    if(full&&fromnotes==0) tabulate() ; else location.href = blink ; 
  } 
  else if(e.keyCode==40&&here.ncol==0) { e.preventDefault() ; reduce() ; } 
  else if(e.keyCode==38&&here.ncol==0) { e.preventDefault() ; enlarge() ; } 
  else if(e.keyCode==70&&full==0)          // 'f' (full screen)
  { e.preventDefault() ; fullname = here.name ; enterfullscreen() ; full = 1 ; }
  else if(e.keyCode==73) info() ; 
}
function info()
{ if(infodiv!=null) { uninfodiv() ; return ; } 
  if(here.ncol>0)infodiv = tabinfodiv(list) ; 
  else infodiv = pixinfodiv(list,here.ind,sizes) ; 
  infodiv.setAttribute('style','position:fixed;bottom:20;right:20;'+
   'background:white;padding:8;opacity:0.8') ;
  document.getElementsByTagName("body")[0].appendChild(infodiv) ; 
}
/* -------------------------------------------------------------------------- */
/*page*/
/* -------------------------------------------------------------------------- */

function tabulate()
{ var ind,ncol,p,body,table,tr,td,a,img,trflag,k,s,opts,thind=thumbind(sizes) ; 
  var prefetch,nimg,padflag,k,num ; 
  uninfodiv() ; 
  gendisplay() ; 
  rind = list.length ;
  lind = -1 ;

  body = document.getElementsByTagName("body")[0] ; 
  while(body.firstChild) body.removeChild(body.firstChild) ;

  ncol = getncol() ;
  here = { name:null , ind:null , ncol:ncol , full:full } ;
  // prefetch will be performed when nimg thumbs have been loaded
  for(nimg=ind=0;ind<list.length;ind++) 
    if(list[ind].name!=undefined&&list[ind].display!='none') nimg += 1 ; 

  p = document.createElement("p") ; 
  p.setAttribute('style',"text-align:center;font-size:140%;margin:2px 0 6px;"+
                 "font-family:arial;color:silver;") ; 
  p.appendChild(document.createTextNode(pagetitle)) ; 
  body.appendChild(p) ; 

  body.appendChild(linkp()) ; 

  table = document.createElement('table') ;
  table.setAttribute('cellspacing','0') ; 
  table.setAttribute('cellpadding','0') ; 
  table.setAttribute('align','center') ; 
  for(s='',ind=0;s==''&&ind<list.length;ind++) 
    if(list[ind].name==undefined) 
      s = "border-bottom:1px solid #444;padding-bottom:4px;" ; 
  table.setAttribute('style',s+'margin:0px auto') ; 

  for(prefetch=null,nload=trflag=colno=ind=0;ind<list.length;ind++)
    if(list[ind].display!='none') 
  { if(0==colno%ncol||list[ind].name==undefined) 
    { if(trflag>0) table.appendChild(tr) ; 
      tr = document.createElement('tr') ; 
      trflag = 0 ; 
    } 
    td = document.createElement('td') ;

    if(list[ind].name==undefined)
    { td.setAttribute("style","border-top:1px solid #444") ; 
      td.setAttribute('align','left') ;  
      td.setAttribute('colspan',ncol) ;  
      p = document.createElement("p") ; 
      p.setAttribute("style","font-size:110%;padding-top:6px;"+
                             "font-family:arial;color:silver") ; 
      if(list[ind].gps==undefined)
        p.appendChild(document.createTextNode(list[ind].title)) ; 
      else
      { p.appendChild(document.createTextNode(list[ind].title+' : ')) ;
        a = document.createElement('a') ; 
        a.setAttribute('href',list[ind].gps) ; 
        a.setAttribute('style','font-size:90%;text-decoration:none') ; 
        a.appendChild(document.createTextNode('[GPS track]')) ;
       p.appendChild(a) ;
      }
      td.appendChild(p) ; 
      tr.appendChild(td) ; 
      table.appendChild(tr) ; 
      tr = document.createElement('tr') ; 
      colno = trflag = 0 ; 
      continue ; 
    } 

    lind = ind ;
    llink = piclink(lind) ;
    if(rind==list.length) { rind = ind ; rlink = piclink(rind) ; }
    // extra padding at the bottom before a title row
    if(0==colno%ncol) 
    { padflag = 4 ; 
      for(num=0,k=ind;k<list.length&&num<ncol&&list[k].name!=undefined;k++) 
        if(list[k].display!='none') num += 1 ;
      if(k<list.length&&num<=ncol) 
      { padflag = 8 ; if(ncol>5) padflag += 2*(ncol-5) ; }
    } 
    td.setAttribute('align','center') ;  
    if(list[ind].display=='|'&&colno%ncol>0)
      td.setAttribute('style','border-left:1px solid #444') ;
    a = document.createElement('a') ; 
    a.setAttribute('href',piclink(ind)) ;
    a.setAttribute("class","box") ; 
    a.setAttribute("title",list[ind].title) ; 
    img = genimage(list[ind],sizes,thind,function() 
            { nload += 1 ; 
              if(nload==nimg&&prefetch!=null) genimage(list[prefetch],sizes) ; 
            } ) ;
    img.setAttribute('border',1) ; 

    // vertical bar: margin is t-r-b-l or t-lr-b
    if(colno%ncol>0&&list[ind].display!='|') 
      img.setAttribute('style',"padding:4px;margin:4px 4px "+padflag+"px 5px") ;
    else img.setAttribute('style',"padding:4px;margin:4px 4px "+padflag+"px") ; 

    a.appendChild(img) ; 
    td.appendChild(a) ; 
    tr.appendChild(td) ; 
    trflag = 1 ; 

    if(prefetch==null) prefetch = ind ;
    colno += 1 ;
  }
  table.appendChild(tr) ;
  body.appendChild(table) ; 
  body.appendChild(linkp()) ; 
}
/* -------------------------------------------------------------------------- */
/*page*/
/* -------------------------------------------------------------------------- */

function pix(h)
{ var ind,query,opts="",k,r,prev,body ;
  thispage = location.href ;
  pagetitle = document.getElementsByTagName("title")[0].textContent ; 
  body = document.getElementsByTagName("body")[0] ; 
  body.setAttribute('style','background:black;margin:0') ; 

  if((typeof links)=='undefined') links = null ;
  if(h!=undefined&&h!=null&&links==null)
    links = [ { name: 'home' , href: h } ] ;

  prevind = null ;
  here = { name:null , ind:null , ncol:0 , full:0 } ;

  if((ind=thispage.lastIndexOf('/'))>=0) thispage = thispage.substring(ind+1) ; 

  // set up the array nitem containing the number of items in each block
  for(nitem=[],ind=-1;ind<list.length;ind=k) 
  { for(k=ind+1;k<list.length&&list[k].name!=undefined;k++) ; 
    if(k>ind+1) nitem.push(k-(ind+1)) ; 
  }

  maxthumb = setthumbshape(list,sizes,thumbshape,imagedir,hithumb) ; 

  if((ind=thispage.indexOf('?'))>=0)
  { query = thispage.substring(ind+1) ;      // 'image=garden&mode=n'
    thispage = thispage.substring(0,ind) ; 
    if(query.substring(0,6)=='image=')
    { query = query.substring(6) ; 
      if((ind=query.indexOf('&mode='))>=0)
      { opts = query.substring(ind+6) ; query = query.substring(0,ind) ; }
    }
    else if(query.substring(0,5)=='mode=')
    { opts = query.substring(5) ; query = null ; }
    else if(query=='') query = null ; 
    else
    { if((ind=query.indexOf('+'))>=0) 
      { opts = query.substring(ind+1) ; query = query.substring(0,ind) ; }
      thispage += '?image=' + query ;
      location.href = thispage + (opts==''?'':('&mode='+opts)) ; 
    }
  } 
  else query = null ; 

  if(query==null) ind = -1 ; 
  else for(ind=0;ind<list.length&&(list[ind].name!=query);ind++) ;

  for(k=0;
      k<list.length&&(list[k].name==undefined||list[k].retpage==undefined);
      k++) ;
  if(k<list.length) retpage = k ; else retpage = null ;

  if(ind>=0&&ind<list.length) // return to table if there is no retpage
  { // all the following code is finding whether we're stepping backwards
    if((k=document.referrer.lastIndexOf('/'))>=0)
    { prev = document.referrer.substring(k+1) ; k = prev.indexOf('?') ; }
    if(k>=0&&prev.substring(k+1,5)!='mode=')
    { prev = prev.substring(k+1) ; 
      k = prev.indexOf('&') ;
      if(k>=0) prev = prev.substring(0,k) ;
      for(k=0;k<list.length&&list[k].name!=prev;k++) ;
      if(k<list.length) prevind = k ;
    }
    else if(thispage==document.referrer) // came from table
    { for(k=ind+1;k<list.length&&list[ind].name==undefined;k++) ;
      if(k==list.length) prevind = list.length ;
    }
  }

  window.onresize = pixresize ;
  document.onkeydown = navigate ; 
  document.addEventListener('touchstart',startswipe,false) ;        
  document.addEventListener('touchmove',midswipe,false) ;        
  document.addEventListener('touchend',endswipe,false) ;
  if(ind<0||ind>=list.length) { fromnotes = 0 ; tabulate() ; }
  else { fromnotes = (opts.charAt(0)=='n'&&retpage!=null) ; display(query) ; }
}
/* -------------------------------------------------------------------------- */
/*page*/
/* ---------------------------------- swipes -------------------------------- */

function simulate(btn)
{ navigate({keyCode:btn,preventDefault:function(){}}) ; }

var xloc=null,yloc=null,xstart=null,ystart=null,fingersep=null,startsep=null ; 
var swipetime=null ;

function startswipe(e) 
{ var x0=e.touches[0].clientX,y0=e.touches[0].clientY,x1,y1 ;
  if(e.touches.length==1) 
  { xstart = x0 ; ystart = y0 ; swipetime = new Date().getTime() ; } 
  else if(e.touches.length==2&&here.ncol==0)
  { e.preventDefault() ;        // don't let iOS take control of pinches
    x1 = e.touches[1].clientX ;
    y1 = e.touches[1].clientY ;
    startsep = Math.sqrt((x1-x0)*(x1-x0)+(y1-y0)*(y1-y0)) ;
  }
}
function midswipe(e) 
{ var x0=e.touches[0].clientX,y0=e.touches[0].clientY,x1,y1 ;
  if(e.touches.length==1) 
  { xloc = x0 ; yloc = y0 ; fingersep = startsep = null ; } 
  else if(e.touches.length==2&&here.ncol==0)
  { e.preventDefault() ; 
    x1 = e.touches[1].clientX ;
    y1 = e.touches[1].clientY ;
    fingersep = Math.sqrt((x1-x0)*(x1-x0)+(y1-y0)*(y1-y0)) ;
    xloc = yloc = xstart = ystart = swipetime = null ; 
  }
}
function endswipe(e) 
{ var v ;
  if(fingersep!=null&&startsep!=null&&here.ncol==0)
  { e.preventDefault() ; 
    if(fingersep>startsep+50) simulate(38) ; 
    else if(startsep>fingersep+50) simulate(40) ; 
  }
  else if(xloc!=null&&yloc!=null&&xstart!=null&&ystart!=null&&swipetime!=null)
  { swipetime = new Date().getTime() - swipetime ; 
    v = Math.sqrt((xloc-xstart)*(xloc-xstart)+(yloc-ystart)*(yloc-ystart)) ;
    if(v>0.65*swipetime)
    { e.preventDefault() ; 
      if(Math.abs(xloc-xstart)>100&&Math.abs(yloc-ystart)<100)
      { if(xloc>xstart) simulate(37) ; else simulate(39) ; }
      else if(yloc>ystart+100&&Math.abs(xloc-xstart)<100) simulate(13) ; 
    }
  }
  xloc = yloc = xstart = ystart = fingersep = startsep = swipetime = null ; 
}
/* -------------------------------------------------------------------------- */

function tabinfodiv(list)
{ var d=document.createElement('div'),i,npix,nsect,nhidden,s,noraw='' ; 
  for(npix=nsect=nhidden=i=0;i<list.length;i++)
  { if(list[i].name==undefined) nsect += 1 ; 
    else 
    { npix += 1 ; 
      if(list[i].display=='none') nhidden += 1 ; 
      if(i==0) nsect += 1 ; 
    }
    if( list[i].rawshape!=undefined && list[i].rawshape.length==2
     && ( list[i].rawshape[0]==0||list[i].rawshape[1]==0) ) 
      noraw += ' ' + list[i].name ;
  }

  if(nsect>1) 
  { d.appendChild(document.createTextNode(nsect+' sections')) ;
    d.appendChild(document.createElement('br')) ;
  }
  s = npix + ' photos' ;
  if(nhidden) s += ' (' + (npix-nhidden) + 'visible, ' + nhidden + ' hidden)' ;
  d.appendChild(document.createTextNode(s)) ;
  d.appendChild(document.createElement('br')) ;

  if(noraw!='')
  { d.appendChild(document.createTextNode('No raw shape for'+noraw)) ;
    d.appendChild(document.createElement('br')) ;
  }
  return d ; 
}

Archived from pix.html

// www.masterlyinactivity.com/software/pix.html
/* The MIT License

   Copyright (c) 2016, by Colin Champion <colin.champion@masterlyinactivity.com>

   Permission is hereby granted, free of charge, to any person obtaining
   a copy of this software and associated documentation files (the
   "Software"), to deal in the Software without restriction, including
   without limitation the rights to use, copy, modify, merge, publish,
   distribute, sublicense, and/or sell copies of the Software, and to
   permit persons to whom the Software is furnished to do so, subject to
   the following conditions:

   The above copyright notice and this permission notice shall be
   included in all copies or substantial portions of the Software.

   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
   MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
   NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
   BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
   ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
   CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
   SOFTWARE.
*/
document.write( '<style>a:link{color:#66aaaa}' + 
                'a:visited{color:#cc3388}a:active{color:#404040}</style>' ) ; 
if((typeof pixlib)=="undefined") document.write
  ('<scri'+'pt src="http://www.masterlyinactivity.com/pixlib.js"></scri'+'pt>');
var thispage,full=0,nload=0,here,lind,rind,blink,pagetitle,retpage ;
var nitem=[],thumbshape=[],maxthumb,imagedir=null,infodiv=null ;
var llink,rlink,ulink,dlink,prevind,fromnotes,fullname ;

function uninfodiv()
{ if(infodiv!=null) 
  { document.getElementsByTagName("body")[0].removeChild(infodiv) ;
    infodiv = null ; 
  }
}
/* -------------------------------------------------------------------------- */

// pixresize responds to a window resize as follows:
// o. update 'full' if necessary
// o. for a table view, if the number of columns can change, change it;
// o. for an image view, if the new size permits a larger or smaller image, 
//    enlarge or reduce;
// o. or if the window is expanding or contracting but hasn't changed size 
//    enough to change the desired image, anticipatively preload a larger or
//    smaller one.

function pixresize()
{ if((full=queryfullscreen())==0&&here.full!=0&&fullname!=here.name)
    location.href = piclink(here.ind) ;

  if(here.name!=null) resize()
  else if(full!=here.full||here.ncol!=(k=getncol())) tabulate() ;
}
/* ------------------- construct a link to an image page -------------------- */

function piclink(ind)
{ if(full) return 'javascript:display("' + list[ind].name + '")' ;
  else return thispage + '?image=' + list[ind].name + (fromnotes?'&mode=n':'') ;
}
/* ----- getncol finds the number of table columns which fit the screen ----- */

function getncol()
{ var maxcol,ncol,k,ind ; // 17 pix for scrollbar, 19 pix for margin, border...
  maxcol = Math.floor((window.innerWidth-17)/(maxthumb[0]+19)) ; 
  if(maxcol<1) maxcol = 1 ;  
  for(ncol=ind=0;ind<nitem.length;ind++) 
  { k = Math.ceil(nitem[ind]/Math.ceil(nitem[ind]/maxcol)) ;
    if(k>ncol) ncol = k ; 
  }
  return ncol ;
}
/* -------------------------------------------------------------------------- */
/*page*/
/* -------------------- create links line of the table page ----------------- */

function linkp()
{ var p,a,span,astyle,i,flag=0 ; 
  astyle = 'font-family:arial;font-weight:normal;text-decoration:none;' ;
  

  p = document.createElement("p") ; 
  p.setAttribute('style',"text-align:center;font-size:100%;margin:6px;"+
                         "font-family:arial;color:silver") ; 

  if(retpage!=null)
  { a = document.createElement('a') ; 
    a.setAttribute('href',list[retpage].retpage+'.html') ; 
    a.setAttribute('style',astyle) ; 
    a.appendChild(document.createTextNode('notes')) ; 
    p.appendChild(a) ; 
    flag = 1 ;
  }

  if(links!=null) for(i=0;i<links.length;flag=1,i++)
  { if(flag) p.appendChild(document.createTextNode(' : ')) ; 
    a = document.createElement('a') ; 
    a.setAttribute('href',links[i].href) ; 
    a.setAttribute('style',astyle) ; 
    a.appendChild(document.createTextNode(links[i].name)) ; 
    p.appendChild(a) ; 
  }

  if(full==0&&querycanfullscreen())
  { if(flag) p.appendChild(document.createTextNode(' : ')) ; 
    a = document.createElement('a') ; 
    a.setAttribute('style',astyle) ; 
    a.setAttribute('href','javascript:{}') ; 
    a.setAttribute('onclick','enterfullscreen()') ; 
    a.appendChild(document.createTextNode('full screen')) ; 
    p.appendChild(a) ; 
    span = document.createElement('span') ;
    span.setAttribute('style',"color:gray") ; 
    span.appendChild(document.createTextNode(' [f key] ')) ;
    p.appendChild(span) ;
  }

  p.appendChild(document.createTextNode(' : ')) ; 
  a = document.createElement('a') ; 
  a.setAttribute('style',astyle) ; 
  a.setAttribute('href','javascript:info()') ; 
  a.appendChild(document.createTextNode('info')) ; 
  p.appendChild(a) ; 
  span = document.createElement('span') ;
  span.setAttribute('style',"color:gray") ; 
  span.appendChild(document.createTextNode(' [i key] ')) ;
  p.appendChild(span) ;

  return p ;
}
/* -------------------------------------------------------------------------- */

function display(name)
{ var titlestr,body,title,i,k,fetchitem,s,ind=findimage(list,name) ;
  uninfodiv() ; 
  body = document.getElementsByTagName("body")[0] ; 

  if(name!=here.name) 
  { title = document.getElementsByTagName("title")[0] ; 
    while(title.childNodes.length>1) title.removeChild(title.firstChild) ;
    titlestr = document.createTextNode(list[ind].title)
    if(title.childNodes.length==0) title.appendChild(titlestr) ;
    else title.replaceChild(titlestr,title.childNodes[0]) ;
  }

  here = { name:name , ind:ind , ncol:0 , full:full } ;

  // navigation links: <
  for(lind=ind-1;
      lind>=0&&(list[lind].name==undefined||list[lind].display=='none');
      lind--) ; 
  if(lind<0) llink = null ; else llink = piclink(lind) ;

  // navigation links: >
  for(rind=ind+1;
      rind<list.length &&
        (list[rind].name==undefined||list[rind].display=='none');
      rind++) ; 
  if(rind==list.length) rlink = null ; else rlink = piclink(rind) ;

  // navigation links: return
  if(fromnotes==0) 
  { if(full) blink = 'javascript:tabulate()' ; else blink = thispage ; 
    s = "table" ;
  }
  else
  { blink = retlink(list,ind) ;
    if(blink==null) alert('no return page for '+list[ind].name) ; 
    s = "notes" ;
  }

  // decide which image if any to prefetch 
  if(prevind!=null&&prevind>ind&&lind>=0) k = lind ; 
  else if((prevind==null||prevind<=ind)&&rind<list.length) k = rind ; 
  else k = null ;
  if(k==null) fetchitem = null ; else fetchitem = list[k] ;

  gendisplay(body,list[ind],sizes,llink,blink,rlink,s,fetchitem) ; 
}
/* ------------------------ navigate using the arrow keys ------------------- */

function navigate(e) 
{ var d ; 
  if(infodiv!=null) { uninfodiv() ; if(e.keyCode==73) return ; }

  if(e.keyCode==37&&lind>=0)          // left arrow key
  { e.preventDefault() ; 
    if(full==0) location.href= llink ;
    else { prevind = lind+1 ; display(list[lind].name) ; }
  }
  else if(e.keyCode==39&&rind<list.length) // right arrow key
  { e.preventDefault() ; 
    if(full==0) location.href= rlink ;
    else { prevind = rind-1 ; display(list[rind].name) ; }
  }
  else if(e.keyCode==13&&here.ncol==0)  // return
  { e.preventDefault() ; 
    if(full&&fromnotes==0) tabulate() ; else location.href = blink ; 
  } 
  else if(e.keyCode==40&&here.ncol==0) { e.preventDefault() ; reduce() ; } 
  else if(e.keyCode==38&&here.ncol==0) { e.preventDefault() ; enlarge() ; } 
  else if(e.keyCode==70&&full==0)          // 'f' (full screen)
  { e.preventDefault() ; fullname = here.name ; enterfullscreen() ; full = 1 ; }
  else if(e.keyCode==73) info() ; 
}
function info()
{ if(infodiv!=null) { uninfodiv() ; return ; } 
  if(here.ncol>0)infodiv = tabinfodiv(list) ; 
  else infodiv = pixinfodiv(list,here.ind,sizes) ; 
  infodiv.setAttribute('style','position:fixed;bottom:20;right:20;'+
   'background:white;padding:8;opacity:0.8') ;
  document.getElementsByTagName("body")[0].appendChild(infodiv) ; 
}
/* -------------------------------------------------------------------------- */
/*page*/
/* -------------------------------------------------------------------------- */

function tabulate()
{ var ind,ncol,p,body,table,tr,td,a,img,trflag,k,s,opts,thind=thumbind(sizes) ; 
  var prefetch,nimg,padflag,k,num ; 
  uninfodiv() ; 
  gendisplay() ; 
  rind = list.length ;
  lind = -1 ;

  body = document.getElementsByTagName("body")[0] ; 
  while(body.firstChild) body.removeChild(body.firstChild) ;

  ncol = getncol() ;
  here = { name:null , ind:null , ncol:ncol , full:full } ;
  // prefetch will be performed when nimg thumbs have been loaded
  for(nimg=ind=0;ind<list.length;ind++) 
    if(list[ind].name!=undefined&&list[ind].display!='none') nimg += 1 ; 

  p = document.createElement("p") ; 
  p.setAttribute('style',"text-align:center;font-size:140%;margin:2px 0 6px;"+
                 "font-family:arial;color:silver;") ; 
  p.appendChild(document.createTextNode(pagetitle)) ; 
  body.appendChild(p) ; 

  body.appendChild(linkp()) ; 

  table = document.createElement('table') ;
  table.setAttribute('cellspacing','0') ; 
  table.setAttribute('cellpadding','0') ; 
  table.setAttribute('align','center') ; 
  for(s='',ind=0;s==''&&ind<list.length;ind++) 
    if(list[ind].name==undefined) 
      s = "border-bottom:1px solid #444;padding-bottom:4px;" ; 
  table.setAttribute('style',s+'margin:0px auto') ; 

  for(prefetch=null,nload=trflag=colno=ind=0;ind<list.length;ind++)
    if(list[ind].display!='none') 
  { if(0==colno%ncol||list[ind].name==undefined) 
    { if(trflag>0) table.appendChild(tr) ; 
      tr = document.createElement('tr') ; 
      trflag = 0 ; 
    } 
    td = document.createElement('td') ;

    if(list[ind].name==undefined)
    { td.setAttribute("style","border-top:1px solid #444") ; 
      td.setAttribute('align','left') ;  
      td.setAttribute('colspan',ncol) ;  
      p = document.createElement("p") ; 
      p.setAttribute("style","font-size:110%;padding-top:6px;"+
                             "font-family:arial;color:silver") ; 
      if(list[ind].gps==undefined)
        p.appendChild(document.createTextNode(list[ind].title)) ; 
      else
      { p.appendChild(document.createTextNode(list[ind].title+' : ')) ;
        a = document.createElement('a') ; 
        a.setAttribute('href',list[ind].gps) ; 
        a.setAttribute('style','font-size:90%;text-decoration:none') ; 
        a.appendChild(document.createTextNode('[GPS track]')) ;
       p.appendChild(a) ;
      }
      td.appendChild(p) ; 
      tr.appendChild(td) ; 
      table.appendChild(tr) ; 
      tr = document.createElement('tr') ; 
      colno = trflag = 0 ; 
      continue ; 
    } 

    lind = ind ;
    llink = piclink(lind) ;
    if(rind==list.length) { rind = ind ; rlink = piclink(rind) ; }
    // extra padding at the bottom before a title row
    if(0==colno%ncol) 
    { padflag = 4 ; 
      for(num=0,k=ind;k<list.length&&num<ncol&&list[k].name!=undefined;k++) 
        if(list[k].display!='none') num += 1 ;
      if(k<list.length&&num<=ncol) 
      { padflag = 8 ; if(ncol>5) padflag += 2*(ncol-5) ; }
    } 
    td.setAttribute('align','center') ;  
    if(list[ind].display=='|'&&colno%ncol>0)
      td.setAttribute('style','border-left:1px solid #444') ;
    a = document.createElement('a') ; 
    a.setAttribute('href',piclink(ind)) ;
    a.setAttribute("class","box") ; 
    a.setAttribute("title",list[ind].title) ; 
    img = genimage(list[ind],sizes,thind,function() 
            { nload += 1 ; 
              if(nload==nimg&&prefetch!=null) genimage(list[prefetch],sizes) ; 
            } ) ;
    img.setAttribute('border',1) ; 

    // vertical bar: margin is t-r-b-l or t-lr-b
    if(colno%ncol>0&&list[ind].display!='|') 
      img.setAttribute('style',"padding:4px;margin:4px 4px "+padflag+"px 5px") ;
    else img.setAttribute('style',"padding:4px;margin:4px 4px "+padflag+"px") ; 

    a.appendChild(img) ; 
    td.appendChild(a) ; 
    tr.appendChild(td) ; 
    trflag = 1 ; 

    if(prefetch==null) prefetch = ind ;
    colno += 1 ;
  }
  table.appendChild(tr) ;
  body.appendChild(table) ; 
  body.appendChild(linkp()) ; 
}
/* -------------------------------------------------------------------------- */
/*page*/
/* -------------------------------------------------------------------------- */

function pix(h)
{ var ind,query,opts="",k,r,prev,body ;
  thispage = location.href ;
  pagetitle = document.getElementsByTagName("title")[0].textContent ; 
  body = document.getElementsByTagName("body")[0] ; 
  body.setAttribute('style','background:black;margin:0') ; 

  if((typeof links)=='undefined') links = null ;
  if(h!=undefined&&h!=null&&links==null)
    links = [ { name: 'home' , href: h } ] ;

  prevind = null ;
  here = { name:null , ind:null , ncol:0 , full:0 } ;

  if((ind=thispage.lastIndexOf('/'))>=0) thispage = thispage.substring(ind+1) ; 

  // set up the array nitem containing the number of items in each block
  for(nitem=[],ind=-1;ind<list.length;ind=k) 
  { for(k=ind+1;k<list.length&&list[k].name!=undefined;k++) ; 
    if(k>ind+1) nitem.push(k-(ind+1)) ; 
  }

  maxthumb = setthumbshape(list,sizes,thumbshape,imagedir,hithumb) ; 

  if((ind=thispage.indexOf('?'))>=0)
  { query = thispage.substring(ind+1) ;      // 'image=garden&mode=n'
    thispage = thispage.substring(0,ind) ; 
    if(query.substring(0,6)=='image=')
    { query = query.substring(6) ; 
      if((ind=query.indexOf('&mode='))>=0)
      { opts = query.substring(ind+6) ; query = query.substring(0,ind) ; }
    }
    else if(query.substring(0,5)=='mode=')
    { opts = query.substring(5) ; query = null ; }
    else if(query=='') query = null ; 
    else
    { if((ind=query.indexOf('+'))>=0) 
      { opts = query.substring(ind+1) ; query = query.substring(0,ind) ; }
      thispage += '?image=' + query ;
      location.href = thispage + (opts==''?'':('&mode='+opts)) ; 
    }
  } 
  else query = null ; 

  if(query==null) ind = -1 ; 
  else for(ind=0;ind<list.length&&(list[ind].name!=query);ind++) ;

  for(k=0;
      k<list.length&&(list[k].name==undefined||list[k].retpage==undefined);
      k++) ;
  if(k<list.length) retpage = k ; else retpage = null ;

  if(ind>=0&&ind<list.length) // return to table if there is no retpage
  { // all the following code is finding whether we're stepping backwards
    if((k=document.referrer.lastIndexOf('/'))>=0)
    { prev = document.referrer.substring(k+1) ; k = prev.indexOf('?') ; }
    if(k>=0&&prev.substring(k+1,5)!='mode=')
    { prev = prev.substring(k+1) ; 
      k = prev.indexOf('&') ;
      if(k>=0) prev = prev.substring(0,k) ;
      for(k=0;k<list.length&&list[k].name!=prev;k++) ;
      if(k<list.length) prevind = k ;
    }
    else if(thispage==document.referrer) // came from table
    { for(k=ind+1;k<list.length&&list[ind].name==undefined;k++) ;
      if(k==list.length) prevind = list.length ;
    }
  }

  window.onresize = pixresize ;
  document.onkeydown = navigate ; 
  document.addEventListener('touchstart',startswipe,false) ;        
  document.addEventListener('touchmove',midswipe,false) ;        
  document.addEventListener('touchend',endswipe,false) ;
  if(ind<0||ind>=list.length) { fromnotes = 0 ; tabulate() ; }
  else { fromnotes = (opts.charAt(0)=='n'&&retpage!=null) ; display(query) ; }
}
/* -------------------------------------------------------------------------- */
/*page*/
/* ---------------------------------- swipes -------------------------------- */

function simulate(btn)
{ navigate({keyCode:btn,preventDefault:function(){}}) ; }

var xloc=null,yloc=null,xstart=null,ystart=null,fingersep=null,startsep=null ; 
var swipetime=null ;

function startswipe(e) 
{ var x0=e.touches[0].clientX,y0=e.touches[0].clientY,x1,y1 ;
  if(e.touches.length==1) 
  { xstart = x0 ; ystart = y0 ; swipetime = new Date().getTime() ; } 
  else if(e.touches.length==2&&here.ncol==0)
  { e.preventDefault() ;        // don't let iOS take control of pinches
    x1 = e.touches[1].clientX ;
    y1 = e.touches[1].clientY ;
    startsep = Math.sqrt((x1-x0)*(x1-x0)+(y1-y0)*(y1-y0)) ;
  }
}
function midswipe(e) 
{ var x0=e.touches[0].clientX,y0=e.touches[0].clientY,x1,y1 ;
  if(e.touches.length==1) 
  { xloc = x0 ; yloc = y0 ; fingersep = startsep = null ; } 
  else if(e.touches.length==2&&here.ncol==0)
  { e.preventDefault() ; 
    x1 = e.touches[1].clientX ;
    y1 = e.touches[1].clientY ;
    fingersep = Math.sqrt((x1-x0)*(x1-x0)+(y1-y0)*(y1-y0)) ;
    xloc = yloc = xstart = ystart = swipetime = null ; 
  }
}
function endswipe(e) 
{ var v ;
  if(fingersep!=null&&startsep!=null&&here.ncol==0)
  { e.preventDefault() ; 
    if(fingersep>startsep+50) simulate(38) ; 
    else if(startsep>fingersep+50) simulate(40) ; 
  }
  else if(xloc!=null&&yloc!=null&&xstart!=null&&ystart!=null&&swipetime!=null)
  { swipetime = new Date().getTime() - swipetime ; 
    v = Math.sqrt((xloc-xstart)*(xloc-xstart)+(yloc-ystart)*(yloc-ystart)) ;
    if(v>0.65*swipetime)
    { e.preventDefault() ; 
      if(Math.abs(xloc-xstart)>100&&Math.abs(yloc-ystart)<100)
      { if(xloc>xstart) simulate(37) ; else simulate(39) ; }
      else if(yloc>ystart+100&&Math.abs(xloc-xstart)<100) simulate(13) ; 
    }
  }
  xloc = yloc = xstart = ystart = fingersep = startsep = swipetime = null ; 
}
/* -------------------------------------------------------------------------- */

function tabinfodiv(list)
{ var d=document.createElement('div'),i,npix,nsect,nhidden,s,noraw='' ; 
  for(npix=nsect=nhidden=i=0;i<list.length;i++)
  { if(list[i].name==undefined) nsect += 1 ; 
    else 
    { npix += 1 ; 
      if(list[i].display=='none') nhidden += 1 ; 
      if(i==0) nsect += 1 ; 
    }
    if( list[i].rawshape!=undefined && list[i].rawshape.length==2
     && ( list[i].rawshape[0]==0||list[i].rawshape[1]==0) ) 
      noraw += ' ' + list[i].name ;
  }

  if(nsect>1) 
  { d.appendChild(document.createTextNode(nsect+' sections')) ;
    d.appendChild(document.createElement('br')) ;
  }
  s = npix + ' photos' ;
  if(nhidden) s += '(' + nhidden + ' hidden)' ;
  d.appendChild(document.createTextNode(s)) ;
  d.appendChild(document.createElement('br')) ;

  if(noraw!='')
  { d.appendChild(document.createTextNode('No raw shape for'+noraw)) ;
    d.appendChild(document.createElement('br')) ;
  }
  return d ; 
}

Archived from pix.html

// www.masterlyinactivity.com/software/pix.html
/* The MIT License

   Copyright (c) 2016, by Colin Champion <colin.champion@masterlyinactivity.com>

   Permission is hereby granted, free of charge, to any person obtaining
   a copy of this software and associated documentation files (the
   "Software"), to deal in the Software without restriction, including
   without limitation the rights to use, copy, modify, merge, publish,
   distribute, sublicense, and/or sell copies of the Software, and to
   permit persons to whom the Software is furnished to do so, subject to
   the following conditions:

   The above copyright notice and this permission notice shall be
   included in all copies or substantial portions of the Software.

   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
   MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
   NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
   BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
   ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
   CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
   SOFTWARE.
*/
document.write( '<style>a:link{color:#66aaaa}' + 
                'a:visited{color:#cc3388}a:active{color:#404040}</style>' ) ; 
if((typeof pixlib)=="undefined") document.write
  ('<scri'+'pt src="http://www.masterlyinactivity.com/pixlib.js"></scri'+'pt>');
var thispage,full=0,nload=0,here,lind,rind,blink,pagetitle,retpage ;
var nitem=[],thumbshape=[],maxthumb,imagedir=null,infodiv=null ;
var llink,rlink,ulink,dlink,prevind,fromnotes,fullname ;

function uninfodiv()
{ if(infodiv!=null) 
  { document.getElementsByTagName("body")[0].removeChild(infodiv) ;
    infodiv = null ; 
  }
}
/* -------------------------------------------------------------------------- */

// pixresize responds to a window resize as follows:
// o. update 'full' if necessary
// o. for a table view, if the number of columns can change, change it;
// o. for an image view, if the new size permits a larger or smaller image, 
//    enlarge or reduce;
// o. or if the window is expanding or contracting but hasn't changed size 
//    enough to change the desired image, anticipatively preload a larger or
//    smaller one.

function pixresize()
{ if((full=queryfullscreen())==0&&here.full!=0&&fullname!=here.name)
    location.href = piclink(here.ind) ;

  if(here.name!=null) resize()
  else if(full!=here.full||here.ncol!=(k=getncol())) tabulate() ;
}
/* ------------------- construct a link to an image page -------------------- */

function piclink(ind)
{ if(full) return 'javascript:display("' + list[ind].name + '")' ;
  else return thispage + '?image=' + list[ind].name + (fromnotes?'&mode=n':'') ;
}
/* ----- getncol finds the number of table columns which fit the screen ----- */

function getncol()
{ var maxcol,ncol,k,ind ; // 17 pix for scrollbar, 19 pix for margin, border...
  maxcol = Math.floor((window.innerWidth-17)/(maxthumb[0]+19)) ; 
  if(maxcol<1) maxcol = 1 ;  
  for(ncol=ind=0;ind<nitem.length;ind++) 
  { k = Math.ceil(nitem[ind]/Math.ceil(nitem[ind]/maxcol)) ;
    if(k>ncol) ncol = k ; 
  }
  return ncol ;
}
/* -------------------------------------------------------------------------- */
/*page*/
/* -------------------- create links line of the table page ----------------- */

function linkp()
{ var p,a,span,astyle,i,flag=0 ; 
  astyle = 'font-family:arial;font-weight:normal;text-decoration:none;' ;
  

  p = document.createElement("p") ; 
  p.setAttribute('style',"text-align:center;font-size:100%;margin:6px;"+
                         "font-family:arial;color:silver") ; 

  if(retpage!=null)
  { a = document.createElement('a') ; 
    a.setAttribute('href',list[retpage].retpage+'.html') ; 
    a.setAttribute('style',astyle) ; 
    a.appendChild(document.createTextNode('notes')) ; 
    p.appendChild(a) ; 
    flag = 1 ;
  }

  if(links!=null) for(i=0;i<links.length;flag=1,i++)
  { if(flag) p.appendChild(document.createTextNode(' : ')) ; 
    a = document.createElement('a') ; 
    a.setAttribute('href',links[i].href) ; 
    a.setAttribute('style',astyle) ; 
    a.appendChild(document.createTextNode(links[i].name)) ; 
    p.appendChild(a) ; 
  }

  if(full==0&&querycanfullscreen())
  { if(flag) p.appendChild(document.createTextNode(' : ')) ; 
    a = document.createElement('a') ; 
    a.setAttribute('style',astyle) ; 
    a.setAttribute('href','javascript:{}') ; 
    a.setAttribute('onclick','enterfullscreen()') ; 
    a.appendChild(document.createTextNode('full screen')) ; 
    p.appendChild(a) ; 
    span = document.createElement('span') ;
    span.setAttribute('style',"color:gray") ; 
    span.appendChild(document.createTextNode(' [f key] ')) ;
    p.appendChild(span) ;
  }
  return p ;
}
/* -------------------------------------------------------------------------- */

function display(name)
{ var titlestr,body,title,i,k,fetchitem,s,ind=findimage(list,name) ;
  uninfodiv() ; 
  body = document.getElementsByTagName("body")[0] ; 

  if(name!=here.name) 
  { title = document.getElementsByTagName("title")[0] ; 
    while(title.childNodes.length>1) title.removeChild(title.firstChild) ;
    titlestr = document.createTextNode(list[ind].title)
    if(title.childNodes.length==0) title.appendChild(titlestr) ;
    else title.replaceChild(titlestr,title.childNodes[0]) ;
  }

  here = { name:name , ind:ind , ncol:0 , full:full } ;

  // navigation links: <
  for(lind=ind-1;
      lind>=0&&(list[lind].name==undefined||list[lind].display=='none');
      lind--) ; 
  if(lind<0) llink = null ; else llink = piclink(lind) ;

  // navigation links: >
  for(rind=ind+1;
      rind<list.length &&
        (list[rind].name==undefined||list[rind].display=='none');
      rind++) ; 
  if(rind==list.length) rlink = null ; else rlink = piclink(rind) ;

  // navigation links: return
  if(fromnotes==0) 
  { if(full) blink = 'javascript:tabulate()' ; else blink = thispage ; 
    s = "table" ;
  }
  else
  { blink = retlink(list,ind) ;
    if(blink==null) alert('no return page for '+list[ind].name) ; 
    s = "notes" ;
  }

  // decide which image if any to prefetch 
  if(prevind!=null&&prevind>ind&&lind>=0) k = lind ; 
  else if((prevind==null||prevind<=ind)&&rind<list.length) k = rind ; 
  else k = null ;
  if(k==null) fetchitem = null ; else fetchitem = list[k] ;

  gendisplay(body,list[ind],sizes,llink,blink,rlink,s,fetchitem) ; 
}
/* ------------------------ navigate using the arrow keys ------------------- */

function navigate(e) 
{ var d ; 
  if(infodiv!=null) { uninfodiv() ; if(e.keyCode==73) return ; }

  if(e.keyCode==37&&lind>=0)          // left arrow key
  { e.preventDefault() ; 
    if(full==0) location.href= llink ;
    else { prevind = lind+1 ; display(list[lind].name) ; }
  }
  else if(e.keyCode==39&&rind<list.length) // right arrow key
  { e.preventDefault() ; 
    if(full==0) location.href= rlink ;
    else { prevind = rind-1 ; display(list[rind].name) ; }
  }
  else if(e.keyCode==13&&here.ncol==0)  // return
  { e.preventDefault() ; 
    if(full&&fromnotes==0) tabulate() ; else location.href = blink ; 
  } 
  else if(e.keyCode==40&&here.ncol==0) { e.preventDefault() ; reduce() ; } 
  else if(e.keyCode==38&&here.ncol==0) { e.preventDefault() ; enlarge() ; } 
  else if(e.keyCode==70&&full==0)          // 'f' (full screen)
  { e.preventDefault() ; fullname = here.name ; enterfullscreen() ; full = 1 ; }
  else if(e.keyCode==73)
  { if(here.ncol>0)infodiv = tabinfodiv(list) ; 
    else infodiv = pixinfodiv(list,here.ind,sizes) ; 
    infodiv.setAttribute('style','position:fixed;bottom:20;right:20;'+
     'background:white;padding:8;opacity:0.8') ;
    document.getElementsByTagName("body")[0].appendChild(infodiv) ; 
  }
}
/* -------------------------------------------------------------------------- */
/*page*/
/* -------------------------------------------------------------------------- */

function tabulate()
{ var ind,ncol,p,body,table,tr,td,a,img,trflag,k,s,opts,thind=thumbind(sizes) ; 
  var prefetch,nimg,padflag,k,num ; 
  uninfodiv() ; 
  gendisplay() ; 
  rind = list.length ;
  lind = -1 ;

  body = document.getElementsByTagName("body")[0] ; 
  while(body.firstChild) body.removeChild(body.firstChild) ;

  ncol = getncol() ;
  here = { name:null , ind:null , ncol:ncol , full:full } ;
  // prefetch will be performed when nimg thumbs have been loaded
  for(nimg=ind=0;ind<list.length;ind++) 
    if(list[ind].name!=undefined&&list[ind].display!='none') nimg += 1 ; 

  p = document.createElement("p") ; 
  p.setAttribute('style',"text-align:center;font-size:140%;margin:2px 0 6px;"+
                 "font-family:arial;color:silver;") ; 
  p.appendChild(document.createTextNode(pagetitle)) ; 
  body.appendChild(p) ; 

  body.appendChild(linkp()) ; 

  table = document.createElement('table') ;
  table.setAttribute('cellspacing','0') ; 
  table.setAttribute('cellpadding','0') ; 
  table.setAttribute('align','center') ; 
  for(s='',ind=0;s==''&&ind<list.length;ind++) 
    if(list[ind].name==undefined) 
      s = "border-bottom:1px solid #444;padding-bottom:4px;" ; 
  table.setAttribute('style',s+'margin:0px auto') ; 

  for(prefetch=null,nload=trflag=colno=ind=0;ind<list.length;ind++)
    if(list[ind].display!='none') 
  { if(0==colno%ncol||list[ind].name==undefined) 
    { if(trflag>0) table.appendChild(tr) ; 
      tr = document.createElement('tr') ; 
      trflag = 0 ; 
    } 
    td = document.createElement('td') ;

    if(list[ind].name==undefined)
    { td.setAttribute("style","border-top:1px solid #444") ; 
      td.setAttribute('align','left') ;  
      td.setAttribute('colspan',ncol) ;  
      p = document.createElement("p") ; 
      p.setAttribute("style","font-size:110%;padding-top:6px;"+
                             "font-family:arial;color:silver") ; 
      if(list[ind].gps==undefined)
        p.appendChild(document.createTextNode(list[ind].title)) ; 
      else
      { p.appendChild(document.createTextNode(list[ind].title+' : ')) ;
        a = document.createElement('a') ; 
        a.setAttribute('href',list[ind].gps) ; 
        a.setAttribute('style','font-size:90%;text-decoration:none') ; 
        a.appendChild(document.createTextNode('[GPS track]')) ;
       p.appendChild(a) ;
      }
      td.appendChild(p) ; 
      tr.appendChild(td) ; 
      table.appendChild(tr) ; 
      tr = document.createElement('tr') ; 
      colno = trflag = 0 ; 
      continue ; 
    } 

    lind = ind ;
    llink = piclink(lind) ;
    if(rind==list.length) { rind = ind ; rlink = piclink(rind) ; }
    // extra padding at the bottom before a title row
    if(0==colno%ncol) 
    { padflag = 4 ; 
      for(num=0,k=ind;k<list.length&&num<ncol&&list[k].name!=undefined;k++) 
        if(list[k].display!='none') num += 1 ;
      if(k<list.length&&num<=ncol) 
      { padflag = 8 ; if(ncol>5) padflag += 2*(ncol-5) ; }
    } 
    td.setAttribute('align','center') ;  
    if(list[ind].display=='|'&&colno%ncol>0)
      td.setAttribute('style','border-left:1px solid #444') ;
    a = document.createElement('a') ; 
    a.setAttribute('href',piclink(ind)) ;
    a.setAttribute("class","box") ; 
    a.setAttribute("title",list[ind].title) ; 
    img = genimage(list[ind],sizes,thind,function() 
            { nload += 1 ; 
              if(nload==nimg&&prefetch!=null) genimage(list[prefetch],sizes) ; 
            } ) ;
    img.setAttribute('border',1) ; 

    // vertical bar: margin is t-r-b-l or t-lr-b
    if(colno%ncol>0&&list[ind].display!='|') 
      img.setAttribute('style',"padding:4px;margin:4px 4px "+padflag+"px 5px") ;
    else img.setAttribute('style',"padding:4px;margin:4px 4px "+padflag+"px") ; 

    a.appendChild(img) ; 
    td.appendChild(a) ; 
    tr.appendChild(td) ; 
    trflag = 1 ; 

    if(prefetch==null) prefetch = ind ;
    colno += 1 ;
  }
  table.appendChild(tr) ;
  body.appendChild(table) ; 
  body.appendChild(linkp()) ; 
}
/* -------------------------------------------------------------------------- */
/*page*/
/* -------------------------------------------------------------------------- */

function pix(h)
{ var ind,query,opts="",k,r,prev,body ;
  thispage = location.href ;
  pagetitle = document.getElementsByTagName("title")[0].textContent ; 
  body = document.getElementsByTagName("body")[0] ; 
  body.setAttribute('style','background:black;margin:0') ; 

  if((typeof links)=='undefined') links = null ;
  if(h!=undefined&&h!=null&&links==null)
    links = [ { name: 'home' , href: h } ] ;

  prevind = null ;
  here = { name:null , ind:null , ncol:0 , full:0 } ;

  if((ind=thispage.lastIndexOf('/'))>=0) thispage = thispage.substring(ind+1) ; 

  // set up the array nitem containing the number of items in each block
  for(nitem=[],ind=-1;ind<list.length;ind=k) 
  { for(k=ind+1;k<list.length&&list[k].name!=undefined;k++) ; 
    if(k>ind+1) nitem.push(k-(ind+1)) ; 
  }

  maxthumb = setthumbshape(list,sizes,thumbshape,imagedir,hithumb) ; 

  if((ind=thispage.indexOf('?'))>=0)
  { query = thispage.substring(ind+1) ;      // 'image=garden&mode=n'
    thispage = thispage.substring(0,ind) ; 
    if(query.substring(0,6)=='image=')
    { query = query.substring(6) ; 
      if((ind=query.indexOf('&mode='))>=0)
      { opts = query.substring(ind+6) ; query = query.substring(0,ind) ; }
    }
    else if(query.substring(0,5)=='mode=')
    { opts = query.substring(5) ; query = null ; }
    else if(query=='') query = null ; 
    else
    { if((ind=query.indexOf('+'))>=0) 
      { opts = query.substring(ind+1) ; query = query.substring(0,ind) ; }
      thispage += '?image=' + query ;
      location.href = thispage + (opts==''?'':('&mode='+opts)) ; 
    }
  } 
  else query = null ; 

  if(query==null) ind = -1 ; 
  else for(ind=0;ind<list.length&&(list[ind].name!=query);ind++) ;

  for(k=0;
      k<list.length&&(list[k].name==undefined||list[k].retpage==undefined);
      k++) ;
  if(k<list.length) retpage = k ; else retpage = null ;

  if(ind>=0&&ind<list.length) // return to table if there is no retpage
  { // all the following code is finding whether we're stepping backwards
    if((k=document.referrer.lastIndexOf('/'))>=0)
    { prev = document.referrer.substring(k+1) ; k = prev.indexOf('?') ; }
    if(k>=0&&prev.substring(k+1,5)!='mode=')
    { prev = prev.substring(k+1) ; 
      k = prev.indexOf('&') ;
      if(k>=0) prev = prev.substring(0,k) ;
      for(k=0;k<list.length&&list[k].name!=prev;k++) ;
      if(k<list.length) prevind = k ;
    }
    else if(thispage==document.referrer) // came from table
    { for(k=ind+1;k<list.length&&list[ind].name==undefined;k++) ;
      if(k==list.length) prevind = list.length ;
    }
  }

  window.onresize = pixresize ;
  document.onkeydown = navigate ; 
  document.addEventListener('touchstart',startswipe,false) ;        
  document.addEventListener('touchmove',midswipe,false) ;        
  document.addEventListener('touchend',endswipe,false) ;
  if(ind<0||ind>=list.length) { fromnotes = 0 ; tabulate() ; }
  else { fromnotes = (opts.charAt(0)=='n'&&retpage!=null) ; display(query) ; }
}
/* -------------------------------------------------------------------------- */
/*page*/
/* ---------------------------------- swipes -------------------------------- */

function simulate(btn)
{ navigate({keyCode:btn,preventDefault:function(){}}) ; }

var xloc=null,yloc=null,xstart=null,ystart=null,fingersep=null,startsep=null ; 
var swipetime=null ;

function startswipe(e) 
{ var x0=e.touches[0].clientX,y0=e.touches[0].clientY,x1,y1 ;
  if(e.touches.length==1) 
  { xstart = x0 ; ystart = y0 ; swipetime = new Date().getTime() ; } 
  else if(e.touches.length==2&&here.ncol==0)
  { e.preventDefault() ;        // don't let iOS take control of pinches
    x1 = e.touches[1].clientX ;
    y1 = e.touches[1].clientY ;
    startsep = Math.sqrt((x1-x0)*(x1-x0)+(y1-y0)*(y1-y0)) ;
  }
}
function midswipe(e) 
{ var x0=e.touches[0].clientX,y0=e.touches[0].clientY,x1,y1 ;
  if(e.touches.length==1) 
  { xloc = x0 ; yloc = y0 ; fingersep = startsep = null ; } 
  else if(e.touches.length==2&&here.ncol==0)
  { e.preventDefault() ; 
    x1 = e.touches[1].clientX ;
    y1 = e.touches[1].clientY ;
    fingersep = Math.sqrt((x1-x0)*(x1-x0)+(y1-y0)*(y1-y0)) ;
    xloc = yloc = xstart = ystart = swipetime = null ; 
  }
}
function endswipe(e) 
{ var v ;
  if(fingersep!=null&&startsep!=null&&here.ncol==0)
  { e.preventDefault() ; 
    if(fingersep>startsep+50) simulate(38) ; 
    else if(startsep>fingersep+50) simulate(40) ; 
  }
  else if(xloc!=null&&yloc!=null&&xstart!=null&&ystart!=null&&swipetime!=null)
  { swipetime = new Date().getTime() - swipetime ; 
    v = Math.sqrt((xloc-xstart)*(xloc-xstart)+(yloc-ystart)*(yloc-ystart)) ;
    if(v>0.65*swipetime)
    { e.preventDefault() ; 
      if(Math.abs(xloc-xstart)>100&&Math.abs(yloc-ystart)<100)
      { if(xloc>xstart) simulate(37) ; else simulate(39) ; }
      else if(yloc>ystart+100&&Math.abs(xloc-xstart)<100) simulate(13) ; 
    }
  }
  xloc = yloc = xstart = ystart = fingersep = startsep = swipetime = null ; 
}

Archived from pix.html

// www.masterlyinactivity.com/software/pix.html
/* The MIT License

   Copyright (c) 2016, by Colin Champion <colin.champion@masterlyinactivity.com>

   Permission is hereby granted, free of charge, to any person obtaining
   a copy of this software and associated documentation files (the
   "Software"), to deal in the Software without restriction, including
   without limitation the rights to use, copy, modify, merge, publish,
   distribute, sublicense, and/or sell copies of the Software, and to
   permit persons to whom the Software is furnished to do so, subject to
   the following conditions:

   The above copyright notice and this permission notice shall be
   included in all copies or substantial portions of the Software.

   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
   MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
   NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
   BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
   ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
   CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
   SOFTWARE.
*/
var pixlib = 1 ; 

/* -------------------------------------------------------------------------- */

function enterfullscreen() 
{ if(document.documentElement.requestFullscreen) 
    document.documentElement.requestFullscreen() ;
  else if(document.documentElement.mozRequestFullScreen) 
    document.documentElement.mozRequestFullScreen() ;
  else if(document.documentElement.webkitRequestFullscreen) 
    document.documentElement.webkitRequestFullscreen() ;
  else if(document.documentElement.msRequestFullscreen) 
    document.documentElement.msRequestFullscreen() ;
}
function queryfullscreen() 
{ if(document.fullScreen||document.mozFullScreen||document.webkitIsFullScreen)
    return 1 ; 
  else return 0 ;
}
function querycanfullscreen()
{ if ( document.documentElement.requestFullscreen
    || document.documentElement.mozRequestFullScreen
    || document.documentElement.webkitRequestFullscreen 
    || document.documentElement.msRequestFullscreen ) return 1 ; 
  else return 0 ;
}
/* -------------------------------------------------------------------------- */

function thumbind(sizes)
{ var ind ;
  for(ind=0;ind<sizes.length&&sizes[ind].type!='thumb';ind++) ;
  if(ind==sizes.length) { alert('no sizes entry of type "thumb"') ; throw '' ; }
  return ind ;
}
/* -------------------------------------------------------------------------- */

function retlink(list,ind)
{ var i,k,blink ;
  for(i=ind;i>=0&&list[i].retpage==undefined;i--) ; 
  if(i<0) return null ; ; 
  blink = list[i].retpage+'.html' ;
  for(k=ind;k>=i&&list[k].retid==undefined;k--) ;
  if(k>=i) blink += '#' + list[k].retid ;
  return blink ;
}
/* -------------------------------------------------------------------------- */
/*page*/
/* ---- fill in missing thumbs and raws, return maximum thumb dimensions ---- */

function setthumbshape(list,sizes,thumbshape,imagedir,hithumb)
{ var ind,r0,r1,maxthumb,k,umax,doraw ;
  if(typeof thumbshape=='undefined'||thumbshape==undefined) thumbshape = null ; 
  if(typeof imagedir=='undefined'||imagedir==undefined) imagedir = null ; 
  if(typeof hithumb=='undefined'||hithumb==undefined) hithumb = null ; 

  ind = thumbind(sizes) ; 
  if(thumbshape==null||thumbshape.length!=2)
  { if(sizes[ind].scale>0) r0 = sizes[ind].scale / sizes[0].scale ;
    else { alert('thumbs have size '+sizes[ind].scale) ; throw '' ; }
  }

  for(doraw=r1=ind=0;ind<sizes.length&&sizes[ind].type!='raw';ind++) ;
  if(ind<sizes.length) { r1 = sizes[ind].scale / sizes[0].scale ; doraw = 1 ; }

  for(maxthumb=[0,0],ind=0;ind<list.length;ind++) if(list[ind].name!=undefined) 
  { if(list[ind].thumbshape==undefined)
    { if(thumbshape!=null&&thumbshape.length==2) 
        list[ind].thumbshape = thumbshape ;
      else for(list[ind].thumbshape=[0,0],k=0;k<2;k++) 
        list[ind].thumbshape[k] = Math.floor(0.5+list[ind].shape[k]*r0) ; 
    }
    else if(list[ind].hithumb==undefined) list[ind].hithumb = hithumb ;
    for(k=0;k<2;k++) if(list[ind].thumbshape[k]>maxthumb[k]) 
      maxthumb[k] = list[ind].thumbshape[k] ; 
    if(doraw&&list[ind].rawshape==undefined)
      for(list[ind].rawshape=[0,0],k=0;k<2;k++) 
        list[ind].rawshape[k] = Math.floor(0.5+list[ind].shape[k]*r1) ; 
    if(imagedir==null) list[ind].filename = list[ind].name ;
    else list[ind].filename = imagedir + '/' + list[ind].name ;
  }

  // fill in the fontsize field in sizes
  for(R=k=ind=0;ind<sizes.length;ind++) if(sizes[ind].type==undefined) 
  { R += Math.log(sizes[ind].scale) ; k += 1 ; }
  R /= k ;

  for(ind=0;ind<sizes.length;ind++) 
    if(sizes[ind].type==undefined&&sizes[ind].fontsize==undefined) 
      sizes[ind].fontsize = 16 * Math.exp((Math.log(sizes[ind].scale)-R)/3) ;

  for(umax=null,ind=0;ind<sizes.length;ind++) if(sizes[ind].type==undefined)
    if(umax==null||sizes[ind].fontsize>umax) umax = sizes[ind].fontsize ;
  for(ind=0;ind<sizes.length;ind++) 
    if(sizes[ind].type=='raw'&&sizes[ind].fontsize==undefined)
      sizes[ind].fontsize = umax ; 

  return maxthumb ;
}
/* ----------------------------- image functions ---------------------------- */

function imgshape(item,sizes,sizeno)
{ if(sizes[sizeno].type=='thumb') return item.thumbshape ; 
  else if(sizes[sizeno].type=='raw') return item.rawshape ; 
  return [ Math.floor(0.5+item.shape[0]*sizes[sizeno].scale/sizes[0].scale) ,
           Math.floor(0.5+item.shape[1]*sizes[sizeno].scale/sizes[0].scale) ] ;
}
function imgsize(item,sizes,sizeno) { return imgshape(item,sizes,sizeno) ; } 

/* -------------------------------------------------------------------------- */
/*page*/
/* ----- sparepix finds the margins left if item is displayed at usesize ---- */

function sparepix(item,sizes,sizeno)
{ var shape=imgsize(item,sizes,sizeno),w=shape[0],h=shape[1],r ;
  h += Math.floor(0.5+1.25*sizes[sizeno].fontsize) + 2 ;
  if(item.caption!=undefined) h += Math.floor(0.5+sizes[sizeno].fontsize) + 2 ;

  r = [ window.innerHeight-50-h , window.innerHeight-h ] ;
  if(window.innerWidth-w<r[0]) r[0] = window.innerWidth-w ;
  if(window.innerWidth-w-50<r[1]) r[1] = window.innerWidth-w-50 ;
  return r ;
}
/* ---------------------- construct the jpg path name ----------------------- */

function jpg(item,sizes,sizeno)
{ return item.filename + sizes[sizeno].suffix + '.jpg' ; }

/* ------------------- generate the srcset for an image --------------------- */

function srcset(item,sizes,sizeno)
{ var i,s,hith,scale ;
  if(sizes[sizeno].type=='raw') return '' ;

  if(sizes[sizeno].type=='thumb') 
  { hith = item.hithumb ;
    if(hith!=undefined&&hith!=null )
      return item.filename + hith.suffix + '.jpg ' + hith.scale + 'x' ;
    if( item.thumbshape[0]*item.shape[1] != item.thumbshape[1]*item.shape[0] ) 
      return '' ;
    scale = ( sizes[0].scale * item.thumbshape[0] ) / item.shape[0] ;
  }
  else scale = sizes[sizeno].scale ;

  for(s='',i=0;i<sizes.length;i++) if(sizes[i].type==undefined)
    if(sizes[i].scale>scale)
  { if(s!='') s += ', ' ;
    s += jpg(item,sizes,i) + ' ' +
           (sizes[i].scale/scale).toFixed(1) + "x" ;
  }
  return s ;
}
/* -------------------------------------------------------------------------- */

function preload(item,sizes,sizeno,loadaction) 
{ return genimage(item,sizes,sizeno,loadaction) ; }

/* ------- getsize finds the largest image size which fits the screen ------- */

function getsize(item,sizes,loadstatus,thresh) 
{ var i,ibest,ismall,spare ;

  for(ismall=ibest=null,i=0;i<sizes.length;i++) 
    if(sizes[i].type==undefined&&(loadstatus==undefined||loadstatus[i]>=thresh))
  { if(ismall==null||sizes[i].scale<sizes[ismall].scale) ismall = i ; 
    spare = sparepix(item,sizes,i) ;
    if(spare[0]>=0||spare[1]>=0)
      if(ibest==null||sizes[i].scale>sizes[ibest].scale) ibest = i ; 
  }
  if(ibest==null) return ismall ; else return ibest ;
}
/* -------------------------------------------------------------------------- */
/*page*/
/* --------------------------------- genimage ------------------------------- */

function genimage(item,sizes,sizeno,loadfunc) 
{ var img=document.createElement('img') , shape , s ;
  if(sizeno==undefined||sizeno==null||sizeno<0) sizeno = getsize(item,sizes) ;
  shape = imgshape(item,sizes,sizeno) ; 
  img.setAttribute('width',shape[0]) ; 
  img.setAttribute('height',shape[1]) ; 
  if((s=srcset(item,sizes,sizeno))!='') img.setAttribute("srcset",s) ; 
  if(loadfunc!=undefined&&loadfunc!=null) img.onload = loadfunc ;
  img.setAttribute("src",jpg(item,sizes,sizeno)) ; 
//  console.log(img.src+(s==""?"":' ['+s+']')) ;
  return img ;
}
/* ------- setimgpos sets the image position according to window size ------- */

function setimgpos(parms,shape)
{ var imgw=shape[0],imgh=shape[1],x,y,s,overflowx,overflowy ;
  var pad=parms.headh + parms.caph ;
  // set space to the amount of room we've got for the image div
  var space = [ window.innerWidth , window.innerHeight-pad ] ;
  if(parms.portrait) space[0] -= 50 ; else space[1] -= 50 ; 

  // set imgw and imgh to the desired dimensions of the image div
  if(imgh>space[1]) imgw += 20 ; // generous scrollbar
  if(imgw>space[0]) imgh += 20 ; 

  // set (x,y) to the coords of the main div
  x = (window.innerWidth-imgw)/2 ;
  y = (window.innerHeight-imgh)/2 - parms.headh;
  if(parms.portrait) { if(x<50) x = 50 ; if(y<0) y = 0 ; }
  else { if(x<0) x = 0 ; if(y<50) y = 50 ; } 
  x = Math.floor(x) ; 
  y = Math.floor(y) ; 

  // now constrain imgw, imgh by the window size
  if(imgw>window.innerWidth-x) imgw = window.innerWidth - x ;
  if(imgh>window.innerHeight-y-pad) imgh = window.innerHeight-y-pad ;

  s = 'position:absolute;left:'+x+'px;width:'+(window.innerWidth-x)+'px;top:' ;
  s += y+'px;height:'+(pad+imgh)+'px;overflow:hidden' ;
  parms.maindiv.setAttribute('style',s) ; 

  s = 'font-family:arial;color:silver;position:absolute;' ;
  s += 'left:0;width:'+(window.innerWidth-x)+'px;top:0;' ;
  s += 'height:'+parms.headh+'px;overflow:hidden;font-size:'+parms.headf+'px' ;
  parms.headdiv.setAttribute('style',s) ; 

  if(parms.capdiv!=null)
  { s = 'font-family:arial;color:silver;position:absolute;' ;
    s += 'left:0;width:'+(window.innerWidth-x)+'px;bottom:0;' ;
    s += 'height:'+parms.caph+'px;overflow:hidden;font-size:'+parms.capf+'px' ;
    parms.capdiv.setAttribute('style',s) ; 
  }

  if(imgw<shape[0]) overflowx = 'scroll' ; else overflowx = 'hidden' ; 
  if(imgh<shape[1]) overflowy = 'scroll' ; else overflowy = 'hidden' ; 
  s = 'position:absolute;left:0;width:'+imgw+'px;top:'+parms.headh+'px;' ;
  s += 'height:'+imgh+'px;overflow-x:'+overflowx+';overflow-y:'+overflowy ;
  parms.imgdiv.setAttribute('style',s) ; 
}
/* -------------------------------------------------------------------------- */
/*page*/
/* -------------------- find the list ind with a given name ----------------- */

function findimage(list,name)
{ var ind ; 
  for(ind=0;ind<list.length;ind++) if(list[ind].name==name) return ind ; 
  return null ;
}
/* ---- adjust element attributes in accordance with a change of img size --- */

function setimg(parms,item,sizes,sizeno,fetchitem,preloadstatus)
{ var font,spare,shape,img,s,i,fetcher ; 

  if(sizeno==null) return ;
  enredcell(parms.enlink,sizes,sizeno,2) ;
  enredcell(parms.redlink,sizes,sizeno,-1) ;

  spare = sparepix(item,sizes,sizeno) ;
  shape = imgsize(item,sizes,sizeno) ;
  font = sizes[sizeno].fontsize ;
  parms.headh = 2 + Math.floor(0.5+1.25*font) ;
  parms.headf = Math.floor(0.5+font) ;
  if(item.caption==undefined) parms.caph = parms.capf = 0 ; 
  else
  { parms.caph = 2+Math.floor(0.5+font) ; 
    parms.capf = Math.floor(0.5+0.8*font) ;
  }

  parms.portrait = spare[0]<spare[1] ;

  // create the image for the new size
  fetcher = null ; 
  if(fetchitem!=undefined&&fetchitem!=null) 
    if(preloadstatus[i=getsize(fetchitem,sizes)]==0)  
  { preloadstatus[i] = 1 ; 
    fetcher = function() { genimage(fetchitem,sizes,i) ; } ;
  }
  img = genimage(item,sizes,sizeno,fetcher) ; 

  if(parms.img==null) parms.imgdiv.appendChild(img) ;
  else parms.imgdiv.replaceChild(img,parms.img) ;
  parms.img = img ; 
  setimgpos(parms,shape) ;
  return sizeno ;
}
/* -------------------------------------------------------------------------- */
/*page*/
/* ------------ rescale finds the next larger/next smaller image ------------ */

function rescale(sizes,sizeno,dir)
{ var i,ind ;

  if(dir<0) 
  { for(ind=null,i=0;i<sizes.length;i++) if(sizes[i].type==undefined)
      if(sizes[i].scale<sizes[sizeno].scale||sizes[sizeno].type=='raw')
        if(ind==null||sizes[i].scale>sizes[ind].scale) ind = i ;
    return ind ;
  }

  if(sizes[sizeno].type=='raw') return null ; 
  for(ind=null,i=0;i<sizes.length;i++) 
    if(sizes[i].type==undefined||(sizes[i].type=='raw'&&dir==2))
      if( sizes[i].scale>sizes[sizeno].scale || sizes[i].type=='raw' )
  { if(ind==null) ind = i ; 
    else if(sizes[ind].type=='raw') ind = i ; 
    else if(sizes[i].scale<sizes[ind].scale&&sizes[i].type!='raw') ind = i ;
  }
  return ind ;
}
/* --------------- reposition image in response to a window resize ---------- */

function resizesub(iparms,dparms)
{ var k2,k0,dir,flag=0,shape,fetch,item=iparms.item,sizes=iparms.sizes ;
  var sizeno=iparms.sizeno ;
  k2 = getsize(item,sizes,iparms.loadstatus,2) ; 
  k0 = getsize(item,sizes) ; 

  // there's a better size already loaded
  if(iparms.holdsize==0&&k2!=sizeno) 
  { setimg(dparms,item,sizes,k2,iparms.fetchitem,iparms.preloadstatus) ; 
    iparms.sizeno = k2 ;
    return ; 
  } 
  // there's a better size not yet loaded
  else if(iparms.holdsize==0&&k0!=sizeno&&iparms.loadstatus[k0]==0) flag = 1 ; 
  else if(iparms.holdsize!=0&&k0==sizeno) iparms.holdsize = 0 ;
  else if(iparms.holdsize==0&&iparms.window!=null)
  { if( window.innerWidth>=iparms.window[0]
     && window.innerHeight>=iparms.window[1] ) dir = 1 ;
    else if( window.innerWidth<=iparms.window[0]
          && window.innerHeight<=iparms.window[1] ) dir = -1 ;
    else dir = null ; 
    if(dir!=null&&(k0=rescale(sizes,sizeno,dir))!=null) 
      if(iparms.loadstatus[k0]==0) flag = 1 ; 
  }
  if(flag)
  { iparms.window = null ;
    iparms.loadstatus[k0] = 1 ; 
    genimage(item,sizes,k0,genloadhandler(iparms.loadstatus,k0)) ; 
  }
  // redisplay if portrait<->landscape makes a fit possible, else redraw
  shape = imgsize(item,sizes,sizeno) ;
  spare = sparepix(item,sizes,sizeno) ;
  if(dparms.portrait!=(spare[0]<spare[1])&&(spare[0]<0)!=(spare[1]<0))
    dparms.portrait = 1-dparms.portrait ;
  setimgpos(dparms,imgsize(item,sizes,sizeno)) ;
}
/* -------------------------------------------------------------------------- */
/*page*/
/* ---------------- create a div containing an image title ------------------ */

function maketextdiv(title)
{ var div=document.createElement('div') ;
  div.appendChild(document.createTextNode(title)) ; 
  div.setAttribute('style','font-family:arial;color:silver') ;
  return div ; 
}
/* --------------------- create/reset enlarge/reduce icon ------------------- */

function enredcell(td,sizes,sizeno,dir)
{ var a,astyle ;
  astyle = 'font-family:arial;font-weight:normal;text-decoration:none;' ;
  while(td.firstChild) td.removeChild(td.firstChild) ;

  if(rescale(sizes,sizeno,dir)!=null) 
  { a = document.createElement('a') ; 
    a.setAttribute('href',dir>0?'javascript:enlarge()':'javascript:reduce()') ; 
    a.setAttribute("title",dir>0?"enlarge [\u2191 key]":"reduce [\u2193 key]") ; 
    a.setAttribute("style",astyle) ; 
    a.appendChild(document.createTextNode(dir>0?'\u2295':'\u2296')) ;
    td.appendChild(a) ; 
  }
}
/* --------------------- create a cell for navigation icon ------------------ */

function navcell(link,dir,string)
{ var a,td=document.createElement('td'),s='center',astyle ;
  if(dir=='l') s = 'left' ; else if (dir=='r') s = 'right' ;
  astyle = 'font-family:arial;font-weight:normal;text-decoration:none;' ;
  s = "text-align:" + s + (dir==0?'':';font-size:20px;') ;
  s += 'text-align:center;vertical-align:middle;font-size:16px;' +
                'line-height:16px;width:16px;height:16px' ;
  td.setAttribute("style",s) ; 
  if(dir!=0) td.setAttribute("rowspan",3) ; 

  if(link!=null) 
  { a = document.createElement('a') ; 
    a.setAttribute('href',link) ; 
    if(dir>0) s = "next [\u2192 key]" ; 
    else if(dir==0) s = "back to " + string + " [\u21b5 key]" ;
    else s = "prev [\u2190 key]" ;
    a.setAttribute("title",s) ; 
    a.setAttribute("style",astyle) ; 
    if(dir>0) s = '>' ; else if(dir==0) s = '\u21b5' ; else s = '<' ;
    a.appendChild(document.createTextNode(s)) ;
    td.appendChild(a) ; 
  }
  return td ; 
}
/* -------------------------------------------------------------------------- */

var maxthumb,thumbshape,imagedir,hithumb,thind=null,sfx=null ;

function thumb(name) 
{ var ind,s ; 
  if(maxthumb==undefined||maxthumb==null) 
    maxthumb = setthumbshape(list,sizes,thumbshape,imagedir,hithumb) ;
  if(thind==null) thind = thumbind(sizes) ;
  if(sfx==null) sfx = sizes[thind].suffix ;

  if(null==(ind=findimage(list,name))) alert('Missing image: '+name) ;
  s = srcset(list[ind],sizes,thind) ;
  document.write('<a href="'+pixpage+'?image='+name+'&mode=n">'+
       '<img class=pix src="'+
       jpg(list[ind],sizes,thind)+'" width='+list[ind].thumbshape[0]+
       ' height='+list[ind].thumbshape[1]+' title="'+list[ind].title+
       ((s==''||s==null)?'':('" srcset="'+s)) + '"></a>') ; 
}
/* -------------------------------------------------------------------------- */

var drawparms=null,itemparms=null ; 

function setsize(k)  
{ if(itemparms==null||(k=rescale(itemparms.sizes,itemparms.sizeno,k))==null) 
    return ;
  itemparms.sizeno = setimg(drawparms,itemparms.item,itemparms.sizes,k) ; 
  itemparms.holdsize = 1 ;  
}
function genloadhandler(loadstatus,k) 
{ return function() { if(itemparms!=null) { loadstatus[k] = 2 ; resize() ; } } ;
}
function resize() { if(itemparms!=null) resizesub(itemparms,drawparms) ; } 
function enlarge() { setsize(2) ; }
function reduce() { setsize(-1) ; }

/* -------------------------------------------------------------------------- */

function gendisplay(element,item,sizes,llink,blink,rlink,fromstring,fetchitem) 
{ var i,sizeno,captioned,table,tr,td,a,utd,dtd ; 
  if(element==undefined||element==null) 
  { itemparms = drawparms = null ; return ; }
  sizeno = getsize(item,sizes) ;
  if(item.caption==undefined) captioned = 0 ; else captioned = 1 ; 

  itemparms = { item:          item,
                sizes:         sizes,
                sizeno:        sizeno,
                holdsize:      0,
                loadstatus:    new Array(sizes.length),
                fetchitem:     fetchitem,
                preloadstatus: new Array(sizes.length),
                window:        [ window.innerWidth , window.innerHeight ] 
              } ;

  for(i=0;i<sizes.length;i++) 
    itemparms.loadstatus[i] = itemparms.preloadstatus[i] = 0 ;
  itemparms.loadstatus[sizeno] = 2 ;

  // make the navigation table
  table = document.createElement('table') ;
  table.setAttribute('cellpadding',0) ; 
  table.setAttribute('cellspacing',0) ; 

  // the '<' link
  tr = document.createElement('tr') ;
  tr.appendChild(navcell(llink,-1)) ; 

  // the enlarge link
  utd = document.createElement('td') ;
  utd.setAttribute("style",'text-align:center;font-size:16px;' +
                           'line-height:16px;width:16px;height:16px') ; 
  tr.appendChild(utd) ; 
  table.appendChild(tr) ; 

  // the '>' link
  tr.appendChild(navcell(rlink,1)) ; 
  table.appendChild(tr) ; 

  // the return link
  tr = document.createElement('tr') ;
  tr.appendChild(navcell(blink,0,fromstring)) ; 
  table.appendChild(tr) ; 

  // the reduce link
  tr = document.createElement('tr') ;
  dtd = document.createElement('td') ;
  dtd.setAttribute("style",'text-align:center;font-size:16px;' +
                          'line-height:16px;width:16px;height:16px') ; 
  tr.appendChild(dtd) ; 
  table.appendChild(tr) ; 

  drawparms = { headh:     null, 
                caph:      null, 
                headf:     null, 
                capf:      null, 
                enlink:    utd,
                redlink:   dtd,
                img:       null,
                portrait:  null,
                maindiv:   document.createElement('div'), 
                headdiv:   maketextdiv(item.title),
                imgdiv:    document.createElement('div'),
                capdiv:    captioned==0?null:maketextdiv(item.caption)
              } ;

  setimg(drawparms,item,sizes,sizeno,fetchitem,itemparms.preloadstatus) ; 

  drawparms.imgdiv.appendChild(drawparms.img) ;
  drawparms.maindiv.appendChild(drawparms.headdiv) ; 
  drawparms.maindiv.appendChild(drawparms.imgdiv) ; 
  if(captioned) drawparms.maindiv.appendChild(drawparms.capdiv) ; 

  while(element.firstChild) element.removeChild(element.firstChild) ;
  element.appendChild(table) ; 
  element.appendChild(drawparms.maindiv) ; 
}
/* -------------------------------------------------------------------------- */

function pixinfodiv(list,i,sizes)
{ var d=document.createElement('div'),ind,hind,r,k,shape,s ;

  s = 'Name: ' + list[i].name ;
  if(list[i].display=='none') s += ' [hidden]' ;
  d.appendChild(document.createTextNode(s)) ;
  d.appendChild(document.createElement('br')) ;

  d.appendChild(document.createTextNode('Title: '+list[i].title)) ;
  d.appendChild(document.createElement('br')) ;

  for(hind=null,ind=0;ind<i;ind++) if(list[ind].name==null) hind = ind ;
  if(hind!=null) 
  { d.appendChild(document.createTextNode('Section: '+list[hind].title)) ;
    d.appendChild(document.createElement('br')) ;
  }

  // how many shapes?
  for(r=ind=0;ind<sizes.length;ind++) if(sizes[ind].type==undefined) r += 1 ;
  s = 'Available in ' + r + ' size' + (r>1?'s: ':': ') ;

  // print the shapes
  for(k=ind=0;ind<sizes.length;ind++) if(sizes[ind].type==undefined)
  { if(k>0) { if(k==r-1) s += ' and ' ; else s += ', ' ; }
    shape = imgsize(list[i],sizes,ind) ;
    s += shape[0] + 'x' + shape[1] ;
    k += 1 ; 
  }
  d.appendChild(document.createTextNode(s)) ;
  d.appendChild(document.createElement('br')) ;

  // print the thumb shape
  shape = list[i].thumbshape ;
  s = 'Thumb: ' + shape[0] + 'x' + shape[1] ;
  hith = list[i].hithumb ;
  if(hith!=undefined&&hith!=null )
    s += ' (hi-res: '+ shape[0]*hith.scale + 'x' + shape[1]*hith.scale + ')' ;
  d.appendChild(document.createTextNode(s)) ;
  d.appendChild(document.createElement('br')) ;

  // print the raw shape
  for(ind=0;ind<sizes.length;ind++) if(sizes[ind].type=='raw')
  { shape = imgsize(list[i],sizes,ind) ;
    s = 'Raw: ' + shape[0] + 'x' + shape[1] ;
    d.appendChild(document.createTextNode(s)) ;
    d.appendChild(document.createElement('br')) ;
  }
  return d ; 
}
/* -------------------------------------------------------------------------- */

function tabinfodiv(list)
{ var d=document.createElement('div'),i,npix,nsect,nhidden,s ; 
  for(npix=nsect=nhidden=i=0;i<list.length;i++)
    if(list[i].name==undefined) nsect += 1 ; 
    else 
    { npix += 1 ; 
      if(list[i].display=='none') nhidden += 1 ; 
      if(i==0) nsect += 1 ; 
    }

  if(nsect>1) 
  { d.appendChild(document.createTextNode(nsect+' sections')) ;
    d.appendChild(document.createElement('br')) ;
  }
  s = npix + ' photos' ;
  if(nhidden) s += '(' + nhidden + ' hidden)' ;
  d.appendChild(document.createTextNode(s)) ;
  d.appendChild(document.createElement('br')) ;
  return d ; 
}

Archived from routemaster.html

var segments=[],selected,actions,nactions,sel,dragging,overviewing=0 ; 
var mouseopt=0,resuri,xmlfile=null,shifted=null,unsavedchanges=[] ; 
var body,mapdiv,pro,starsdiv=null,routeprops,map=null,clickhandle=null ; 
var scroller=null,imgdiv,imghandle,imginfo,imgind ;
var cursorbtn,scissorsbtn,binbtn,undobtn,redobtn,penbtn,setbtn=null,dlbtn ;

var defparms = {tol:10,maxsep:95,wppenalty:1000,vweight:1} ;
var parser = new DOMParser() ;

var infowindow = 
{ handle: null , 
  type: null , 
  open: function(s,pos,type)
  { this.handle = new google.maps.InfoWindow({content:s,position:pos}) ;
    this.handle.open(map) ; 
    google.maps.event.addListener(this.handle,'closeclick',function() 
    { if(infowindow.type=='highlight')
      { redraw(selected[0]) ; 
        if(scroller!=null) { clearInterval(scroller) ; scroller = null ; }
      }
      else if(infowindow.type=='phinfo') walkto(selected[0],selected[1]) ;
      infowindow.handle = infowindow.type = starsdiv = null ; 
    } ) ;
    this.type = type ; 
  } , 
  close: function() 
  { if(this.handle==null) return null ;
    var response = this.type ;
    this.handle.close() ; 
    if(response=='highlight') 
    { redraw(selected[0]) ; 
      if(scroller!=null) { clearInterval(scroller) ; scroller = null ; }
    }
    else if(response=='phinfo') walkto(selected[0],selected[1]) ; 
    this.handle = this.type = starsdiv = null ; 
    return response ; 
  } 
} ; 
/* --------------- construct a segment from an xml document ----------------- */

function genseg(a,b) 
{ this.data = a ; 
  this.props = b ; 
  this.route = this.routehandler = this.dots = this.dothandler = null ; 
  this.colour = "red" ;
}
/* -------------------------------------------------------------------------- */
/*                                CONSTRUCTORS                                */
/* -------------------------------------------------------------------------- */

function dotpath(a,b)
{ this.path = [a,b] ;
  this.cursor = 'default' ;
  this.geodesic = true ;
  this.strokeOpacity = 0 ;
  this.icons = [ { icon:   { path: 'M 0 0 L 1 0',strokeOpacity:1,scale:1 } , 
                   offset: '1px' , 
                   repeat: '4px' 
                  } ] ;
  this.zIndex = 0 ;
}
/* -------------------------------------------------------------------------- */

function linepath(s0,start,end,colour,width)
{ var i,len=(start<0?segments[s0].data.length:end-start) ; 
  if(width==undefined) width = 2 ; 
  this.path = new Array(len) ; 
  if(start<0) for(i=0;i<len;i++) this.path[i] = segments[s0].data[i].pos ;
  else for(i=0;i<len;i++) this.path[i] = segments[s0].data[start+i].pos ; 
  this.clickable = 'false' ;
  this.cursor = 'default' ;
  this.geodesic = true ;
  this.strokeColor = colour ;
  this.strokeOpacity = 1.0 ;
  this.strokeWeight = width ;
  if(width==2) this.zIndex = 0 ; else this.zIndex = 1 ; 
}
/* -------------------------------------------------------------------------- */

function listinfo()
{ this.list = [] ; 
  this.sizes = [] ;
  this.uri = null ; 
  this.thumbind = this.scale = this.status = this.type = this.pixpage = null ; 
}
/* -------------------------------------------------------------------------- */
/*page*/
/* -------------------------------------------------------------------------- */
/*                             UTILITY FUNCTIONS                              */
/* -------------------------------------------------------------------------- */

function interp(x,y,lamda)
{ return google.maps.geometry.spherical.interpolate(x,y,lamda) ; }

function bearing(x,y)
{ return google.maps.geometry.spherical.computeHeading(x,y) ; }

/* --------------------------- button handlers  ----------------------------- */

function greyout(btn)
{ if(overviewing||btn.active==0) return 0 ; 
  btn.btn.setAttribute('src',btn.greyimg) ; 
  btn.ui.removeEventListener('click',btn.handler) ;
  if(btn==penbtn) btn.ui.removeEventListener('contextmenu',photoprompt) ;
  btn.ui.style.cursor = 'default' ; 
  btn.active = 0 ; 
  return 1 ; 
}
function blackout(btn)
{ if(overviewing||btn.active) return ; 
  btn.btn.setAttribute('src',btn.blackimg) ; 
  btn.ui.addEventListener('click',btn.handler) ;
  if(btn==penbtn) btn.ui.addEventListener('contextmenu',photoprompt) ; 
  btn.ui.style.cursor = 'pointer' ; 
  btn.active = 1 ; 
}
/* ------------------------ enter/exit full screen -------------------------- */

// most of the code is available from pixlib
function enterFullscreen() { infowindow.close() ; enterfullscreen() ; } 

function exitFullscreen() 
{ infowindow.close() ; 
  if(document.exitFullscreen) document.exitFullscreen() ;
  else if(document.mozCancelFullScreen) document.mozCancelFullScreen() ;
  else if(document.webkitExitFullscreen) document.webkitExitFullscreen() ;
}
/* -------------------------------------------------------------------------- */

function findimg(id)
{ var i ; 
  for(i=0;i<imginfo.list.length;i++) 
    if(imginfo.list[i].name!=undefined&&imginfo.list[i].name==id) return i ; 
  return -1 ; 
}
/* ------------------- message warning of unsaved changes ------------------- */

function unsavedmsg(ok)
{ var msg , len = unsavedchanges.length , i ; 
  if(len==0) return null ; 
  msg = 'You have ' + len + ' unsaved change' + (len==1?"":'s') ; 
  if(len<=3) for(i=0;i<len;i++)
     msg += (i?',':' (') + unsavedchanges[i] + (i==len-1?')':'') ;
  msg += '\nIf you hit ' + (ok?'[OK]':'[Leave page]') ; 
  return msg + (len==1?' this change':' these changes') + ' will be lost.' ;
}
/* -------------------------------------------------------------------------- */
/*page*/
/* --------------- selpoint: choose the clicked waypoint  ------------------- */

function selpoint(event)
{ var i,j,closest,d,mindist,s0,s1 ; 
  if(dragging) return ; 
  var flag = (infowindow.close()=='wpinfo') && (shifted==0) ; 

  if(overviewing==0&&shifted)
  { s0 = selected[0] ;
    s1 = segments[s0].data.length ;
    insert(s0,s1,1) ;
    segments[s0].data[s1].setpos(event.latLng) ;
    redrawconnect(s0,s1) ;
    done(['move',s0,s1,event.latLng,event.latLng,1]) ; 
  }
  else for(s1=s0=-1,i=0;i<segments.length;i++) 
    for(j=0;j<segments[i].data.length;j++)
  { d = dist(segments[i].data[j].pos,event.latLng) ;
    if(s0<0||d<mindist) { s0 = i ; s1 = j ; mindist = d ; } 
  }
  walkto(s0,s1,flag) ; 
}
/* -------------------------- track highlighter  ---------------------------- */

function highlight()
{ var s0=selected[0],scroll,thind=null ;
  infowindow.close() ;
  undraw(s0) ; 
  draw(s0,4) ;
  if(imginfo.uri!=null) thind = thumbind(imginfo.sizes) ;
  scroll = highdiv(segments[s0].props,imginfo.list,imginfo.sizes,
                   thind,segments[s0].props.photo) ;
  scroller = scroll.scroller ;
  infowindow.open(scroll.div,northernmost(segments[s0].data),'highlight') ; 
}
/* ------------------------------- getbtnpos -------------------------------- */

function getbtnpos(btnno)
{ var bounds=map.getBounds(),sw,ne,lat,lon,lam ;
  sw = bounds.getSouthWest() ; 
  ne = bounds.getNorthEast() ; 
  lam = 52.0 / window.innerHeight ; 
  lat = lam*ne.lat() + (1-lam)*sw.lat() ; 
  lam = 0.5 + (btnno*32-112.0)/window.innerWidth ;
  lon = lam*ne.lng() + (1-lam)*sw.lng() ;
  return new google.maps.LatLng(lat,lon) ; 
}
/* ----- unambig: does the selected waypoint determine a unique segment? ---- */

function unambig() // does the selected waypoint determine a unique segment? 
{ var s0=selected[0],s1=selected[1] ; 
  if(segments.length==1) return 1 ; 
  if( ( s0==segments.length-1 || s1!=segments[s0].data.length-1 ||
        ! segments[s0].data[s1].pos.equals(segments[s0+1].data[0].pos) ) && 
      ( s0==0 || s1!=0 ||
        ! segments[s0].data[s1].pos.equals
          (segments[s0-1].data[segments[s0-1].data.length-1].pos) ) )
    return 1 ; 
  else return 0 ;
}
/* -------------------------------------------------------------------------- */
/*page*/
/* --------------------------------- walkto --------------------------------- */

// draw a selection point (and possibly an info box) at [s0,s1], bringing up
// a wpinfo window if flag = 0 or a selinfo window if flag = 1 

function walkto(s0,s1,flag) 
{ var datum = segments[s0].data[s1] , pos = datum.pos ; 
  if(flag==undefined) flag = 0  ;
  selected = [ s0,s1 ] ;
  if(overviewing) return highlight() ; 
  map.panToBounds(new google.maps.LatLngBounds(pos,pos)) ; 
  drawsel(0,[s0,s1]) ; 
  if(flag||(datum.type==null&&datum.photo.length==0)) 
  { if(flag==1) wpinfo() ; 
    else if(flag==2) seginfo() ; 
    else if(flag==3) highlight() ; 
    return ; 
  }
  infowindow.open(walktodiv(datum),pos,'walking') ; 
}
/* -------------------------------------------------------------------------- */
/*page*/
/* -------------------------- keystroke handler  ---------------------------- */

function keystroke(e)
{ var s0=selected[0],s1=selected[1],slast,flag ;

  if(e.keyCode==16) { shiftkey(1) ; return ; } 
  if(e.keyCode==40&&overviewing==0) 
  { map.panTo(segments[s0].data[s1].pos) ; return ; } 

  flag = infowindow.close() ;
  if(flag=='highlight') flag = 3 ; 
  else if(flag=='wpinfo') flag = 1 ; 
  else flag = 0 ; 

  if(e.keyCode==32) { selclick() ; return ; } // space
  if(overviewing) 
  { if(e.keyCode==70) enterfullscreen() ; 
    if(flag!=3||(e.keyCode!=39&&e.keyCode!=37&&e.keyCode!=8&&e.keyCode!=46)) 
      return ; 
  }
  if(e.keyCode==13) // return 
  { if(dragging) undraggit() ; else if(s0>=0) draggit(0) ; return ; } 
  if(dragging) return ; 

  if(e.keyCode==8||e.keyCode==46) // delete/backspace
  { e.preventDefault() ; 
    if(!e.shiftKey&&(segments.length>1||segments[0].data.length>1)) wpdel() ;
    else if(e.shiftKey&&binbtn.active) discard() ; 
    return ; 
  }
  if(e.keyCode==9) { e.preventDefault() ; inswp(1) ; return ; } // tab

  if(e.keyCode==39) // forwards
  { e.preventDefault() ;
    if(e.shiftKey) { s1 = 0 ; s0 += 1 ; if(s0==segments.length) s0 = 0 ; }
    else if(s1<segments[s0].data.length-1) s1 += 1 ; 
    else { s0 += 1 ; if(s0==segments.length) s0 = 0 ; s1 = 0 ; } 
  }
  else if(e.keyCode==37) // backwards 
  { e.preventDefault() ;  
    if(e.shiftKey) { s1 = 0 ; s0 -= 1 ; if(s0<0) s0 = segments.length-1 ; }
    else if(s1>0) s1 -= 1 ; 
    else 
    { s0 -= 1 ; 
      if(s0<0) s0 = segments.length-1 ; 
      s1 = segments[s0].data.length-1 ; 
    } 
  }
  else return ; 
  walkto(s0,s1,e.shiftKey?2:flag) ;
}
/* -------------------------- shift key handler  ---------------------------- */

function shiftkey(val)
{ shifted = val ; 
  if(mouseopt==1&&map!=null) 
    map.setOptions({draggableCursor:val?'crosshair':'default'}) ;
  if(val==0&&overviewing==0) getalts(200) ;
}
/* --------------------- undraw & redraw segments  -------------------------- */

function undraw(i) 
{ segments[i].route.setMap(null) ; 
  if(segments[i].clickhandler!=null) 
  { google.maps.event.removeListener(segments[i].clickhandler) ;
    segments[i].clickhandler = null ; 
  }
}
function redraw(i) { undraw(i) ; draw(i) ; }

function recolour(i) 
{ if(overviewing) return ; 
  else if(i&1) segments[i].route.setOptions({strokeColor:"#ff9999"}) ; 
  else segments[i].route.setOptions({strokeColor:"#ff0000"}) ; 
}
function obliterate(s0) // undraw route and all labels
{ var i ;
  for(i=0;i<segments[s0].data.length;i++) segments[s0].data[i].setmap(null) ; 
  undraw(s0) ; 
  disconnect(s0-1) ; 
  disconnect(s0) ; 
}
/* ----------------------------- draw segments ------------------------------ */

function draw(i,width)
{ var colour,poly ; 
  if(overviewing) colour = segments[i].colour ; 
  else if(i&1) colour = "#ff9999" ; 
  else colour = "#ff0000" ; 
  if(width==undefined) poly = new linepath(i,-1,0,colour) ;
  else poly = new linepath(i,-1,0,colour,width) ;
  segments[i].route = new google.maps.Polyline(poly) ;
  segments[i].route.setMap(map) ;
  if(segments[i].clickhandler==null)
    segments[i].clickhandler = 
      google.maps.event.addListener(segments[i].route,"click",selpoint) ;
}
/* ----------------------- connect and disconnect segments ------------------ */

function disconnect(i) 
{ if(overviewing||i<0||i>=segments.length-1||segments[i].dots==null) return ;
  segments[i].dots.setMap(null) ; 
  if(segments[i].dothandler!=null) 
  { google.maps.event.removeListener(segments[i].dothandler) ;
    segments[i].dothandler = null ; 
  }
}
function reconnect(i) { disconnect(i) ; connect(i) ; }

function connect(i)
{ if(overviewing||i<0||i>=segments.length-1) return ; 
  var opos = segments[i].data[segments[i].data.length-1].pos ; 
  var npos = segments[i+1].data[0].pos ;
  if(opos.equals(npos)) return ;
  segments[i].dots = new google.maps.Polyline(new dotpath(opos,npos)) ;
  segments[i].dots.setMap(map) ;
  segments[i].dothandler = 
    google.maps.event.addListener(segments[i].dots,"click",selpoint) ;
}
function redrawconnect(s0,s1) 
{ redraw(s0) ; 
  if(s1==0) reconnect(s0-1) ; 
  if(s1=segments[s0].data.length-1) reconnect(s0) ; 
}
/* ---------------------- draw the selection point -------------------------- */

// note: there's no point in allowing clicking on a marker because the 
// event position is always the marker position rather than the click position

function drawsel(opt,selection)
{ if(selection!=undefined) selected = selection ;
  var ind,clen,s0=selected[0],s1=selected[1],pos=segments[s0].data[s1].pos ;
  if(opt) reprofile() ; 
  clen = segments[s0].data.length ; 
  if(clen==1) arrow.rotation = 90 ; 
  else
  { if(s1==clen-1) ind = s1-1 ; else ind = s1 ;
    icons.arrow.rotation = 
      bearing(segments[s0].data[ind].pos,segments[s0].data[ind+1].pos) ;
  }

  if(sel.marker==null) sel.marker = new google.maps.Marker
    ({ position:pos, map:map, cursor:'default', icon:icons.arrow , zIndex:2 }) ;
  else // avoid unnecessary redraws
  { if(icons.arrow.rotation!=sel.orientation) sel.marker.setIcon(icons.arrow) ;
    if(!pos.equals(sel.marker.getPosition())) sel.marker.setPosition(pos) ; 
  }
  sel.orientation = icons.arrow.rotation ; 
  drawxcur(pro,selected) ;

  blackout(penbtn) ;
  if(segments.length>1&&unambig()) blackout(binbtn) ; else greyout(binbtn) ;  
  if(s1!=0&&s1!=clen-1) blackout(scissorsbtn) ; else greyout(scissorsbtn) ;
}
/* ------------- selclick: respond to click of cursor button  --------------- */

function selclick()
{ mouseopt = 1-mouseopt ; 
  infowindow.close() ;  
  if(mouseopt)
  { map.setOptions({draggable:false, draggableCursor:'default'}) ;
    if(overviewing==0) cursorbtn.btn.setAttribute('src',resuri+'hand.png') ; 
    clickhandle = google.maps.event.addListener(map,"click",selpoint) ;
  }
  else
  { map.setOptions({draggable:true, draggableCursor:''}) ;
    if(overviewing==0) cursorbtn.btn.setAttribute('src',resuri+'arrow.png') ; 
    google.maps.event.removeListener(clickhandle) ;
  }
}
/* -------------------------------------------------------------------------- */

function genhead(uri,key)
{ if(uri==undefined||uri==null)  
    resuri = 'http://www.masterlyinactivity.com/routemaster/resources/' ;
  else
  { resuri = uri + '/' ;
    document.write('<script src="http://maps.google.com/maps/api/js?' +
                    ((key==null||key==undefined)?'':('key='+key+'&')) +
                   'libraries=geometry"></scr' + 'ipt>') ;
  }
  document.write
  ('<script src="' + resuri + 'dms.js"></scr' + 'ipt>' +
   '<script src="' + resuri + 'vector3d.js"></scr' + 'ipt>' +
   '<script src="' + resuri + 'latlon-ellipsoidal.js"></scr' + 'ipt>' +
   '<script src="' + resuri + 'utm.js"></scr' + 'ipt>' +
   '<script src="' + resuri + 'osgridref.js"></scr' + 'ipt>' +
   '<script src="' + resuri + 'FileSaver.js"></scr' + 'ipt>' +
   '<script src="' + resuri + 'Blob.js"></scr' + 'ipt>' +
   '<script src="' + resuri + 'tcxlib.js"></scr' + 'ipt>' +
   '<script src="' + resuri + 'routemasterlib.js"></scr' + 'ipt>' +
   '<script src="http://www.masterlyinactivity.com/pixlib.js"></scr' + 'ipt>' +
   '<style type="text/css">html, body {width: 100%; height: 100%}' +
       'body {margin:0px}a:link{color:#66aaaa}' + 
       'a:visited{color:#cc3388}a:active{color:#404040}' +
       '</style><title>Routemaster</title>' + 
   '<link rel="shortcut icon" href=' + resuri + 'bus.gif>' ) ;
}
/* -------------------------------------------------------------------------- */
/*page*/
/* -------------------------------------------------------------------------- */
/*                FUNCTIONS TO GENERATE THE INITIAL MAP                       */
/* -------------------------------------------------------------------------- */

function genpage() 
{ var thispage=document.URL,xhttp,quotind,plusind,listxhttp,div,x ;
  imginfo = new listinfo() ; 
  imgdiv = null ; 

  window.onload = function() 
  { window.addEventListener("beforeunload",function(e) 
    { var msg = unsavedmsg(0) ; 
      if(msg==null) return undefined ; 
      (e || window.event).returnValue = msg ; //Gecko + IE
      return msg ; //Gecko + Webkit, Safari, Chrome etc. (msg is ignored by ffx)
    } ) ;
  } ;
  window.onfocus = function() { shiftkey(0) ; } ; 

  body = document.getElementsByTagName("body")[0] ;
  while(body.childNodes.length>0) 
    body.removeChild(body.childNodes[body.childNodes.length-1]) ;
  mapdiv = document.createElement('div') ; 
  mapdiv.setAttribute('id','map') ; 
  mapdiv.setAttribute('style','width:100%;height:100%;position:absolute') ; 
  body.appendChild(mapdiv) ;

  if((quotind=thispage.indexOf('?'))>=0)
  { thispage = thispage.substring(quotind+1) ; 
    plusind = thispage.indexOf('+'); 
    if(plusind>0) 
    { getlist(thispage.substring(plusind+1),'uri') ; 
      thispage = thispage.substring(0,plusind) ;
    }
    if(plusind>0||thispage.substring(thispage.length-3)!='.js')
    { xhttp = new XMLHttpRequest() ;
      xhttp.onreadystatechange = function() 
      { if(xhttp.readyState==4) 
        { if(xhttp.status==200)
          { x = parser.parseFromString(xhttp.responseText,"application/xml") ;
            render(x,thispage,0,'uri') ; 
          }
          else alert("Unable to read "+thispage+": error code "+xhttp.status) ;
        }
      }
      xhttp.open("GET",thispage,true) ;
      xhttp.send() ;
      return ;
    }
    else getlist(thispage,'uri') ; 
  }

  // come here if we need to browse the filesystem for input route
  mapdiv.appendChild(filedialogue(0)) ; 
  div = blurbdiv(resuri) ;
  div.setAttribute('style','font-family:helvetica;margin:4px;'+
                           'border-top:solid 1px silver;padding-top:2px') ; 
  mapdiv.appendChild(div) ; 
  div = helpdiv(resuri,1) ; 
  div.setAttribute('style','font-family:helvetica;margin:4px;font-size:90%') ;
  mapdiv.appendChild(div) ; 
}
/* -------------------------------------------------------------------------- */
/*page*/
/* -------------------------------- getlist --------------------------------- */

function getlist(uri,imgtype) 
{ var xhttp = new XMLHttpRequest(),list=null,sizes=null,pixpage=null,i,r ; 
  var imagedir=null,thumbshape = [] ;
  imginfo.status = 'waiting' ; 
  imginfo.type = imgtype ; 

  xhttp.onreadystatechange = function() 
  { if(xhttp.readyState==4)
    { if(xhttp.status!=200)
      { alert("Unable to read "+uri+": error code "+xhttp.status) ; return ; }
      eval(xhttp.responseText) ; 
      imginfo.uri = uri ; 
      imginfo.list = list ; 
      imginfo.sizes = sizes ; 
      imginfo.pixpage = reluri(uri,pixpage) ; 
      imginfo.thumbind = thumbind(sizes) ;
      setthumbshape(list,sizes,thumbshape,reluri(uri,imagedir)) ;
      for(i=0;i<list.length;i++) if(list[i].retpage!=undefined)
        list[i].retpage = reluri(uri,list[i].retpage) ;
      imginfo.status = 'ready' ; 
    }
  }
  xhttp.open("GET",uri,true) ;
  xhttp.send() ;
}
/* ----------------------------- file dialogue ------------------------------ */

function filedialogue(overwrite)
{ var input = document.createElement('input') ; 
  var para = document.createElement('p') ; 
  para.appendChild(document.createTextNode
                             ('\u00a0\u00a0\u00a0Select TCX/GPX file: ')) ; 
  input.setAttribute('type','file') ; 
  input.setAttribute('accept','.tcx,.gpx') ; 
  input.addEventListener('change',function(e)
  { reader = new FileReader() ;
    reader.onload = function(e) 
    { var xmldoc = parser.parseFromString(reader.result,"application/xml") ;
      render(xmldoc,input.files[0].name,overwrite,'file') ; 
    } 
    reader.readAsText(input.files[0]) ;	
  } ) ;
  para.appendChild(input) ; 
  return para ;
}
/* -------------------------------------------------------------------------- */
/*page*/
/* --------------------- set up the map and buttons ------------------------- */

function render(xmldoc,filename,overwrite,origin) 
{ var i,opts,centre,lat,lon,minlon,maxlon,minlat,maxlat,newseg,s0,d ;
  var colours,segno,istcx ; 

  infowindow.close() ;
  document.onkeydown = keystroke ;
  document.onkeyup = function(e) { if(e.keyCode==16) shiftkey(0) ; } ;
  xmlfile = filename ;

  // read data
  i = filename.length ; 
  if(filename.substring(i-4,i).toLowerCase()=='.tcx') istcx = 1 ; 
  else if(filename.substring(i-4,i).toLowerCase()=='.gpx') istcx = 0 ; 
  else { alert(filename+' is neither .tcx nor .gcx') ; throw '' ; }

  if(istcx) newseg = readtcx(xmldoc) ; else newseg = readgpx(xmldoc) ;
  if(newseg.segments.length==0||newseg.segments[0].length==0) 
  { alert('no data returned') ; return ; }
  if(newseg.segments.length>1&&setbtn!=null)
  { alert('trying to add a multitrack index... not permitted') ; return ; } 

  // check and process photo list
  if(imginfo.type!='uri') for(i=0;i<newseg.segments.length;i++)
    if(newseg.props[i].list!=null)
  { if(imginfo.uri!=null&&newseg.props[i].list!=imginfo.uri)
    { alert('inconsistent photo lists: ' + imginfo.uri + ' and ' + 
            newseg.props[i].list) ; 
      return ; 
    }
    imginfo = new listinfo() ; 
    getlist(newseg.props[i].list,'tcx') ; 
  }

  if(overwrite)
  { for(i=0;i<segments.length;i++) obliterate(i) ; 
    unprofile() ; 
    if(sel.marker!=null) sel.marker.setMap(null) ;
    segments = [] ; 
    if(imginfo.type=='tcx') imginfo = new listinfo() ; 
  } 

  s0 = segments.length ; 
  if(s0==0)
  { sel = { marker:null, orientation: null } ; 
    pending = [] ; 
    xpending = [] ; 
    actions = [] ; 
    unsavedchanges = [] ; 
    nactions = dragging = 0 ; 
    pro = starsdiv = null ; 
    routeprops = new propstype() ;
  }

  if(newseg.segments.length>1) 
  { overviewing = 1 ; 
    colours = gencolours(newseg.segments.length) ; 
    routeprops.stars = -1 ; 
  } 
  if(istcx&&routeprops.title==null&&newseg.title!=null) settitle(newseg.title) ;

  // process the new segments
  for(i=0;i<newseg.segments.length;i++)
  { newseg.props[i].source = [ filename , origin ] ; 
    if(routeprops.stars==null) routeprops.stars = newseg.props[i].stars ;
    if(routeprops.overview==null) 
      routeprops.overview = newseg.props[i].overview ;
    if(routeprops.title==null||routeprops.title=='Untitled Route')
      if(newseg.props[i].title!=null) settitle(newseg.props[i].title) ; 
    segments.push(new genseg(newseg.segments[i],newseg.props[i])) ; 
    if(overviewing) segments[segments.length-1].colour = colours[i] ;
    actions[nactions++] = 
      [ 'load',s0+i,newseg.segments[i].slice(),newseg.props[i] ] ;
  }
  if(routeprops.title==null) settitle('Untitled Route') ; 

  if(!newseg.props[0].optim.already&&!overviewing) 
    optimaction(segments.length-1,defparms,0) ; 
  if(overviewing) 
  { for(d='',i=0;i<newseg.segments.length;i++) if(newseg.props[i].title!=null)
    { if(d!='') d += ' | ' ; d += newseg.props[i].title ; }
    if(d!=null) routeprops.desc = d ; 
  }
  else { routeprops.desc = newseg.props[0].desc ; getalts(100) ; }
  if(routeprops.desc!=null) setdesc(routeprops.desc) ;

  // find max and min lat and long - have to look at all segs, not just
  // newly loaded to avoid google's repeatedly adding a margin 
  for(maxlat=null,segno=0;segno<segments.length;segno++)
    for(i=0;i<segments[segno].data.length;i++)
  { lat = segments[segno].data[i].pos.lat() ; 
    lon = segments[segno].data[i].pos.lng() ; 
    if(maxlat==null||lon<minlon) minlon = lon ; 
    if(maxlat==null||lon>maxlon) maxlon = lon ; 
    if(maxlat==null||lat<minlat) minlat = lat ; 
    if(maxlat==null||lat>maxlat) maxlat = lat ; 
  }

  if(s0==0)
    centre = new google.maps.LatLng((minlat+maxlat)/2,(minlon+maxlon)/2) ;

  if(map==null) // all this only done on first call
  { opts = { zoom: 22,
             center: centre,
             scaleControl: true,
             rotateControl: false,
             streetViewControl: false,
             mapTypeId: overviewing?
                google.maps.MapTypeId.ROADMAP:google.maps.MapTypeId.TERRAIN,
             disableDoubleClickZoom: true,
             styles: [ { "featureType": "poi", 
                         "stylers": [{ "visibility": "off" }]
                        } ],
             mapTypeControl:true,
             mapTypeControlOptions: 
               { style:google.maps.MapTypeControlStyle.HORIZONTAL_BAR }, 
             mapTypeIds: [ google.maps.MapTypeId.ROADMAP,
                           google.maps.MapTypeId.TERRAIN,
                           google.maps.MapTypeId.SATELLITE
                         ]
           } ;

    map = new google.maps.Map(mapdiv,opts) ;

    // set up buttons
    if(overviewing==0)
    { setbtn = genbutton('settings') ;
      cursorbtn = genbutton('cursor') ;
      cursorbtn.ui.addEventListener('click',selclick) ;
      scissorsbtn = genbutton('scissors') ;
      binbtn = genbutton('bin') ;
      penbtn = genbutton('pen') ;
      undobtn = genbutton('undo') ;
      redobtn = genbutton('redo') ;
      dlbtn = genbutton('dl') ;
    }
    selclick() ;
  }

  map.fitBounds(new google.maps.LatLngBounds
                             (new google.maps.LatLng(minlat,minlon),
                              new google.maps.LatLng(maxlat,maxlon))) ; 

  for(segno=s0;segno<segments.length;segno++)
    for(i=0;i<segments[segno].data.length;i++) 
      segments[segno].data[i].setmap(map) ;

  if(nactions>1) donesomething() ; // specifically, done loading & optimisation
  else actions.length = nactions ; // load with no optimisation hence no undo

  if(s0==0) { selected = [0,0] ; if(overviewing==0) drawsel(1) ; } 
  else greyout(dlbtn) ; 
  for(segno=s0;segno<segments.length;segno++)
  { draw(segno) ; connect(segno-1) ; }
  connect(segments.length-1) ; 
  reprofile() ; 
}
/* ------------------------------- settitle --------------------------------- */

function settitle(newtitle) 
{ routeprops.title = newtitle ; 
  var h = document.getElementsByTagName('title') ;
  if(h.length==0) h = document.createElement('title') ;
  else { h = h[0] ; while(h.firstChild) h.removeChild(h.firstChild) ; }
  h.appendChild(document.createTextNode(newtitle)) ;
}
/* -------------------------------------------------------------------------- */
/*page*/
/* -------------------------------- setdesc --------------------------------- */

function setdesc(newdesc) 
{ routeprops.desc = newdesc ; 
  var i , d='' , m = document.getElementsByTagName('meta') ;
  if(overviewing==0&&routeprops.stars!=null) for(i=0;i<=routeprops.stars;i++)
  { if(i<routeprops.stars) d += '\u2605' ; else d += ' | ' ; }
  for(i=0;i<m.length&&m[i].getAttribute('name')!='description';i++) ;
  if(i<m) { m[i].setAttribute('content',newdesc) ; return ; }
  m = document.createElement('meta') ;
  m.setAttribute('name','description') ; 
  m.setAttribute('content',d+newdesc) ; 
  document.getElementsByTagName('head')[0].appendChild(m) ;
}
/* ------------------------------- retitle ---------------------------------- */

function retitle(opt) 
{ var newval , msg , field = (opt=='title'?'title':'desc') ; 
  var oldval = routeprops[field] ;
  infowindow.close() ; 
  if(oldval==null) msg = 'Add ' + opt ; else msg = 'Modify ' + opt ;
  if(opt=='title') msg += ' (max 15 chars):' ; else msg += ':' ;
  newval = window.prompt(msg,oldval) ;
  if(newval==null) return ; 
  if(opt=='title') newval = newval.substring(0,15) ; 
  if(newval==oldval) return ; 
  if(opt=='title') settitle(newval) ; else setdesc(newval) ; 
  actions[nactions++] = [ 'edit'+opt , oldval , newval ] ; 
  donesomething() ;
  routeinfo() ; 
}
/* ------------------------------- restars ---------------------------------- */

function restars(oldstars,newstars) 
{ actions[nactions++] = [ 'stars' , oldstars , newstars ] ; 
  donesomething() ;
  starsline(routeprops.stars=newstars,1) ; 
}
/* ------------------------------- genbutton -------------------------------- */

function genbutton(name)
{ var u,v,w,b,g,k,h,div=document.createElement('div'),act ;
  u = document.createElement('div') ;
  u.style.backgroundColor = '#ffffff' ;
  u.style.border = '2px solid #ffffff' ;
  u.style.borderRadius = '3px' ;
  u.style.boxShadow = '0 2px 6px rgba(0,0,0,.3)' ;
  if(name=='dl'||name=='settings'||name=='cursor') u.style.cursor = 'pointer' ;
  else u.style.cursor = 'default' ;
  u.style.marginBottom = '12px' ;
  if(name!='dl') u.style.marginRight = '4px' ;
  u.style.textAlign = 'center' ;
  div.appendChild(u) ;

  if(name=='scissors') { h = snip ; div.index = 3 ; } 
  else if(name=='bin') { h = discard ; div.index = 4 ; }
  else if(name=='pen') { h = labelprompt ; div.index = 5 ; }
  else if(name=='undo') { h = undo ; div.index = 6 ; }
  else if(name=='redo') { h = redo ; div.index = 7 ; }
  else if(name=='dl') { h = function() { dl(0) ; }  ; div.index = 8 ; }
  else if(name=='settings') { h = popup ; div.index = 1 ; }
  else if(name=='cursor') { h = null ; div.index = 2 ; }
  g = greybtn(resuri,name) ;
  k = blackbtn(resuri,name) ;
  if(name=='dl'||name=='settings'||name=='cursor') b = buttonimg(k) ;
  else b = buttonimg(g) ;
  u.appendChild(b) ;

  map.controls[google.maps.ControlPosition.BOTTOM_CENTER].push(div) ;

  if(name=='dl'||name=='settings') u.addEventListener('click',h) ; 
  if(name=='dl'||name=='settings') act = 1 ; else act = 0 ; 

  return { ui:u , btn:b , active:act , greyimg:g , blackimg:k , handler:h } ; 
}
/* -------------------------------------------------------------------------- */
/*page*/
/* -------------------------------------------------------------------------- */
/*                             OPTIMISATION                                   */
/* -------------------------------------------------------------------------- */

function optimaction(segno,parms,force)
{ var s = segments[segno], result = optimise(s.data,parms) , loadno ; 
  var ndel = s.data.length - result.length ; 
  for(loadno=nactions-1;loadno>=0&&actions[loadno][0]!='load';loadno--) ; 
  if(loadno<0||(force==0&&ndel<s.data.length/2)||ndel==0) return 0 ; 
  actions[loadno][3].optim.ndel = ndel ; 
  actions[nactions++] = [ 'optimise' , segno , parms ] ; 
  segments[segno] = new genseg(result,segments[segno].props) ;
  actions[loadno][3].optim.parms = { tol: parms.tol , 
                                     maxsep: parms.maxsep , 
                                     wppenalty: parms.wppenalty , 
                                     vweight: parms.vweight 
                                   } ; 
  return 1 ; 
}
/* -------------------------------------------------------------------------- */

function optimprompt()
{ var msg = 'Enter 4 optimisation parms (tol, maxsep, wppenalty and vweight)' ; 
  var parmstr = defparms.tol + ' ' + 
                defparms.maxsep.toFixed(0) + ' ' +
                defparms.wppenalty.toFixed(0) + ' ' +
                defparms.vweight.toFixed(1) ;
  var parms,i ; 
  infowindow.close() ;  

  for(i=0;;i++)
  { newparms = prompt(msg,parmstr) ; 
    if(newparms==null) return ;
    if(newparms=='') { parms = defparms ; break ; }
    newparms = newparms.split(' ') ;
    if(newparms.length==0) { parms = defparms ; break ; }
    parms = { tol: parseFloat(newparms[0]) , 
              maxsep: parseFloat(newparms[1]) , 
              wppenalty: parseFloat(newparms[2]) , 
              vweight: parseFloat(newparms[3]) } ; 
    if( isNaN(parms.tol)==0 && isNaN(parms.maxsep)==0 && 
        isNaN(parms.wppenalty)==0 && isNaN(parms.vweight)==0 ) break ; 
    if(i==0) msg = '*** Illegal parms ***\n' + msg ; 
  }
  if(optimaction(segments.length-1,parms,1)) 
  { donesomething() ; draw(segments.length-1) ; } 
  routeinfo() ; 
}  
/* -------------------------------------------------------------------------- */
/*page*/
/* -------------------------------------------------------------------------- */

function popup()
{ infowindow.close() ;
  var d=cogwheelmenu(dragging,pro!=null&&pro.prodiv!=null,routeprops.overview) ;
  infowindow.open(d,getbtnpos(0),'settings') ; 
}
/* ------------------------------- calwork --------------------------------- */

function calwork(s0,y)
{ var i,s1 ; 
  for(s1=0;s1<segments[s0].data.length;s1++)
    if(segments[s0].data[s1].h!=null) segments[s0].data[s1].h += y ; 
  reprofile() ; 
}
/* ------------------------------ manualcal --------------------------------- */

function manualcal()
{ infowindow.close() ; 
  var x,y,s0=selected[0] ;
  x = prompt('Enter offset in metres to add to altidudes:') ;
  if(x==null) return ; 
  y = parseFloat(x) ; 
  if(isNaN(y)) { alert(x+' is not a number') ; return ; }
  calwork(s0,y) ; 
  done(['recal',s0,y]) ; 
}  
/* --------------------------------- help ----------------------------------- */

function help() 
{ infowindow.close() ; infowindow.open(helpdiv(resuri),getbtnpos(0),'help') ; }

/* --------------------------------- wpdel ---------------------------------- */

function wpdelwork(s0,s1)
{ var i,response=segments[s0].data[s1],clen=segments[s0].data.length ;
  response.setmap(null) ;
  for(i=s1;i<clen-1;i++) segments[s0].data[i] = segments[s0].data[i+1] ;
  segments[s0].data.length = clen-1 ; 
  selected = [s0,s1] ; 
  if(s1==segments[s0].data.length) selected[1] -= 1 ;  
  redrawconnect(s0,s1) ; 
  drawsel(1) ; 
  return response ;
}
function wpdel()
{ var s0=selected[0],s1=selected[1],i ;
  var flag = infowindow.close() ; 
  done(['wpdel',s0,s1,wpdelwork(s0,s1)]) ; 
  if(flag=='wpinfo') wpinfo() ; 
}
/* --------------------------------- revseg --------------------------------- */

function revsegwork(s0)
{ var i,s=segments[s0],j,x,len=s.data.length ;
  disconnect(s0-1) ; disconnect(s0) ; 
  for(i=0;i<len/2;i++)
  { j = (len-1) - i ; 
    x = s.data[i] ; s.data[i] = s.data[j] ; s.data[j] = x ; 
  }
  for(i=0;i<s.data.length;i++) 
    if(s.data[i].type=='Right') s.data[i].settype('Left') ;
    else if(s.data[i].type=='Left') s.data[i].settype('Right') ;

  if(s0==selected[0]) selected[1] = (len-1) - selected[1] ; 
  connect(s0-1) ; connect(s0) ; 
  drawsel(1) ; 
}
/* -------------------------------------------------------------------------- */

function revseg()
{ infowindow.close() ; 
  revsegwork(selected[0]) ; 
  done(['revseg',selected[0]]) ; 
}
/* -------------------------------------------------------------------------- */

function addload(overwrite)
{ var msg ; 
  infowindow.close() ; 
  if(overwrite) 
  { msg = unsavedmsg(1) ; if(msg!=null) if(!confirm(msg)) return ; }
  infowindow.open(filedialogue(overwrite),getbtnpos(0),'addload') ; 
}
/* -------------------------------------------------------------------------- */
/*page*/
/* -------------------------------------------------------------------------- */
/*    DRAGGING A (POSSIBLY NEWLY INSERTED) WAYPOINT IS QUITE A LOT OF WORK    */
/* -------------------------------------------------------------------------- */

function insert(s0,s1,n)
{ var i ;
  for(i=segments[s0].data.length+n-1;i>s1;i--)
    segments[s0].data[i] = segments[s0].data[i-n] ;
  for(i=0;i<n;i++) segments[s0].data[s1+i] = new datatype(null,null) ;  
}
/* --------------------------------- inswp ---------------------------------- */

function inswp(dir)
{ var s0=selected[0],s1=selected[1],bounds,del,pos,data=segments[s0].data ;
  var len = data.length ;
  if(len==1) pos = data[0].pos ;

  if(dir>=0) s1 = selected[1] += 1 ; 
  insert(s0,s1,1) ;  
  if(len==1)
  { bounds = map.getBounds() ;
    del = bounds.getNorthEast().lng() - bounds.getSouthWest().lng() ; 
    pos = new google.maps.LatLng(pos.lat(),pos.lng()+dir*del/10) ; 
  }
  else if(s1==0) pos = interp(data[2].pos,data[1].pos,1.5) ; 
  else if(s1<len) pos = interp(data[s1-1].pos,data[s1+1].pos,0.5) ;
  else pos = interp(data[s1-2].pos,data[s1-1].pos,1.5) ;
  data[s1].setpos(pos) ; 
  draggit(1) ; 
}
/* -------------------------------- draggit --------------------------------- */

// draggit makes the current waypoint draggable

var l1,l2,startpos,seg0,seg1,seg2,colour,inserted ; 

function draggit(insparm)
{ var s0=selected[0],s1=selected[1],start,end,i,len=segments[s0].data.length ;
  startpos = segments[s0].data[s1].pos ;
  inserted = insparm ; 
  infowindow.close() ; 
  greyout(scissorsbtn) ;
  greyout(binbtn) ;
  greyout(penbtn) ;
  greyout(undobtn) ;
  greyout(redobtn) ;
  greyout(dlbtn) ;
  map.panToBounds(new google.maps.LatLngBounds(startpos,startpos)) ;

  sel.marker.setMap(null) ; 
  sel.marker = new google.maps.Marker(
                  { position: segments[s0].data[s1].pos,
                    map: map,
                    cursor: 'default',
                    icon: icons.concircle ,
                    draggable: true ,
                    zIndex: 2
                  } ) ;

  if(s0&1) colour = "#ff9999" ; else colour = "#ff0000" ; 
  segments[s0].route.setMap(null) ;
  if(segments[s0].clickhandler!=null) 
  { google.maps.event.removeListener(segments[s0].clickhandler) ;
    segments[s0].clickhandler = null ; 
  }

  seg0 = seg2 = null; 
  if(s1>1)
  { seg0 = new google.maps.Polyline(new linepath(s0,0,s1,colour)) ;
    seg0.setMap(map) ;
  }

  if(s1==0) start = 0 ; else start = s1-1 ; 
  if(s1==len-1) end = s1+1 ; else end = s1+2 ; 
  seg1 = new google.maps.Polyline(new linepath(s0,start,end,colour)) ;
  seg1.setMap(map) ;

  if(s1<segments[s0].data.length-2)
  { seg2 = new google.maps.Polyline(new linepath(s0,s1+1,len,colour)) ;
    seg2.setMap(map) ;
  }

  l1 = google.maps.event.addListener(sel.marker,'drag',function()
  { segments[s0].data[s1].setpos(this.getPosition()) ; 
    seg1.setMap(null) ;
    seg1 = new google.maps.Polyline(new linepath(s0,start,end,colour)) ;
    seg1.setMap(map) ;
    if(s1==0&&s0>0) { disconnect(s0-1) ; connect(s0-1) ; }
    if(s1==len-1&&s0<segments.length-1) { disconnect(s0) ; connect(s0) ; }
  } ) ;
 
  dragging = 1 ; 
}  
/* ------------------------------- undraggit -------------------------------- */

// undraggit is invoked by [return] to terminate waypoint dragging

function undraggit()
{ var s0=selected[0],s1=selected[1],i,s1dash,pos=segments[s0].data[s1].pos ; 
  var xpos ; 
  google.maps.event.removeListener(l1) ;
  dragging = 0 ; 
  if(seg0!=null) seg0.setMap(null) ;
  seg1.setMap(null) ;
  if(seg2!=null) seg2.setMap(null) ;
  segments[s0].route = new google.maps.Polyline(new linepath(s0,-1,0,colour)) ;
  segments[s0].route.setMap(map) ;
  segments[s0].data[s1].h = null ;
  getalts(100) ; 

  sel.marker.setMap(null) ; 
  sel.marker = null ; // force a redraw
  drawsel(1) ; 
  if(inserted||dist(startpos,pos)>5) 
    done(['move',s0,s1,startpos,pos,inserted]) ; 
  if(segments.length==1) blackout(dlbtn) ; 
}
/* -------------------------------------------------------------------------- */

function seginfo()
{ var pos = segments[selected[0]].data[selected[1]].pos ;
  infowindow.close() ;
  infowindow.open(seginfodiv(segments,selected[0]),pos,'seginfo') ; 
}
/* -------------------------------------------------------------------------- */

function deltimes()
{ var s0,s1,task=[] ;
  for(s0=0;s0<segments.length;s0++) for(s1=0;s1<segments[s0].data.length;s1++)
    if(segments[s0].data[s1].t!=null)
  { task.push([s0,s1,segments[s0].data[s1].t]) ; 
    segments[s0].data[s1].t = null ; 
  }
  infowindow.close() ;  
  done(['deltimes',task]) ;
}  
/* -------------------------------------------------------------------------- */
/*page*/
/* ------------------------- interpolate extra points ----------------------- */

function extrapts(opt)
{ var s0,s1,sep,data,n,opos,npos,i,lambda ;
  var task = [ 'extra' , selected[0] , selected[1] ] ;
  infowindow.close() ; 

  for(s0=0;s0<segments.length;s0++) 
    for(data=segments[s0].data,s1=1;s1<data.length;s1++)
      if((sep=dist(opos=data[s1-1].pos,npos=data[s1].pos))>100) 
  { n = Math.floor(sep/95) ;
    insert(s0,s1,n) ; 
    for(i=0;i<n;i++) 
    { lambda = (i+1) / (n+1) ; 
      data[s1+i].setpos(new google.maps.
                          LatLng(lambda*npos.lat()+(1-lambda)*opos.lat(),
                                 lambda*npos.lng()+(1-lambda)*opos.lng())) ;
    }
    if(selected[0]==s0&&s1<=selected[1]) selected[1] += n ; 
    task.push([s0,s1,data.slice(s1-1,s1+n+1)]) ; 
    s1 += n ;
  }

  getalts(1) ; 
  done(task) ; 
  routeinfo() ; 
}
/* -------------------------------------------------------------------------- */
/*                 FUNCTIONS FOR HANDLING THE ALTITUDE PROFILE                */
/* -------------------------------------------------------------------------- */

function drawprofile()
{ infowindow.close() ; 
  if((pro=procoords(segments))==null) return ; 
  drawpro(pro) ;
  body.appendChild(pro.prodiv) ; 
  body.appendChild(pro.curdiv) ;
  drawxcur(pro,selected) ;
} 
/* ------------------------------- unprofile -------------------------------- */

function unprofile()
{ var i,match,node ; 
  infowindow.close() ; 
  if(pro==null||pro.prodiv==null) return ; 
  pro.curdiv.removeEventListener('click',pro.curhandle) ;
  for(match=0,i=body.childNodes.length-1;match==0&&i>=0;i--)
  { node = body.childNodes[i] ; 
    match = (node==pro.prodiv) ; 
    body.removeChild(node) ; 
  }
  pro = null ; 
} 
function reprofile() 
{ if(pro!=null&&pro.prodiv!=null) { unprofile() ; drawprofile() ; } } 

/* -------------------------------------------------------------------------- */
/*       ROUTEINFO IS A MENU WHICH ITSELF SUPPORTS THE COMBINE FUNCTION       */
/* -------------------------------------------------------------------------- */

function routeinfo() 
{ infowindow.close() ; 
  infowindow.open(routediv(routeprops),getbtnpos(0),'routeinfo') ; 
}
/* -------------------------------------------------------------------------- */
/*page*/
/* ------------------------------- combine1 --------------------------------- */

function combine1(sa,sb)
{ var i,ca,cb,calen,cblen,la=null,lb=null,cdup=0 ; 
  undraw(sb) ; 
  disconnect(sb-1) ; 
  calen = segments[sa].data.length ;
  cblen = segments[sb].data.length ;
  cb = segments[sb].data[0].pos ; 
  cdup = ( ca = cb.equals(segments[sa].data[calen-1].pos) ) ; 
  if(cdup) 
  { la = segments[sa].data[calen-1] ; 
    lb = segments[sb].data[0] ; 
    segments[sa].data.length = ( calen -= 1 ) ;
  }

  if(selected[0]==sb) selected = [ sa , selected[1]+calen ] ; 
  segments[sa].data = segments[sa].data.concat(segments[sb].data) ; 

  return [ cblen , cdup , la , lb ] ; 
}
function combinework()
{ var task,s0 ;
  for(task=['combine',segments.length],s0=1;s0<segments.length;s0++) 
    task.push(combine1(0,s0)) ;
  segments.length = 1 ; 
  return task ; 
}
/* -------------------------------------------------------------------------- */

function combine()
{ infowindow.close() ; 
  done(combinework()) ; 
  drawsel(1) ; 
  redraw(0) ; 
  blackout(dlbtn) ; 
}  
function recombine()
{ var s0 ;
  for(s0=1;s0<segments.length;s0++) { undraw(s0) ; combine1(0,s0) ;}
  segments.length = 1 ; 
  drawsel(1) ;  
  redraw(0) ;
  blackout(dlbtn) ; 
}
/* -------------------------------------------------------------------------- */

//  combine returns [ cblen , cdup , la , lb ] ; 

function uncombine(task)
{ var i,j,llen,flag,subtask ; 

  for(flag=0,s0=task[1]-1,i=task.length-1;i>=2;i--,s0--)
  { subtask = task[i] ; 
    cblen = subtask[0] ; 
    cdup = subtask[1] ; 
    llen = segments[0].data.length ;
    segments[s0] = { data: segments[0].data.slice(llen-cblen,llen) , 
                     route: null , 
                     clickhandler: null
                   } ;
    llen = segments[0].data.length = llen+cdup-cblen ;
    if(cdup) 
    { segments[0].data[llen-1] = subtask[2] ; 
      segments[s0].data[0] = subtask[3] ; 
    }
    if(flag==0&&selected[1]>=llen)
    { selected = [ s0 , selected[1]-llen ] ; flag = 1 ; } 
  }
  drawsel(1) ; 
  undraw(0) ; 
  for(i=0;i<segments.length;i++) { draw(i) ; connect(i) ; } 
  greyout(dlbtn) ; 
}
/* -------------------------------------------------------------------------- */
/*page*/
/* -------------------------------------------------------------------------- */
/*           WPINFO IS A MENU GIVING ACCESS TO THE SETALT FUNCTION            */
/* -------------------------------------------------------------------------- */

function wpinfo() 
{ infowindow.close() ; 
  infowindow.open
    (wpinfodiv(),segments[selected[0]].data[selected[1]].pos,'wpinfo') ; 
}
/* -------------------------------- setalt ---------------------------------- */

function setalt(edit)
{ infowindow.close() ; 
  var s0=selected[0],s1=selected[1],x,y=null,oldalt ; 
  oldalt = segments[s0].data[s1].h.toFixed(0) ;
  if(edit) x = prompt('Enter altitude (m):',oldalt) ;
  else x = prompt('Enter altitude (m):') ;
  if(x==null) return ; 
  if(x!=''&&isNaN(y=parseFloat(x))) { alert(x+' is not a number') ; return ; }
  if(y==null&&oldalt==null) return ; 
  if(y!=null&&Math.abs(y-oldalt)<0.1) return ; 
  segments[s0].data[s1].h = y ; 
  done(['setalt',s0,s1,oldalt,y]) ; 
  reprofile() ; 
  wpinfo() ; 
}  
/* -------------------------------------------------------------------------- */
/*   THE LABELS ARE ACCESSED FROM THE PEN BUTTON OR BY CLICKING ON THE MAP    */
/* -------------------------------------------------------------------------- */

function labelprompt()
{ var oldcaption='',oldtype,type='Generic',s0=selected[0],s1=selected[1] ;
  var str , flag = (infowindow.close()=='wpinfo') ; 
  var datum = segments[s0].data[s1] ;
  
  oldtype = datum.type ; 
  if(oldtype!=null) oldcaption = datum.marker.title ; 
  if(oldcaption==null) oldcaption = '' ;
  if(oldtype!=null) type = oldtype ;
  if(oldtype==null) str = 'Enter' ; else str = 'Modify or delete' ; 
  var caption = window.prompt(str+' label:',oldcaption) ;

  if(caption==null) { if(flag) wpinfo() ; else walkto(s0,s1) ; return ; } 
  else if(caption=='') type = null ; 
  else caption = caption.substring(0,10) ;
  if(caption==oldcaption) 
  { if(flag) wpinfo() ; else walkto(s0,s1) ; return ; } 

  segments[s0].data[s1].setlabel(type,caption) ; 
  done(['editlabel',s0,s1,oldcaption,caption,oldtype,type]) ; 
  if(flag) wpinfo() ; else walkto(s0,s1) ; 
}
/* -------------------------------------------------------------------------- */
/*page*/
/* ------------------------------ labelcycle -------------------------------- */

function labelcycle()
{ var s0,s1,datum,types,caption,flag=(infowindow.close()=='wpinfo') ;  ;
  for(s0=0;s0<segments.length;s0++) for(s1=0;s1<segments[s0].data.length;s1++)
  { datum = segments[s0].data[s1] ;
    if(datum.marker!=this) continue ; 
    types = datum.labelcycle() ;
    caption = datum.marker.title ;
    selected = [s0,s1] ; 
    done(['editlabel',s0,s1,caption,caption,types[0],types[1]]) ; 
    if(flag) wpinfo() ; 
    return ;
  }
}
function photoprompt(e) 
{ var s0=selected[0],s1=selected[1] ;
  if(e!=null) e.preventDefault() ;
  var flag = (infowindow.close()=='wpinfo') ; 
  var datum = segments[s0].data[s1] ;
  var photo = window.prompt('Enter photo name:','') ;

  if(photo!=null&&photo!='') 
  { done(['editphoto',s0,s1,datum.photo.length,null,photo]) ; 
    datum.addphoto(photo) ; 
  }
  if(flag) wpinfo() ; else walkto(s0,s1) ; 
}
function photoedit(ind)
{ var s0=selected[0],s1=selected[1],i ;
  var flag = (infowindow.close()=='wpinfo') ; 
  var datum = segments[s0].data[s1] ;
  var photo = window.prompt('New photo name:',datum.photo[ind]) ;

  if(photo!=null&&photo!='') for(i=0;i<datum.photo.length;i++)
    if(datum.photo[i]==photo) { photo = null ; break ; }
  if(photo!=null)
  { if(photo=='') photo = null ;
    done(['editphoto',s0,s1,ind,datum.photo[ind],photo]) ; 
    datum.setphoto(ind,photo) ; 
  }
  if(flag) wpinfo() ; else walkto(s0,s1) ; 
}
/* -------------------------------------------------------------------------- */
/*page*/
/* ----------------------------- display photo ------------------------------ */

var lmove,rmove ; 

function advance(s0,s1,ind)
{ for(ind++;;ind++)
  { if(ind>=segments[s0].data[s1].photo.length) { ind = 0 ; s1 += 1 ; }
    if(s1==segments[s0].data.length) 
    { s0 += 1 ; if(s0==segments.length) return null ; s1 = 0 ; }
    if(ind<segments[s0].data[s1].photo.length) return [s0,s1,ind] ;
  }
}
function retreat(s0,s1,ind)
{ for(ind--;;ind--)
  { if(ind<0) { s1 -= 1 ; ind = null ; }
    if(s1<0)
    { if(s0==0) return null ; else s0 -= 1 ; 
      s1 = segments[s0].data.length-1 ; 
    }
    if(ind==null) ind = segments[s0].data[s1].photo.length - 1 ;
    if(ind>=0) return [s0,s1,ind] ;
  }
}
function prev() { dodisplay(lmove[0],lmove[1],lmove[2],-1) ; }
function backtogps() 
{ document.onkeydown = keystroke ; 
  window.removeEventListener('resize',resize) ; 
  body.removeChild(imgdiv) ; 
  walkto(selected[0],selected[1],0) ;
}
function next() { dodisplay(rmove[0],rmove[1],rmove[2],1) ; }

function display(ind)
{ document.onkeydown = imgwalk ;
  window.addEventListener('resize',resize) ; 
  infowindow.close() ; 
  imgdiv = document.createElement('div') ; 
  imgdiv.setAttribute('style','position:fixed;width:100%;height:100%;'+
                      'left:0;top:0;background:black') ;
  dodisplay(selected[0],selected[1],ind,1) ; 
  body.appendChild(imgdiv) ; 
}
function dodisplay(s0,s1,ind,dir)
{ var phind=findimg(segments[s0].data[s1].photo[ind]) , pre=null ;
  selected[0] = s0 ; 
  selected[1] = s1 ; 
  lmove = retreat(s0,s1,ind) ;
  rmove = advance(s0,s1,ind) ;
  if(dir<0&&lmove!=null)
    pre = findimg(segments[lmove[0]].data[lmove[1]].photo[lmove[2]]) ;
  else if(dir>=0&&rmove!=null)
    pre = findimg(segments[rmove[0]].data[rmove[1]].photo[rmove[2]]) ;
  if(pre!=null) pre = imginfo.list[pre] ;

  gendisplay(imgdiv,imginfo.list[findimg(segments[s0].data[s1].photo[ind])],
             imginfo.sizes,lmove==null?null:'javascript:prev()',
             'javascript:backtogps()',rmove==null?null:'javascript:next()',
             'GPS track',pre) ; 
}
/* -------------------------------------------------------------------------- */
/*page*/
/* ------------------------------ image walk -------------------------------- */

function imgwalk(e)
{ e.preventDefault() ; 

  if(e.keyCode==39) { if(rmove!=null) next() ; return ; }
  else if(e.keyCode==37) { if(lmove!=null) prev() ; return ; }
  else if(e.keyCode==40) reduce() ; 
  else if(e.keyCode==38) enlarge() ; 
  else if(e.keyCode==70) enterfullscreen() ; 
  else backtogps() ;
}
/* --------------------------------- photo info ----------------------------- */

function phinfo(i) 
{ infowindow.close() ; 
  var s0=selected[0],s1=selected[1] ; 
  infowindow.open(phdiv(i),segments[s0].data[s1].pos,'phinfo') ; 
}
/* ------------------------- snip: apply scissors  -------------------------- */

function snipwork(s0,s1)
{ var i,k,newlen ; 
  undraw(s0) ; 
  segments.length += 1 ; 
  for(i=segments.length-1;i>s0+1;i--) segments[i] = segments[i-1] ; 

  newlen = segments[s0].data.length - s1 ;
  segments[s0+1] =  new genseg(segments[s0].data.slice(s1),segments[s0].props) ;
  segments[s0+1].dots = segments[s0].dots ;
  segments[s0+1].dothandler = segments[s0].dothandler ;
  segments[s0].dots = segments[s0].dothandler = null ; 

  segments[s0].data.length = s1 + 1 ; 
  segments[s0].data[s1] = 
    new datatype(segments[s0].data[s1].pos,segments[s0].data[s1].h) ; 
  draw(s0) ;
  draw(s0+1) ; 
  for(i=s0+2;i<segments.length;i++) recolour(i) ;
  drawsel(1,[s0+1,0]) ; 
  greyout(dlbtn) ; 
}
function snip()
{ var i,s0=selected[0],s1=selected[1] ; 
  infowindow.close() ; 
  done(['snip',s0,s1]) ; 
  snipwork(s0,s1) ; 
}
/* ------------------------ discard: bin a segment  ------------------------- */

function binwork(s0)
{ var i ; 
  obliterate(s0) ; 
  for(i=s0;i<segments.length-1;i++) segments[i] = segments[i+1] ; 
  segments.length -= 1 ; 

  for(i=s0;i<segments.length;i++) recolour(i) ;
  connect(s0-1) ;

  selected[1] = 0 ; 
  if(selected[0]==segments.length) selected[0] = 0 ; 
  drawsel(1) ; 
  if(segments.length==1) blackout(dlbtn) ; 
}
function discard()
{ var i,s0=selected[0] ; 
  infowindow.close() ;  
  done(['bin',s0,segments[s0]]) ; 
  binwork(s0) ; 
}
/* -------------------------------------------------------------------------- */

function actiontype(x)
{ if( x=='snip'||x=='combine'||x=='interpolate'
   || x=='optimise'||x=='load' ) return 0 ; else return 1 ;
}
function done(something) 
{ if( nactions>0 && unsavedchanges.length>0 && something[0]=='editlabel'
   && actions[nactions-1][0]==something[0]
   && actions[nactions-1][1]==something[1] // don't merge change with delete
   && actions[nactions-1][2]==something[2] && something[6]!=null )
  { actions[nactions-1][4] = something[4] ; // caption
    actions[nactions-1][6] = something[6] ; // type
  }
  else { actions[nactions++] = something ; donesomething() ; }
}
function donesomething()
{ actions.length = nactions ; 
  blackout(undobtn) ; 
  greyout(redobtn) ; 
  if(actiontype(actions[nactions-1][0])!=0) 
  { if(unsavedchanges.length>=3) unsavedchanges.push(null) ; 
    else unsavedchanges.push(actionname(actions[nactions-1])) ;
  }
}
/* -------------------------------------------------------------------------- */
/*page*/
/* --------------------------------- undo  ---------------------------------- */

function undo()
{ infowindow.close() ;  
  var opts = 'Undo ' + actionname(actions[nactions-1]) ;
  infowindow.open(genclickable('confirmedundo()',opts),getbtnpos(5),'undo') ; 
}
function confirmedundo()
{ var i,ano=nactions-1,action=actions[ano][0],s0=actions[ano][1],s1,caption ;
  var oldcaption,task,ind ; 
  infowindow.close() ;  

  if(action!='revseg'&&action!='interpolate'&&action!='deltimes') 
    s1 = actions[ano][2] ;

  if(action=='bin') 
  { disconnect(s0-1) ; 
    for(i=segments.length;i>s0;i--) 
    { segments[i] = segments[i-1] ; recolour(i) ; } 
    segments[s0] = s1 ;
    for(s1=0;s1<segments[s0].data.length;s1++)
      segments[s0].data[s1].setmap(map) ; 
    draw(s0) ; 
    connect(s0-1) ; 
    connect(s0) ; 
    if(selected[0]>=s0) selected[0] += 1 ; 
    drawsel(1) ; 
    greyout(dlbtn) ; 
  }
  else if(action=='snip') // undo snip
  { selected = [ s0 , segments[s0].data.length-1 ] ; 
    combine1(s0,s0+1) ; 
    for(i=s0+1;i<segments.length-1;i++) 
    { segments[i] = segments[i+1] ; recolour(i) ; } 
    segments.length -= 1 ; 
    if(segments.length==1) blackout(dlbtn) ; 
    draw(s0) ;
    drawsel(1) ; 
  } 
  else if(action=='editlabel')  // undo create/edit/delete label
    segments[s0].data[s1].setlabel(actions[ano][5],actions[ano][3]) ;
  else if(action=='edittitle') settitle(s0) ; 
  else if(action=='editdescription') setdesc(s0) ; 
  else if(action=='wpdel')      // ['wpdel',s0,s1,wpdelwork(s0,s1)]
  { insert(s0,s1,1) ; 
    segments[s0].data[s1] = actions[ano][3] ;
    segments[s0].data[s1].setmap(map) ;
    redrawconnect(s0,s1) ;
    drawsel(1,[s0,s1]) ; 
  }
  else if(action=='move')
  { if(actions[ano][5]) wpdelwork(s0,s1) ; else move(s0,s1,actions[ano][3]) ; }
  else if(action=='recal') calwork(s0,-s1) ; 
  else if(action=='setalt') segments[s0].data[s1].h = actions[ano][3] ;
  else if(action=='combine') uncombine(actions[ano]) ; 
  else if(action=='revseg') revsegwork(s0) ; 
  else if(action=='stars') routeprops.stars = s0 ; 
  else if(action=='deltimes') for(i=0;i<s0.length;i++) 
    segments[s0[i][0]].data[s0[i][1]].t = s0[i][2] ;
  else if(action=='optimise') // [ 'load' , s0 , data.slice() , props ]
  { for(ano--;ano>=0&&actions[ano][0]!='load';ano--) ;
    segments[s0].data = actions[ano][2] ;
    actions[loadno][3].optim.ndel = 0 ; 
    redraw(s0) ;
    drawsel(1,[s0,0]) ; 
  }
  else if(action=='editphoto') 
  { ind = actions[ano][3] ;
    if(actions[ano][5]==null)      // undo delete
      for(i=segments[s0].data[s1].photo.length;i>ind;i--)
        segments[s0].data[s1].photo[i] = segments[s0].data[s1].photo[i-1] ;
    if(ind>=segments[s0].data[s1].photo.length)
      segments[s0].data[s1].addphoto(actions[ano][4]) ;
    else segments[s0].data[s1].setphoto(ind,actions[ano][4]) ;
  }
  else if(action=='extra') 
    for(selected=[s0,s1],i=actions[ano].length-1;i>=3;i--)
  { task = actions[ano][i]
    segments[task[0]].data.splice(task[1],task[2].length-2) ; 
  }

  nactions -= 1 ; 
  if(nactions<=1) greyout(undobtn) ; 
  blackout(redobtn) ; 
  if(actiontype(actions[nactions][0])!=0&&unsavedchanges.length>0)
    unsavedchanges.length -= 1 ;  ;
  if( action=='optimise' || action=='dltimes' || action=='stars'
   || action=='editdescription') routeinfo() ; 
  else if(action=='editphoto'||action=='editlabel') walkto(s0,s1) ;
}
/* --------------------------------- move ----------------------------------- */

function move(s0,s1,pos)
{ segments[s0].data[s1].setpos(pos) ; redrawconnect(s0,s1) ; drawsel(1) ; }

/* -------------------------------------------------------------------------- */
/*page*/
/* --------------------------------- redo  ---------------------------------- */

function redo()
{ infowindow.close() ;  
  var opts = 'Redo ' + actionname(actions[nactions]) ;
  infowindow.open(genclickable('confirmedredo()',opts),getbtnpos(6),'redo') ; 
}
function confirmedredo()
{ var i,action=actions[nactions][0],s0=actions[nactions][1],s1,caption,a,b,c ;
  var task,ind,photo ; 
  if(action!='bin'&&action!='revseg'&&action!='interpolate'&&action!='deltimes')
    s1 = actions[nactions][2] ;
  infowindow.close() ; 

  if(action=='bin') binwork(s0) ; 
  else if(action=='snip') snipwork(s0,s1) ; 
  else if(action=='editlabel') // redo create/edit/delete label
    segments[s0].data[s1].setlabel(actions[nactions][6],actions[nactions][4]) ;
  else if(action=='edittitle') settitle(s1) ; 
  else if(action=='editdescription') setdesc(s1)  ; 
  else if(action=='wpdel') wpdelwork(s0,s1) ; 
  else if(action=='move')     // ['move',s0,s1,oldpos,newpos,inserted]
  { if(actions[nactions][5]) insert(s0,s1,1) ; 
    move(s0,s1,actions[nactions][4]) ; 
  }
  else if(action=='recal') calwork(s0,s1) ; 
  else if(action=='setalt') segments[s0].data[s1].h = actions[nactions][4] ;
  else if(action=='combine') recombine(actions[nactions]) ; 
  else if(action=='revseg') revsegwork(s0) ; 
  else if(action=='stars') routeprops.stars = s1 ; 
  else if(action=='deltimes') for(i=0;i<s0.length;i++) 
    segments[s0[i][0]].data[s0[i][1]].t = null ;
  else if(action=='optimise') 
  { result = optimise(segments[s0].data,actions[nactions][2]) ; 
    actions[loadno][3].optim.ndel = segments[s0].data.length - result.length ;
    segments[s0].data = result ; 
    redraw(s0) ;
    drawsel(1,[s0,0]) ; 
    routeinfo() ; 
  }
  else if(action=='editphoto') 
  { ind = actions[nactions][3] ;
    photo = actions[nactions][5] ;
    if(actions[nactions][4]==null) segments[s0].data[s1].addphoto(photo) ;
    else segments[s0].data[s1].setphoto(ind,photo) ; 
  }
  else if(action=='extra') 
    for(selected=[s0,s1],i=3;i<actions[nactions].length;i++)
  { task = actions[nactions][i] ;
    a = segments[task[0]].data.slice(0,task[1]) ;
    b = task[2].slice(1,task[2].length-1) ; 
    c = segments[task[0]].data.slice(task[1]) ;
    segments[task[0]].data = a.concat(b,c) ; 
  }

  nactions += 1 ; 
  if(nactions==actions.length) greyout(redobtn) ; 
  blackout(undobtn) ; 
  if(actiontype(actions[nactions-1][0])!=0) unsavedchanges.push(action) ;
  if(action=='stars'||action=='editdescription') routeinfo() ; 
  else if(action=='editphoto'||action=='editlabel') walkto(s0,s1) ;
}
/* -------------------------------------------------------------------------- */
/*page*/
/* ---------------------------------- dl  ----------------------------------- */

function dl(opt) 
{ var str,i,npix,s0,s1,filename ;
  infowindow.close() ; 
  if(opt==undefined||opt==0) { opt = 0 ; getalts(1) ; }

  // filename
  i = routeprops.title.indexOf(' ') ;
  if(i<=0) filename = routeprops.title ; 
  else filename = routeprops.title.substring(0,i) ;
  if(filename==''||filename==null) filename = 'Untitled' ; 
  filename += '.tcx' ; 

  // check for photos
  for(npix=s0=0;s0<segments.length;s0++) 
    for(s1=0;s1<segments[s0].data.length;s1++) 
      npix += segments[s0].data[s1].photo.length ;

  // photo list
  if(npix>0&&imginfo.status=='ready')
  { if(imginfo.type=='tcx') routeprops.list = imginfo.uri ; // 'tcx' vice 'uri'
    else 
    { routeprops.list = document.URL ;
      if((i=routeprops.list.lastIndexOf('?'))>=0) 
        routeprops.list = routeprops.list.substring(0,i) ; 
      routeprops.list = reluri(routeprops.list,imginfo.uri) ; 
    }
  }

  if(opt) // write index and return 
  { str = writeoverview(segments,routeprops.title,routeprops.list) ;  
    saveAs(new Blob([str],{type: "text/plain;charset=utf-8"}),filename) ;
    return ;
  }

  // record optimisation 
  routeprops.optim.origlen = routeprops.optim.ndel = 0 ; 
  routeprops.optim.parms = null ; 
  for(i=0;i<nactions;i++) if(actions[i][0]=='load')
  { routeprops.optim.ndel += actions[i][3].optim.ndel ; 
    routeprops.optim.origlen += actions[i][3].optim.origlen ; 
    routeprops.optim.parms = actions[i][3].optim.parms ;
  }
  
  str = writetcx(routeprops,segments[0].data) ;  
  if(str==null) return ; 
  unsavedchanges = [] ; 
  saveAs(new Blob([str],{type: "text/plain;charset=utf-8"}),filename) ;
}
/* -------------------------------------------------------------------------- */

Archived from routemaster.html

/* ---------------------------- relative uri  ------------------------------- */

function reluri(u1,u2) 
{ var last = u1.lastIndexOf('/') ; 
  if(last<0) return u2 ; 
  u1 = u1.substring(0,last) ; 

  while(u2.substring(0,3)=='../')
  { last = u1.lastIndexOf('/') ; 
    if(last<0) return u2 ; 
    u2 = u2.substring(3) ; 
    u1 = u1.substring(0,last) ; 
  }
  return u1 + '/' + u2 ; 
}
/* -------------------------------------------------------------------------- */

function underline(d) 
{ d.setAttribute('style',
      'margin-bottom:2px;border-bottom:solid 1px silver;padding-bottom:2px') ; 
  return d ; 
}

function textdiv(title,body,lim)
{ var div=document.createElement('div'),b,nobr,flag=0 ;
  if(lim==undefined||lim==0) lim = null ; 
  else if(lim<0) { flag = 1 ; lim = -lim ; } 

  if(title!=null) 
  { b = document.createElement('b') ;
    b.appendChild(document.createTextNode(title+': ')) ;
  }

  if(lim==null||body.length<lim)
  { nobr = document.createElement('nobr') ;
    if(title!=null) nobr.appendChild(b) ;
    nobr.appendChild(document.createTextNode(body)) ;
    div.appendChild(nobr) ;
  }
  else 
  { if(title!=null) div.appendChild(b) ; 
    div.appendChild(document.createTextNode(body)) ; 
    if(flag==0) underline(div) ;  
  }
  return div ; 
}
/* -------------------------------------------------------------------------- */

function seginfodiv(segments,segno)
{ var div=document.createElement('div'),props=segments[segno].props,prose,lim ; 
  div.appendChild(textdiv(null,'Segment ' + segno + ' of ' +
    segments.length + ' (' + segments[segno].data.length + ' points)')) ;
  if(props.title!=null) div.appendChild(textdiv('Title',props.title)) ;
  if(props.source!=null) div.appendChild(textdiv('Source',props.source[0])) ;
  if(props.stats==null) lim = -50 ; else lim = 50 ; 
  if(props.desc!=null) div.appendChild(textdiv('Description',props.desc,lim)) ;
  if(props.stats!=null) div.appendChild(textdiv('Stats',props.stats)) ;
  return div ; 
}
/* -------------------------------------------------------------------------- */
/*page*/
/* -------------------------------------------------------------------------- */

function highdiv(props,list,sizes,sizeno,names) 
{ var div,scroll,p,a,d,dwid,nfetched=2 ;
  var items,i,ind,maxh,minw,sum,scroll,image=[null,null,null] ;
  for(items=[],i=0;i<names.length;i++) if((ind=findimage(list,names[i]))!=null)
  { scroll =  { ind:     ind,
                shape:   imgshape(list[ind],sizes,sizeno),
                top:     0 } ;
    items.push(scroll) ;
  }
  div = document.createElement('div') ;
  div.setAttribute('style','font-family:helvetica') ; 

  if(items.length>0)  
  { minw = items[0].shape[0] ;
    if(items.length>1) minw += items[items.length-1].shape[0] ;
    for(i=0;i<2&&i<items.length;i++) image[i] = new scrolltype(i) ; 

    for(maxh=i=0;i<items.length;i++)
    { if(items[i].shape[1]>maxh) maxh = items[i].shape[1] ;
      if(i)
      { sum = items[i].shape[0] + items[i-1].shape[0] ;
        if(sum<minw) minw = sum ;
      }
    }
    for(i=0;i<items.length;i++) 
      items[i].top = Math.floor(0.5+(maxh-items[i].shape[1])/2) ;

    scroll = document.createElement('div') ; 
    if(items.length==1) minw -= 4 ; 
    scroll.setAttribute('style','position:absolute;width:'+(minw+4)+'px;'+
                                'height:'+maxh+'px;overflow:hidden') ; 
    for(sum=i=0;i<2&&i<items.length;sum+=items[i].shape[0]+4,i++)
    { image[i].addimage(sum) ; scroll.appendChild(image[i].img) ; }
    div.appendChild(scroll) ;

    p = document.createElement('div') ; 
    p.setAttribute('style','width:'+(minw+4)+'px;'+'height:'+(maxh+4)+'px') ;
    div.appendChild(p) ;
  }

  if(props.title!=null) div.appendChild(textdiv('Title',props.title)) ; 
  if(props.stars!=null) div.appendChild(starsline(props.stars,0)) ;
  if(props.desc !=null) 
  { d = textdiv('Description',props.desc,50) ;
    if(items.length>0&&props.desc.length>=50) 
    { if(minw+4<400) dwid = 400 ; else dwid = minw+4 ;  
      d.setAttribute('style','width:'+dwid+'px') ; 
    }
    div.appendChild(d) ; 
  }
  if(props.stats!=null) div.appendChild(textdiv('Stats',props.stats)) ; 

  if(props.tracklink!=null)
  { nobr = document.createElement('nobr') ;
    a = document.createElement('a') ;
    a.setAttribute('style',
                   'cursor:pointer;color:#0000bd;text-decoration:none') ; 
    a.setAttribute('href',props.tracklink) ; 
    a.setAttribute('target',"_blank") ; 
    a.setAttribute('onclick',"infowindow.close()") ; 
    a.appendChild(document.createTextNode('View track')) ;
    nobr.appendChild(a) ;
    nobr.appendChild(document.createTextNode(' (opens in new tab/window)')) ;
    div.appendChild(nobr) ;
  }
  return { div:div , scroller: items.length<=2?null:setInterval(scroller,30) } ;

  function scrolltype(i)
  { this.img = this.top = this.pos = null ; 
    this.ind = i ; // index into items
    this.wid = items[i].shape[0] ;
    this.addimage = function(pos)
    { var fetch=null,ind=this.ind ; 
      if(ind<items.length-1&&nfetched<=ind)
      { nfetched += 1 ; 
        fetch = function() { genimage(list[items[ind+1].ind],sizes,sizeno) ; }
      }
      this.img = genimage(list[items[ind].ind],sizes,sizeno,fetch) ;
      this.pos = pos ; 
      this.scrollimage() ; 
    }
    this.scrollimage = function()
    { this.img.setAttribute('style',
        'position:absolute;top:'+items[this.ind].top+'px;left:'+this.pos+'px') ;
    }
  }
  function scroller()
  { var i,ind,offset ; 
    for(i=0;i<3;i++) if(image[i]!=null) image[i].pos -= 1 ; 
    if(image[0].pos+image[0].wid<=0)
    { scroll.removeChild(image[0].img) ; 
      for(i=0;i<2;i++) image[i] = image[i+1] ; 
      image[2] = null ; 
    } 
    for(i=0;i<3&&image[i]!=null;i++) image[i].scrollimage() ;
    offset = image[i-1].pos + image[i-1].wid + 4 ;
    if(offset<minw)
    { if(image[i-1].ind==items.length-1) ind = 0 ; 
      else ind = image[i-1].ind + 1 ; 
      image[i] = new scrolltype(ind) ;
      image[i].addimage(offset) ;
      scroll.appendChild(image[i].img) ; 
    }
  }
}
/* -------------------------------------------------------------------------- */
/*page*/
/* -------------------------------------------------------------------------- */

function greybtn(uri,name)
{ if(name=='cursor') name = 'arrow' ; else name = 'grey' + name ; 
  return uri + name + '.png' ;
}
function blackbtn(uri,name)
{ if(name=='cursor') name = 'hand' ; else name = 'black' + name ; 
  return uri + name + '.png' ;
}
function buttonimg(gif)
{ var img = document.createElement('img') ;
  img.setAttribute('src',gif) ; 
  img.setAttribute('width','24') ; 
  img.setAttribute('height','24') ; 
  return img ;
}
function buttoncell(gif1,gif2) 
{ var td=document.createElement('td'),nobr=document.createElement('nobr') ;
  td.setAttribute('style','padding-bottom:4px') ; 
  nobr.appendChild(buttonimg(gif1)) ; 
  if(gif2!=null&&gif2!=undefined)
  { nobr.appendChild(document.createTextNode(' ')) ; 
    nobr.appendChild(buttonimg(gif2)) ;
  }
  td.appendChild(nobr) ; 
  return td ;
}
function textcell(p1,p2) 
{ var td=document.createElement('td'),nobr ;
  td.setAttribute('style','padding-bottom:4px') ; 
  nobr = document.createElement('nobr') ;
  nobr.appendChild(document.createTextNode(p1)) ;
  td.appendChild(nobr) ;
  if(p2!=null&&p2!=undefined)
  { td.appendChild(document.createElement('br')) ;
    nobr = document.createElement('nobr')
    nobr.appendChild(document.createTextNode(p2)) ;
    td.appendChild(nobr) ;
  }
  return td ;
}
function appendrow(td,p)
{ var nobr = document.createElement('nobr') ;
  nobr.appendChild(document.createTextNode(p)) ; 
  td.appendChild(nobr) ;
  td.appendChild(document.createElement('br')) ;
}
function genlink(uri,legend,blank)
{ var a = document.createElement('a') ;
  a.setAttribute('style','cursor:pointer;color:#0000bd;text-decoration:none') ; 
  a.setAttribute('href',uri) ; 
  if(blank!=undefined) a.setAttribute('target','_blank') ; 
  a.appendChild(document.createTextNode(legend)) ; 
  return a ;
}
function genspan(legend,bropt,spanstyle)
{ var span = document.createElement(bropt=='hr'?'div':'span') ; 
  span.appendChild(document.createTextNode(legend)) ; 
  if(spanstyle==undefined) spanstyle = '' ; 
  else if(spanstyle!='') spanstyle += ';' ; 
  if(bropt==']br') 
  { span.appendChild(document.createTextNode(']')) ; bropt = 'br' ;} 
  if(bropt=='br') span.appendChild(document.createElement('br')) ; 
  else if(bropt=='hr') spanstyle += 
    'margin-bottom:2px;border-bottom:solid 1px silver;padding-bottom:2px' ;
  if(spanstyle!='') span.setAttribute('style',spanstyle) ; 
  return span ;
}
function genclickable(action,legend,bropt)
{ var span = genspan(legend,bropt,'cursor:pointer;color:#0000bd') ; 
  span.setAttribute('onclick',action) ; 
  return span ;
}
/* -------------------------------------------------------------------------- */
/*page*/
/* -------------------------------------------------------------------------- */

function blurbdiv(uri)
{ var div=document.createElement('div'),img=document.createElement('img'),b,p ;
  img.setAttribute('width',16) ; 
  img.setAttribute('height',16) ; 
  img.setAttribute('src',uri+'bus.gif') ; 
  div.appendChild(img) ;
  b = document.createElement('b') ;
  b.appendChild(document.createTextNode
                ('\u00a0\u00a0Routemaster GPS track editor:')) ;
  div.appendChild(b) ;
  div.appendChild(document.createElement('br')) ;
  p = document.createElement('div') ;
  p.appendChild(document.createTextNode
    ('The main use of routemaster is to load GPS tracks, display them, allow'+
     ' them to be edited in various ways, and to save them back to disc.')) ;
  p.setAttribute('style','padding-top:4px') ;
  div.appendChild(p) ; 
  p = document.createElement('div') ;
  p.appendChild(document.createTextNode
    ('It may also be used to display tracks on a website (see the example '+
     'track in the links below) or to display an index of tracks in an '+
     'area (for which there is also an example). However these uses require '+
     'you to host your own instance of the tool.')) ;
  p.setAttribute('style','text-indent:14px;border-bottom:solid 1px silver;'+
                         'padding-bottom:2px') ;
  div.appendChild(p) ; 
  return div ; 
}
/* -------------------------------------------------------------------------- */

function northernmost(data)
{ var i,maxlat,maxi ; 
  for(i=0;i<data.length;i++) if(i==0||data[i].pos.lat()>maxlat)
  { maxi = i ; maxlat = data[i].pos.lat() ; }
  return data[maxi].pos ;
}
/* -------------------------------------------------------------------------- */

function helpdiv(uri,noblank)
{ var div=document.createElement('div'),d,t,tr,td,a ; 
  if(noblank==undefined) noblank = 0 ; 
  t = document.createElement('table') ;
  t.setAttribute('cellpadding',0) ; 
  t.setAttribute('cellspacing',0) ; 
  t.setAttribute('style','font-size:100%') ; 

  tr = document.createElement('tr') ;
  tr.appendChild(buttoncell(greybtn(uri,'cursor'),blackbtn(uri,'cursor'))) ;

  td = document.createElement('td') ;
  td.setAttribute('rowspan',100) ; 
  td.appendChild(document.createTextNode('\u00a0\u00a0\u00a0')) ; // &nbsp;
  tr.appendChild(td) ; 

  tr.appendChild(textcell
    ('toggle between using the mouse to select waypoints and to drag the map',
     '(the space bar has the same function)')) ;
  t.appendChild(tr) ; 

  tr = document.createElement('tr') ;
  tr.appendChild(buttoncell(blackbtn(uri,'settings'),blackbtn(uri,'dl'))) ; 
  tr.appendChild(textcell('access to help menu and to various tools and ' + 
                          'functions /','download route as .tcx')) ; 
  t.appendChild(tr) ; 

  tr = document.createElement('tr') ;
  tr.appendChild(buttoncell(blackbtn(uri,'scissors'),blackbtn(uri,'bin'))) ; 
  tr.appendChild(textcell
    ('split the current segment at the selected point /',
     'delete the currrent segment (or use the [shift delete] '+
     'or [shift backspace] key)')) ;
  t.appendChild(tr) ; 

  tr = document.createElement('tr') ;
  tr.appendChild(buttoncell(blackbtn(uri,'pen'))) ; 
  tr.appendChild(textcell
    ('add a labelled coursepoint at the current position (1-10chars)',
     'click on flag to edit; right-click to change symbol; '+
     'delete label to delete')) ;
  t.appendChild(tr) ; 

  /* ------------------------------------------------------------------------ */
  /*page*/
  /* ------------------------------------------------------------------------ */

  tr = document.createElement('tr') ;
  td = document.createElement('td') ;
  td.setAttribute('valign','top') ; 
  td.appendChild(document.createTextNode('Keyboard: ')) ; 
  tr.appendChild(td) ; 

  td = document.createElement('td') ;
  appendrow(td,
            '\u2190/\u2192 move the current waypoint forwards or backwards;') ;
  appendrow(td,
       '[shift \u2190]/[shift \u2192] move it to the previous/next segment;') ;
  appendrow(td,'\u2193 centres the map on the current waypoint;') ;
  appendrow(td,'[return] makes the current waypoint draggable;') ;
  appendrow(td,'[tab] inserts a draggable waypoint;') ;
  appendrow(td,'[space] = toggle cursor mode;') ;
  appendrow(td,'[del], [backspace] = delete waypoint;') ;
  appendrow(td,
       '[shift del], [shift backspace] = delete segment (=bin button).') ;
  tr.appendChild(td) ; 
  t.appendChild(tr) ; 

  tr = document.createElement('tr') ;
  td = document.createElement('td') ;
  td.setAttribute('valign','top') ; 
  td.appendChild(document.createTextNode('Mouse: ')) ; 
  tr.appendChild(td) ; 

  td = document.createElement('td') ;
  appendrow(td,'when the cursor is in selection mode:') ;
  appendrow(td,
         '[shift click] extends the current segment by the cursor position.') ;
  tr.appendChild(td) ; 
  t.appendChild(tr) ; 

  d = document.createElement('div') ;
  d.appendChild(t) ; 
  div.appendChild(underline(d)) ; 

  /* ------------------------------------------------------------------------ */

  d = document.createElement('div')

  if(noblank)
  { a = genlink('http://www.masterlyinactivity.com/routemaster/?routes/'+
                'Caibros.tcx','Example track to experiment with') ; 
    d.appendChild(a) ; 
    d.appendChild(document.createElement('br')) ;
    a = genlink('http://www.masterlyinactivity.com/routemaster/?routes/'+
                'capeverde.tcx','Example of a route index') ; 
    d.appendChild(a) ; 
    d.appendChild(document.createElement('br')) ;
  }

  a = genlink('http://www.masterlyinactivity.com/software/routemaster.html',
              'Technical documentation and source code') ; 
  if(noblank==0) a.setAttribute('target','_blank') ;
  d.appendChild(a) ; 
  if(noblank==0) 
    d.appendChild(document.createTextNode(' (opens in new tab/window)')) ; 

  div.appendChild(d) ; 
  return div ; 
}
/* -------------------------------------------------------------------------- */
/*page*/
/* -------------------------------------------------------------------------- */
/*            THE MAIN POPUP MENU AND THE FUNCTIONS IT GOVERNS                */
/* -------------------------------------------------------------------------- */

function cogwheelmenu(dragopt,prof,ov)
{ var d = document.createElement('div') ;

  // dragging
  if(dragopt) 
  { d.appendChild(genspan('Hit [return] when you\'ve finished dragging','br')) ;
    return d ;
  }

  // route options
  d.appendChild(genclickable('routeinfo()','Route info','br')) ; 
  if(prof==0) 
    d.appendChild(genclickable('drawprofile()','Show altitude profile','br')) ;
  else d.appendChild(genclickable('unprofile()','Hide altitude profile','br')) ;
  d.appendChild(genclickable('addload(1)','Load new route','br')) ; 
  if(ov!=null)
  { d.appendChild(genlink(ov,'View route index','blank')) ;
    d.appendChild(genspan(' (opens in new tab/window)','br')) ; 
  }
  d.appendChild(genclickable('dl(1)','Download track as route index','hr')) ; 

  // segment options
  d.appendChild(genclickable('seginfo()','Segment info','br')) ; 
  if(unambig()) d.appendChild(genclickable('revseg()','Reverse segment','br')) ;
  else d.appendChild(genspan('Reverse segment','br','color:silver')) ;
  d.appendChild
    (genclickable('manualcal()','Calibrate segment altitudes','br')) ; 
  d.appendChild(genclickable('addload(0)','Load route as a new segment','hr')) ;

  // waypoint options
  d.appendChild(genclickable('wpinfo()','Waypoint info','br')) ; 
  if(segments[selected[0]].data.length>1) 
    d.appendChild(genclickable('wpdel()','Delete waypoint','br')) ; 
  else d.appendChild(genspan('Delete waypoint','br','color:silver')) ;
  d.appendChild(genclickable('draggit(0)','Make waypoint draggable','br')) ; 
  d.appendChild
    (genclickable('inswp(1)','Insert draggable waypoint ahead','br')) ; 
  d.appendChild
    (genclickable('inswp(-1)','Insert draggable waypoint behind','hr')) ; 

  // tool options
  if(querycanfullscreen())
  { if(queryfullscreen()==0) d.appendChild
                (genclickable('enterFullscreen()','Enter full screen','br')) ; 
    else d.appendChild
                (genclickable('exitFullscreen()','Leave full screen','br')) ; 
  } 
  d.appendChild(genclickable('help()','Help','br')) ; 
  return d ; 
}
/* -------------------------------------------------------------------------- */
/*page*/
/* -------------------------------------------------------------------------- */

function walktodiv(datum) 
{ var s,d=document.createElement('div'),dd,k,ind,imgname ; 

  if(datum.type!=null)
  { dd = document.createElement('div') ;
    s = datum.type + ': ' + datum.marker.title + ' [' ;
    dd.appendChild(document.createTextNode(s)) ;
    dd.appendChild(genclickable('labelprompt()','Edit',']br')) ;
    if(datum.photo.length>0) underline(dd) ; 
    d.appendChild(dd) ; 
  }

  for(ind=0;ind<datum.photo.length;ind++)
  { dd = document.createElement('div') ;
    if(imginfo.status!='ready') k = -1 ; 
    else k=findimg(datum.photo[ind])
    if(k>=0) 
    { s = genimage(imginfo.list[k],imginfo.sizes,imginfo.thumbind) ;
      dd.appendChild(s) ; 
      dd.appendChild(document.createElement('br')) ;
    }
    else 
    { s = 'Photo: ' + datum.photo[ind] + ' (' ;
      if(imginfo.status=='null') s += 'no list provided)' ;
      else if(imginfo.status=='ready') 
      { imgname = imginfo.uri ;
        k = imgname.lastIndexOf('/') ;
        if(k>=0) imgname = imgname.substring(k+1) ;
        s += 'not present in ' + imgname + ')' ; 
      }
      else if(imginfo.status=='waiting') s += imgname + ' is not available)' ;
      else s += 'imginfo.status = ' + imginfo.status + ')' ; 
      dd.appendChild(genspan(s),'br') ;
    }
    dd.appendChild(document.createTextNode('[')) ;
    dd.appendChild(genclickable('photoedit('+ind+')','Edit')) ;
    dd.appendChild(document.createTextNode(']')) ;
    if(k>=0)
    { dd.appendChild(document.createTextNode(' : [')) ;
      dd.appendChild(genclickable('phinfo('+k+')','Info')) ;
      dd.appendChild(document.createTextNode('] : [')) ;
      dd.appendChild(genclickable('display('+ind+')','Enlarge')) ;
      dd.appendChild(document.createTextNode(']')) ;
    }
    d.appendChild(underline(dd)) ; 
  }
  if(datum.photo.length>0) 
  { dd = document.createElement('div') ;
    dd.appendChild(document.createTextNode('[')) ;
    dd.appendChild(genclickable('photoprompt(null)','Add photo',']br')) ;
    d.appendChild(dd) ; 
  }
  return d ; 
}
/* -------------------------------------------------------------------------- */
/*page*/
/* -------------------------------------------------------------------------- */
/*           WPINFO IS A MENU GIVING ACCESS TO THE SETALT FUNCTION            */
/* -------------------------------------------------------------------------- */

function wpinfodiv() 
{ var s0=selected[0],s1=selected[1],alt,nalt,s,x,lat,lng,grad,gradstr,time ;
  var datum = segments[s0].data[s1] , pos = datum.pos ;
  var s , d = document.createElement('div') ; 

  lat = pos.lat() ; 
  lng = pos.lng() ; 
  if(lat>=0) s = lat.toFixed(5) + '\u00b0 N, ' ; 
  else { lat = -lat ; s = lat.toFixed(5) + '\u00b0 S, ' ; }
  if(lng>=0) s += lng.toFixed(5) + '\u00b0 E' ; 
  else { lng = -lng ; s += lng.toFixed(5) + '\u00b0 W' ; }
  d.appendChild(genspan(s,'br')) ;
    
  x = new LatLon(lat,lng) ; 
  if(lat>49.9&&lat<62&&lng>-12&&lng<2.5&&lat-1.5*lng<75) 
    s = 'OS grid ref: ' + OsGridRef.latLonToOsGrid(x) ;
  else s = 'UTM coords = ' + x.toUtm() ; 
  d.appendChild(genspan(s,'br')) ;

  alt = segments[s0].data[s1].h ;
  if(alt!=null) 
  { d.appendChild(genspan('Altitude: ' + alt.toFixed(0) + 'm [')) ;
    d.appendChild(genclickable('setalt(1)','Edit',']br')) ;
  }
  else d.appendChild(genclickable('setalt(0)','Set altitude','br')) ;

  time = segments[s0].data[s1].t ;
  if(time!=null&&time.getFullYear()>1980) 
  { d.appendChild(genspan('Date: '+time.toDateString(),'br')) ;
    d.appendChild(genspan('Time: '+time.toTimeString(),'br')) ;
  }

  if(datum.type!=null) 
  { d.appendChild(genspan(datum.type + ': ' + datum.marker.title + ' [')) ;
    d.appendChild(genclickable('labelprompt()','Edit',']br')) ;
  }

  if(alt==null||s1==segments[s0].data.length-1) nalt = null ; 
  else 
  { nalt = segments[s0].data[s1+1].h ; 
    if(nalt!=null) x = dist(pos,segments[s0].data[s1+1].pos) ; 
  }
  if(nalt!=null&&Math.abs(nalt-alt)<x) 
  { grad = 100*Math.asin((nalt-alt)/x) ; 
    gradstr = Math.abs(grad).toFixed(0) ; 
    if(gradstr=='0') d.appendChild(genspan('Flat','br')) ;
    else if(grad>0) d.appendChild(genspan('Climb '+gradstr+'%','br')) ;
    else d.appendChild(genspan('Descend '+gradstr+'%','br')) ;
  }
  if(segments.length>1) s = 'Segment '+s0+' p' ; else s = 'P' ;
  s += 'oint ' + s1 ; 
  d.appendChild(genspan(s,null,'font-size:80%')) ;
  return d ;
}
/* -------------------------------------------------------------------------- */

function titlediv(a,b,lim)
{ var bold,d=document.createElement('div') ; 
  if(lim==null||lim==undefined||b.length<=lim) 
  { d.setAttribute('style','white-space:nowrap') ;
    d.appendChild(document.createTextNode(a+': ')) ;
    bold = document.createElement('b') ;
    bold.appendChild(document.createTextNode(b)) ;
    d.appendChild(bold) ;
    d.appendChild(document.createTextNode(' [')) ;
  }
  else d.appendChild(document.createTextNode(a+': '+b+' [')) ;

  d.appendChild(genclickable('retitle("'+a.toLowerCase()+'")','Edit',']br')) ;
  return d ;
}
/* -------------------------------------------------------------------------- */
/*page*/
/* -------------------------------------------------------------------------- */

function routediv(rp,ov) 
{ var s0,s1,s,asc,des,oalt,alt,nlabels,nowpts,i,unsaved,props,spacing,npix ; 
  var maxsep,sep,outoforder,tlast,otime,time,tdist,ttime,ntimes,s,tdiv,ldiv ;
  var nnull , loadno , pr , dd , d = document.createElement('div') ; 

  for(loadno=nactions-1;loadno>=0&&actions[loadno][0]!='load';loadno--) ; 
  if(loadno>=0) pr = actions[loadno][3] ; else pr = null ; 
  tlast = null ;
  tdist = ttime = outoforder = dd = nnull = 0 ;
  maxsep = nlabels = npix = des = asc = nowpts = ntimes = 0 ;

  // calculate route properties 

  for(s0=0;s0<segments.length;s0++) 
  { nowpts += segments[s0].data.length ;

    for(oalt=null,s1=0;s1<segments[s0].data.length;otime=time,s1++)
    { if((alt=segments[s0].data[s1].h)==null) nnull += 1 ; 
      else
      { if(oalt!=null) 
        { if(alt>oalt) asc += alt-oalt ; else des += oalt - alt ; } 
        oalt = alt ;
      }
      if(segments[s0].data[s1].type!=null) nlabels += 1 ;
      npix += segments[s0].data[s1].photo.length ;

      time = segments[s0].data[s1].t ;
      if(time!=null) { time = time.getTime() ; ntimes += 1 ; }
      if(tlast!=null&&time!=null&&time<tlast) outoforder = 1 ; // out of order
      if(time!=null) tlast = time ;

      if(s1) 
      { sep = dist(segments[s0].data[s1-1].pos,segments[s0].data[s1].pos) ;
        dd += sep ; 
        if(sep>maxsep) maxsep = sep ; 
        if(time!=null&&otime!=null) 
        { tdist += sep ; ttime += (time-otime)/3600 ; }
      }
    }
  }

  // title
  d.appendChild(titlediv('Title',rp.title,50)) ;

  // stars
  starsdiv = document.createElement('div') ; 
  starsdiv.setAttribute('style','color:#0000bd') ; 
  d.appendChild(starsline(rp.stars,1)) ;

  /* ------------------------------------------------------------------------ */
  /*page*/
  /* ------------------------------------------------------------------------ */

  // description
  if(rp.desc==null) 
  { tdiv = document.createElement('div') ; 
    tdiv.appendChild(document.createTextNode('[')) ;
    tdiv.appendChild(genclickable('redescribe()','Add description',']br')) ;
  }
  else tdiv = titlediv('Description',rp.desc,50) ;
  d.appendChild(underline(tdiv)) ;

  // last added route
  if(loadno>0)
  { s = '\u00a0\u00a0\u00a0Last added route' ;
    if(pr.title==null) s += ':' ; else s += ' (' + pr.title + '):' ;
    d.appendChild(genspan(s,'br')) ;
    s = '\u00a0\u00a0\u00a0' ;
  }
  else s = '' ;

  // number of track points and optimisation
  s += 'Track points on input: ' + pr.inputlen ; 
  if(pr.optim.already)  
    d.appendChild(genspan(s+' (previously optimised)','br')) ;
  else if(pr.optim.ndel==0) 
  { d.appendChild(genspan(s+' [')) ;
    if(nactions==loadno+1) 
      d.appendChild(genclickable('optimprompt()','Optimise','br')) ;
    else d.appendChild(genspan('Optimise',null,'color:silver','br')) ; 
  }
  else d.appendChild
         (genspan(s+', optimised to '+(pr.inputlen-pr.optim.ndel),'br')) ;
  if(!pr.optim.already&&pr.inputlen-pr.optim.ndel!=nowpts) 
    d.appendChild(genspan('Now ' + nowpts + ' track points','br')) ;

  // are there any missing altitudes?
  if(nnull) 
  { d.appendChild(genspan(nnull+' points have no associated altitudes [')) ;
    d.appendChild(genclickable('getalts(0)','Find altitudes',']br')) ;
  }

  // are points timed and are they in sequence?
  if(outoforder==0) 
  { if(ntimes==0) d.appendChild(genspan('No timings provided','br')) ;
    else 
    { if(ntimes>=nowpts) s = '[' ;
      else s = (nowpts-ntimes) + ' points have no associated timings [' ;
      d.appendChild(genspan(s)) ;
      d.appendChild(genclickable('deltimes()','Discard timings',']br')) ;
    }
  }
  else d.appendChild(genspan('Times are out of sequence (will be '+
                             'discarded on download)','br')) ;

 // labels and photos
  if(nlabels>0) d.appendChild
    (genspan(nlabels+' labelled course point'+(nlabels>1?'s':''),'br')) ; 
  if(npix>0) d.appendChild(genspan(npix+' photo'+(npix>1?'s':''),'br')) ; 

  /* ------------------------------------------------------------------------ */
  /*page*/
  /* ------------------------------------------------------------------------ */

  // unsaved changes
  unsaved = unsavedchanges.length ; 
  if(unsaved>0) d.appendChild
    (genspan(unsaved+' unsaved change'+(unsaved>1?'s':''),'br')) ;

  // number of segments - option to combine
  if(segments.length>1) 
  { d.appendChild(genspan(segments.length + ' segments [')) ;
    d.appendChild(genclickable('combine()','Combine',']br')) ;
    d.appendChild(genspan('Note that segments must be combined before saving',
                          'br','font-style:italic')) ;
  }
  
  // max waypoint separation - option to interpolate
  d.appendChild
    (genspan('Max waypoint separation: '+maxsep.toFixed(0)+'m','br')) ;
  if(maxsep>=100) 
  { d.appendChild(genspan('Note that separations \u003e100m are illegal '+
                          'on Garmin','br','font-style:italic')) ;
    d.appendChild(genspan('\u00a0\u00a0\u00a0[')) ;
    d.appendChild(genclickable('extrapts()','Interpolate extra points',']br')) ;
  }

  // distance / ascent / descent / average speed
  tdiv = document.createElement('div') ; 
  tdiv.setAttribute('style',
    'margin-top:2px;border-top:solid 1px silver;padding-top:2px') ; 

  function ldivadd(b)
  { ldiv.appendChild(document.createTextNode(b)) ;
    ldiv.appendChild(document.createElement('br')) ;
  } ;

  ldiv = document.createElement('div') ; 
  ldiv.setAttribute('style','float:left;padding-right:8px') ; 
  ldivadd('Total distance:') ;
  ldivadd('Total ascent:') ;
  ldivadd('Total descent:') ;
  if(outoforder==0&&tdist>0&&ttime>0) ldivadd('Average speed:') ;
  tdiv.appendChild(ldiv) ; 

  ldiv = document.createElement('div') ; 
  ldiv.setAttribute('style','float:left;text-align:right') ; 
  ldivadd((dd/1000).toFixed(3)) ;
  ldivadd(asc.toFixed(0)) ;
  ldivadd(des.toFixed(0)) ;
  if(outoforder==0&&tdist>0&&ttime>0) ldivadd((tdist/ttime).toFixed(1)) ;
  tdiv.appendChild(ldiv) ; 

  ldiv = document.createElement('div') ; 
  ldiv.setAttribute('style','float:left;padding-left:2px') ; 
  ldivadd('km') ;
  ldivadd('m') ;
  ldivadd('m') ;
  if(outoforder==0&&tdist>0&&ttime>0) ldivadd('km/hr') ;
  tdiv.appendChild(ldiv) ; 

  d.appendChild(tdiv) ; 
  return d ; 
}
/* -------------------------------------------------------------------------- */
/*page*/
/* -------------------------------------------------------------------------- */

function starsline(nstars,editable)
{ var i , c , s , d ;
  if(editable) 
  { if(starsdiv==null) return ; else d = starsdiv ; 
    while(d.childNodes.length>0) 
      d.removeChild(d.childNodes[d.childNodes.length-1]) ;
  }
  else d = document.createElement('div') ; 
 
  for(i=1;i<=5;i++) 
  { s = document.createElement('span') ;
    if(nstars==null||i>nstars) c = '\u2606' ; else c = '\u2605' ;
    s.appendChild(document.createTextNode(c)) ;
    if(editable&&i!=nstars) 
    { s.setAttribute('style','cursor:pointer') ; 
      function createfunc(i) { return function() { restars(nstars,i) ; } } ;
      s.onclick = createfunc(i) ; 
    }
    d.appendChild(s) ; 
  }
  if(editable&&nstars!=null) 
  { d.appendChild(genspan(' [')) ; 
    d.appendChild(genclickable('restars('+nstars+',null)','Clear')) ; 
    d.appendChild(genspan(']')) ; 
  }
  return d ; 
}
/* --------------------------------- photo info ----------------------------- */

function phdiv(i) 
{ var d=document.createElement('div') ,ind,hind,r,k,shape ;
  var list=imginfo.list , sizes=imginfo.sizes ;

  d.appendChild(genspan('Name: '+list[i].name,'br')) ;
  d.appendChild(genspan('Title: '+list[i].title,'br')) ;
  for(hind=null,ind=0;ind<i;ind++) if(list[ind].name==null) hind = ind ;
  if(hind!=null) 
    d.appendChild(genspan('Under \u201c'+list[hind].title+'\u201d','br')) ;

  // how many sizes?
  for(r=ind=0;ind<sizes.length;ind++) if(sizes[ind].type==undefined) r += 1 ;
  d.appendChild(genspan('Available in '+r+' size'+(r>1?'s: ':': '))) ;

  // print the sizes
  for(k=ind=0;ind<sizes.length;ind++) if(sizes[ind].type==undefined)
  { if(k==0) s = '' ; else { if(k==r-1) s = ' and ' ; else s = ', ' ; }
    shape = imgsize(list[i],sizes,ind) ;
    d.appendChild(genspan(s+shape[0]+'x'+shape[1])) ;
    k += 1 ; 
  }
  d.appendChild(document.createElement('br')) 

  shape = list[i].thumbshape ;
  d.appendChild(genspan('Thumb: '+shape[0]+'x'+shape[1],'br')) ;
  s += '<br>Thumb: ' + shape[0] + 'x' + shape[1] ;
  for(ind=0;ind<sizes.length;ind++) if(sizes[ind].type=='raw')
  { shape = imgsize(list[i],sizes,ind) ;
    d.appendChild(genspan('Raw: '+shape[0]+'x'+shape[1],'br')) ;
  }

  if(imginfo.pixpage!=null)
  { d.appendChild(genlink(imginfo.pixpage,'Full photo set',1)) ;
    d.appendChild(genspan(' (opens in new tab/window)','br')) ;
  }

  if(list[i].retid!=null)
  { for(hind=null,ind=0;ind<=i;ind++) 
      if(list[ind].retpage!=undefined&&list[ind].retpage!=null) 
        hind = list[ind].retpage + '.html#' + list[i].retid ; 
    if(hind!=null) 
    { d.appendChild(genlink(hind,'Route notes',1)) ;
      d.appendChild(genspan(' (opens in new tab/window)','br')) ;
    }
  }
  return d ; 
}
/* -------------------------------------------------------------------------- */
/*page*/
/* -------------------------------------------------------------------------- */
/*          FUNCTIONS FOR COMPUTING & DISPLAYING THE ALTITUDE PROFILE         */
/* -------------------------------------------------------------------------- */

function profiletype(x,y,sind)
{ var i,ymin,ymax  ;
  this.x = x ; 
  this.y = y ; 
  this.sind = sind ; 
  this.sum = x[x.length-1] ;
  for(ymin=ymax=null,i=0;i<y.length;i++) if(y[i]!=null) 
  { if(ymax==null||y[i]>ymax) ymax = y[i] ;
    if(ymin==null||y[i]<ymin) ymin = y[i] ;
  }
  if(ymin>0) { if(ymax>3*ymin) ymin = 0 ; else ymin *= 1 - (ymax/ymin-1)/2 ; }
  this.ymin = ymin ;
  this.ymax = ymax ; 
  this.yspan = Math.max(1,ymax-ymin) ; 
  this.prodiv = this.curdiv = this.curcan = this.curhandle = null ; 
}
// member functions
profiletype.prototype.getx = function(x) { return 10 + 600 * x / this.sum ; } ;
profiletype.prototype.locx = function(i) { return this.getx(this.x[i]) ; } ;
profiletype.prototype.gety = function(y) 
{ return 10 + 180*(this.ymax-y)/this.yspan ; } ;

profiletype.prototype.getxy = function(i)
{ return [this.getx(this.x[i]),this.y[i]==null?null:this.gety(this.y[i])] ; } ;

profiletype.prototype.getsel = function(x)
{ var lo=0,hi=this.x.length-1,m,s0 ;
  if(x<0) x = 0 ; else if(x>600) x = 600 ;
  x += 10 ; 
  while(hi>lo+1)  // binary search
  { m = Math.floor((lo+hi)/2) ; if(this.locx(m)>x) hi = m ; else lo = m ; }
  if(Math.abs(this.locx(lo)-x)<Math.abs(this.locx(hi)-x)) m = lo ; else m = hi ;
  for(s0=0;s0<this.sind.length-1&&this.sind[s0+1]<=m;s0++) ;
  return [ s0 , m-this.sind[s0] ] ;
} ;
/* -------------------------------------------------------------------------- */

function procoords(segments)
{ var n,x,y,s0,s1,sum,len,pos,oldpos,sind,i ;
  for(n=s0=0;s0<segments.length;s0++) n += segments[s0].data.length ;
  x = new Array(n) ;
  y = new Array(n) ;
  sind = new Array(segments.length+1) ;
  x[0] = 0 ; 

  for(ymax=ymin=null,sum=i=s0=0;s0<segments.length;s0++) 
    for(sind[s0]=i,len=segments[s0].data.length,s1=0;s1<len;s1++,i++)
  { y[i] = segments[s0].data[s1].h ; 
    pos = segments[s0].data[s1].pos ;
    if(i) sum = x[i] = sum + dist(pos,oldpos) ; 
    oldpos = pos ; 
  }
  sind[segments.length] = n ; 
  if(n==0) return null ; else return new profiletype(x,y,sind) ; 
}
/* -------------------------------------------------------------------------- */
/*page*/
/* -------------------------------------------------------------------------- */

function drawpro(pro)
{ var div=document.createElement('div'),c=document.createElement('canvas') ;
  var ctx,i,n,s0,s1,len,xinit,ox,step,xy ;

  div.setAttribute
    ('style','position:absolute;height:190px;right:0;top:0;width:620px') ; 
  c.setAttribute('width',620) ; 
  c.setAttribute('height',200) ; 
  div.appendChild(c) ;
  ctx = c.getContext("2d") ; 
  ctx.font = "10px Helvetica" ;
  ctx.lineWidth = 0 ; 
  ctx.globalAlpha = 0.6 ; 

  ctx.fillStyle = 'lightgray' ;
  ctx.rect(0,0,620,200) ;
  ctx.fill() ; 

  // draw a profile of each segment
  for(i=n=s0=0;s0<pro.sind.length-1;s0++) 
  { len = pro.sind[s0+1] - pro.sind[s0] ;
    if(s0&1) ctx.fillStyle = "#ff9999" ; else ctx.fillStyle = "#ff0000" ; 
    for(xinit=ox=null,s1=0;s1<len;s1++,n++,ox=xy[0])
    { xy = pro.getxy(n) ; 
      if(xy[1]!=null) 
      { if(xinit==null) 
        { ctx.beginPath() ; ctx.moveTo(xy[0],xy[1]) ; xinit = xy[0] ; }
        else ctx.lineTo(xy[0],xy[1]) ; 
      }
    }
    ctx.lineTo(xy[0],190) ; 
    ctx.lineTo(xinit,190) ; 
    ctx.closePath() ; 
    ctx.fill() ; 
  }

  // lines
  if(pro.yspan>2500) step = 1000 ; 
  else if(pro.yspan>1250) step = 500 ;
  else step = 100 ; 

  for(i=step*Math.floor(pro.ymin/step+1);i<pro.ymax;i+=step) 
  { y = 0.5 + pro.gety(i) ;
    ctx.beginPath() ; 
    ctx.lineWidth = 1 ; 
    ctx.strokeStyle = '#555' ; 
    ctx.moveTo(10,y) ;
    ctx.lineTo(610,y) ; 
    ctx.stroke() ; 
    ctx.strokeText(i,590,y-2) ;
  }
  pro.prodiv = div ; 

  // cursor
  pro.curdiv = document.createElement('div') ;
  pro.curdiv.setAttribute
    ('style','position:absolute;height:190px;right:0;top:0;width:620px') ; 
  pro.curcan = null ; 
  pro.curhandle = pro.curdiv.addEventListener("click",function(e)
  { var pos = e.clientX - (window.innerWidth-610) ; 
    if((pos-594)*(pos-594)+(e.clientY-16)*(e.clientY-16)<200)
    { unprofile() ; return ; } 
    drawsel(0,pro.getsel(pos)) ; 
  } ) ;  
}
/* -------------------------------------------------------------------------- */
/*page*/
/* -------------------------------------------------------------------------- */

function drawxcur(pro,sel)
{ if(pro==null||pro.prodiv==null) return null ; 
  var pos = pro.getx(pro.x[sel[1]+pro.sind[sel[0]]]) , i ; 
  var canvas=document.createElement('canvas') , ctx=canvas.getContext("2d") ; 
  canvas.setAttribute('width',620) ; 
  canvas.setAttribute('height',200) ; 
  ctx.beginPath() ; 
  ctx.lineWidth = 1 ; 
  ctx.moveTo(pos,10) ;
  ctx.lineTo(pos,190) ; 
  ctx.stroke() ; 

  // the circle of the 'x' 
  ctx.beginPath() ; 
  ctx.strokeStyle = '#555' ; 
  ctx.fillStyle = 'white' ; 
  ctx.lineWidth = 3 ; 
  ctx.arc(604,16,14.1,0,2*Math.PI,false) ;
  ctx.stroke() ; 
  ctx.fill() ; 

  for(i=6;i<=26;i+=20) // the two bars of the 'x'
  { ctx.beginPath() ; 
    ctx.moveTo(594,i) ; 
    ctx.lineTo(614,32-i) ; 
    ctx.stroke() ; 
  }
  if(pro.curcan!=null) pro.curdiv.removeChild(pro.curcan) ; 
  pro.curdiv.appendChild(pro.curcan=canvas) ;
}
/* -------------------------------------------------------------------------- */
/*page*/
/* -------------------------------- actionname ------------------------------ */

function actionname(x)
{ var i,s ; 
  if(x[0]=='bin') return 'delete segment' ; 
  if(x[0]=='snip') return 'split segment' ; 
  if(x[0]=='editlabel') 
  { if(x[4]=='') return 'delete label' ; 
    else if(x[3]=='') return 'label waypoint' ;
    else return 'edit label' ; 
  }
  if(x[0]=='edittitle') return 'edit title' ; 
  if(x[0]=='editdescription') return 'edit description' ; 
  if(x[0]=='wpdel') return 'delete waypoint' ; 
  if(x[0]=='move') 
  { if(x[5]) return 'insert waypoint' ; else return 'drag waypoint' ; }
  if(x[0]=='recal') return 'recalibrate altitudes' ; 
  if(x[0]=='setalt') return 'set waypoint altitude' ; 
  if(x[0]=='resign') return 'change label symbol' ; 
  if(x[0]=='combine') return 'combine '+x[1]+' segments' ; 
  if(x[0]=='revseg') return 'reverse segment' ; 
  if(x[0]=='optimise') return 'optimisation' ; 
  if(x[0]=='deltimes') return 'delete times' ; 
  if(x[0]=='editphoto') 
  { if(x[5]==null) return 'delete photo' ; 
    else if(x[4]==null) return 'add photo' ;
    else return 'change photo' ; 
  }
  if(x[0]=='extra') return 'interpolate extra points' ; 
  if(x[0]=='stars'&&x[2]==null)
  { s = 'clear ' ; for(i=0;i<x[1];i++) s += '\u2605' ; return s ; } 
  if(x[0]=='stars')
  { s = 'set ' ; for(i=0;i<x[2];i++) s += '\u2605' ; return s ; } 
  alert('Unrecognised action: '+x[0]) ;
}
/* -------------------------------------------------------------------------- */
/*page*/
/* --------------------------------- getalts -------------------------------- */

var elevator=null,reqlist=[] ; 

function getalts(thresh)
{ var s0,s1,start,end,n,npts,flag,i,lox,ind,point ; 
  if(thresh==0) { infowindow.close() ; thresh = 1 ; } 
  if(reqlist.length>0) return ;
  if(elevator==null) elevator = new google.maps.ElevationService ;

  for(reqlist=[],flag=npts=s0=0;s0<segments.length;s0++)
    for(s1=0;s1<segments[s0].data.length;s1++) if(segments[s0].data[s1].h==null)
  { if(s1>0) start = s1-0 ; else start = s1 ; 
    for(;s1<segments[s0].data.length&&segments[s0].data[s1].h==null;s1++) ;
    if(s1==segments[s0].data.length) end = s1 ; else end = s1+1 ; 
    n = end - start ; 
    if(npts+n<=500) { npts += n ; reqlist.push([s0,start,end]) ; }
    else if(reqlist.length>0) { flag = 1 ; break ; }
    else
    { for(point=new Array(501),i=0;i<=500;i++)
        point[i] = segments[s0].data[start+Math.floor((i*(n-1))/500)] ;
      reqlist.push([s0,0,501]) ;
      flag = 2 ; 
      break ; 
    }
  }
  if(flag==0&&npts<thresh) { reqlist = [] ; return ; }

  if(flag!=2) for(point=new Array(npts),i=ind=0;ind<reqlist.length;ind++) 
  { s0 = reqlist[ind][0] ;
    start = reqlist[ind][1] ;
    end = reqlist[ind][2] ;
    for(s1=start;s1<end;s1++,i++) point[i] = segments[s0].data[s1] ;
  }
  for(lox=new Array(point.length),i=0;i<point.length;i++) 
    lox[i] = point[i].pos ;

  elevator.getElevationForLocations( {locations:lox} , function (results,status)
  { // assume that the results come in sequence, ie. correspond to xpending[0]
    var d0,dn,k ;
    if(status===google.maps.ElevationStatus.OVER_QUERY_LIMIT)
      alert('No calibration data available: over Google query limit') ;  
    else if(status===google.maps.ElevationStatus.INVALID_REQUEST)
      alert('Invalid calibration request') ;  
    else if(status===google.maps.ElevationStatus.REQUEST_DENIED)
      alert('Calibration request denied') ;  
    else if(status===google.maps.ElevationStatus.UNKNOWN_ERROR)
      alert('Unknown error reported for calibration request:'+status) ;  
    else if(status!==google.maps.ElevationStatus.OK)
      alert('Calibration error') ;  
    if(status!==google.maps.ElevationStatus.OK) throw '' ;

    if(results.length!=lox.length) flag = 1 ; 
    else for(flag=i=0;i<lox.length&&flag==0;i++) 
      if(dist(lox[i],results[i].location)>5) flag= 1 ; 
    if(flag) 
    { alert("elevation response does not correspond to request") ; return ; }

    for(k=ind=0;ind<reqlist.length;ind++,k+=n) 
    { n = reqlist[ind][2] - reqlist[ind][1] ; 
      if(point[k].h!=null&&point[k+n-1].h!=null)
      { d0 = point[k].h - results[k].elevation ;
        dn = point[k+n-1].h - results[k+n-1].elevation ;
        for(i=1;i<n-1;i++) 
          point[k+i].h = results[k+i].elevation + (i*dn+((n-1)-i)*d0) / (n-1) ;
      }
      else
      { if(point[k].h!=null) d0 = point[k].h - results[k].elevation ; 
        else if(point[k+n-1].h!=null) 
          d0 = point[k+n-1].h - results[k+n-1].elevation ; 
        else d0 = 0 ; 
        for(i=0;i<n;i++) point[k+i].h = results[k+i].elevation + d0  ;
      }
    }
    reqlist = [] ; 
    getalts(thresh) ; 
  } ) ;
}

Archived from routemaster.html

// www.masterlyinactivity.com/software/routemaster.html
//
/* The MIT License

   Copyright (c) 2016, by Colin Champion <colin.champion@masterlyinactivity.com>

   Permission is hereby granted, free of charge, to any person obtaining
   a copy of this software and associated documentation files (the
   "Software"), to deal in the Software without restriction, including
   without limitation the rights to use, copy, modify, merge, publish,
   distribute, sublicense, and/or sell copies of the Software, and to
   permit persons to whom the Software is furnished to do so, subject to
   the following conditions:

   The above copyright notice and this permission notice shall be
   included in all copies or substantial portions of the Software.

   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
   MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
   NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
   BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
   ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
   CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
   SOFTWARE.
*/
var icons = 
{ // coursepoint icons
  flagsign:
  { path: "M 0.5 20.5  L 0.5 0.5  12.5 6  0.5 11.5  ",
    fillColor: '#FCDFFF',
    fillOpacity: 0.7,
    strokeColor: 'purple',
    strokeWeight: 1.5,
    anchor: new google.maps.Point(0.5,20.5),
  } ,
  turnleft:
  { path: "M 18.5 20.5  L 16.5 11.5  A 2 2 0 0 0 14.5 9.5  "+
          "L 11.5 10  11.5 13.5  "+
          "6.5 7.5  11.5 1.5  11.5 5  16.5 5.5  A 3.5 3.5 0 0 1 20 9   z",
    fillColor: '#FCDFFF',
    fillOpacity: 0.7,
    strokeColor: 'purple',
    strokeWeight: 1.5,
    anchor: new google.maps.Point(18.5,20.5),
  } ,
  straighton:
  { path: "M 7.5 20.5  L 4.5 6.5  0.5 6.5  7.5 0.5  14.5 6.5  10 6.5  z",
    fillColor: '#FCDFFF',
    fillOpacity: 0.7,
    strokeColor: 'purple',
    strokeWeight: 1.5,
    anchor: new google.maps.Point(7.5,20.5),
  } ,
  turnright:
  { path: "M 3.5 20.5  L 5.5 11.5  A 2 2 0 0 1 7.5 9.5  L 10.5 10  10.5 13.5  "+
          "15.5 7.5  10.5 1.5  10.5 5  5.5 5.5  A 3.5 3.5 0 0 0 2 9   z",
    fillColor: '#FCDFFF',
    fillOpacity: 0.7,
    strokeColor: 'purple',
    strokeWeight: 1.5,
    anchor: new google.maps.Point(3.5,20.5),
  } ,
  shriek:
  { path: "M 8.5 21.5 A 2.5 2.5 0 0 1 8.5 16.5  A 2.5 2.5 0 1 1 8.5 21.5  "+
          "M 8.5 14.5   4.5 5.5  A 4.5 4.5 0 1 1 12.5 5.5 L 8.5 14.5" ,
    fillColor: '#FCDFFF',
    fillOpacity: 0.7,
    strokeColor: 'purple',
    strokeWeight: 1.5,
    anchor: new google.maps.Point(8.5,21.5),
  } ,
  fork:
  { path: "M 0.5 0.5  L 0.5 5.5  2.5 5.5  2.5 0.5  2.5 5.5  4.5 5.5  4.5 0.5" +
          "  4.5 5.5  6.5 5.5   6.5 0.5   6.5 7.5  " +
          "A 2.5 2.5 0 0 1 4.25 9.95   L 5 19.5  "+
          "A 1.5 1.5 0 0 1  2 19.5   L 2.75 9.95 " + 
          "A 2.5 2.5 0 0 1 0.5 7.5   z",
    fillColor: '#FCDFFF',
    fillOpacity: 0.7,
    strokeColor: 'purple',
    strokeWeight: 1.5,
    anchor: new google.maps.Point(3.5,22),
  } ,
  // icon for arrow representing current waypoint
  arrow:
  { path: "M 6 9  0 15  6 0  12 15 z",
    fillColor: 'black',
    fillOpacity: 1,
    strokeColor: 'black',
    strokeWeight: 0,
    anchor: new google.maps.Point(6,6),
    rotation: 0,
    clickable: false 
  } ,
  // icon for concentric circles representing draggable waypoint
  concircle:
  { path: "M 6 0  A 6 6 0 1 0 6 12  A 6 6 0 1 0 6 0 M 6 3  " +
          "A 3 3 0 1 0 6  9   A 3 3 0 1 0 6  3",
    fillColor: 'black',
    fillOpacity: 0,
    strokeColor: 'black',
    strokeWeight: 1,
    strokeOpacity: 1,
    anchor: new google.maps.Point(6,6),
    clickable: false 
  } ,
  // camera icon
  camera:
  { path: "M 0.5 4   A 1.5 1.5 0 0 1 2 2.5   L  5.5 2.5   7 0.5  11 0.5   " + 
          "12.5 2.5   14 2.5   A 1.5 1.5 0 0 1  16 3   L 20 7   16 11 " +
          "A 1.5 1.5 0 0 1 15 11.5   L 2 11.5   A 1.5 1.5 0 0 1 0.5 10  z " + 
          "M 9 4  A 3 3 0 0 1 9 10   A 3 3 0 0 1 9 4 " ,  
    fillColor: '#FCDFFF',
    fillOpacity: 0.7,
    strokeColor: 'purple',
    strokeWeight: 1.5,
    anchor: new google.maps.Point(21,7),
    clickable: false 
  } 
} ;
function xmlfloat(x) { return parseFloat(x.childNodes[0].textContent) ; }

function isvaliddate(d) 
{ if(Object.prototype.toString.call(d)!=="[object Date]") return false ;
  else return !isNaN(d.getTime()) ;
}
function isvalidnum(x) { return !isNaN(parseFloat(x)) && isFinite(x) ; }

/* ------------------------------- data structure --------------------------- */

// I found the following logic quite hard to get right. A (non-null) label
// satisfies the following constraints:
// o. the marker is non-null
// o. the map may be null, and if it is null the title may also be null and the
//    icon may be arbitrary
// o. if the type is null, the map is null
// o. the map is null if and only if the clickhandler is inactive
// the same constraints apply (mutatis mutandis) to the photo, so it follows 
// that the label may have a null map and the photo non-null (and vice versa)
//    we therefore conclude that a label must be in one of 3 states:
// o. type null, map null, handlers inactive, but marker non-null
// o. type non-null, map null, handlers inactive, marker non-null
// o. type non-null, map non-null, handlers active, marker non-null
// the state in which type is non-null and map is null is applied to all 
// labels in a segment being deleted (we preserve the information in the 
// action list but don't want the label to be displayed)

function datatype(pos,h,t)
{ this.pos = pos ; 
  this.h = h ; 
  this.marker = this.photomarker = this.type = this.t = null ;
  if(t!=undefined&&t!=null&&isvaliddate(t)&&t.getTime()>365*24*3600000) 
    this.t = t ; 
  this.photo = [] ;
  this.caption = '' ;  
  this.clickhandler = this.righthandler = this.photohandler = null ; 
}
// member functions
datatype.prototype.geticon = function()
{ if(this.type=='Left')  return icons.turnleft ; 
  else if(this.type=='Straight') return icons.straighton ; 
  else if(this.type=='Right') return icons.turnright ; 
  else if(this.type=='Danger') return icons.shriek ; 
  else if(this.type=='Food') return icons.fork ; 
  else return icons.flagsign ; 
} ;
datatype.prototype.setlabelmap = function(m) 
{ if(m==null||this.type==null) m = null ; else m = map ; 
  if(m==null&&this.marker==null) return ;
  this.marker.setMap(m) ; 
  if(m==null&&this.clickhandler!=null)
  { google.maps.event.removeListener(this.clickhandler) ;
    google.maps.event.removeListener(this.righthandler) ;
    this.clickhandler = this.righthandler = null ; 
  }
  if(m!=null&&this.clickhandler==null)
  { this.clickhandler = this.marker.addListener('click',selpoint) ;
    this.righthandler = this.marker.addListener('rightclick',labelcycle) ;
  }
} ;
datatype.prototype.setphotomap = function(m) 
{ if(m==null||this.photo.length==0) m = null ; else m = map ; 
  if(m==null&&this.photomarker==null) return ;
  this.photomarker.setMap(m) ;
  if(m==null&&this.photohandler!=null) 
  { google.maps.event.removeListener(this.photohandler) ;
    this.photohandler = null ; 
  }
  if(m!=null&&this.photohandler==null) 
    this.photohandler = this.photomarker.addListener('click',selpoint) ;
} ;
datatype.prototype.setlabel = function(t,c) 
{ this.type = t ; 
  this.caption = c ; 
  if(t==null) { if(this.marker!=null) this.setlabelmap(null) ; return ; } 
  if(this.marker==null) this.marker = new google.maps.Marker
      ({ position:this.pos,map:map,icon:this.geticon(),title:c,zIndex:1 }) ;
  else { this.marker.setIcon(this.geticon()) ; this.marker.setTitle(c) ; }
  this.setlabelmap(map) ; 
} ;
datatype.prototype.setphoto = function(ind,p) 
{ var i ;
  if(p==null)
  { for(i=ind;i<this.photo.length-1;i++) this.photo[i] = this.photo[i+1] ; 
    this.photo.length -= 1 ; 
    if(this.photo.length==0&&this.photomarker!=null) this.setphotomap(null) ; 
    return ; 
  }
  else { this.photo[ind] = p ; if(ind==0) this.photomarker.setTitle(p) ; }
} ;
datatype.prototype.addphoto = function(p) 
{ this.photo.push(p) ; 
  if(this.photomarker==null) this.photomarker = new google.maps.Marker
      ({ position:this.pos,map:map,icon:icons.camera,title:p,zIndex:1 }) ;
  this.setphotomap(map) ; 
} ;
datatype.prototype.setpos = function(p) 
{ this.pos = p ; 
  if(this.type!=null) this.marker.setPosition(p) ; 
  if(this.photo.length>0) this.photomarker.setPosition(p) ; 
} ;
datatype.prototype.setmap = function(m) 
{ this.setlabelmap(m) ; this.setphotomap(m) ; } ;

datatype.prototype.settype = function(t) 
{ this.type = t ; this.marker.setIcon(this.geticon()) ; } ;

datatype.prototype.labelcycle = function()
{ var oldtype = this.type , type ; 
  if(oldtype=='Generic') type = 'Left' ; 
  else if(oldtype=='Left') type = 'Straight' ; 
  else if(oldtype=='Straight') type = 'Right' ; 
  else if(oldtype=='Right') type = 'Danger' ; 
  else if(oldtype=='Danger') type = 'Food' ; 
  else type = 'Generic' ; 
  this.settype(type) ;
  return [ oldtype , type ] ; 
}
function addlabel(data,pos,type,caption) 
{ var j,ind,mindist ; 
  for(j=0;j<data.length;j++) if(j==0||dist(pos,data[j].pos)<mindist) 
  { mindist = dist(pos,data[j].pos) ; ind = j ; } 
  data[ind].setlabel(type,caption) ;
}
function propstype()
{ this.desc = this.title = this.list = this.inputlen = this.source = null ;
  this.stats = this.tracklink = this.overview = this.stars = null ; 
  this.photo = [] ;
  this.optim = { already: 0, ndel: 0, origlen: 0, parms: null }
}
/* -------------------------------------------------------------------------- */

function readtcx(xmldoc)
{ var nodeno,type,lat,lon,i,j,node,alt,pos,segment,props,title,list,txt ;
  var ind,caption,data,photo,time,valid,anc,xmlnodes,nsegment,propno,names ;
  var track,trackpoint,trackno,course,coursepoint,courseno,courselen,overview ;
  var validalt,fieldnames ; 

  // loop over trackpoints 
  track = xmldoc.getElementsByTagName('Trackpoint') ;
  for(courselen=[],data=[],anc=null,trackno=0;trackno<track.length;trackno++)
  { trackpoint = track[trackno] ;
    if(trackpoint.parentNode.parentNode.nodeName=='Course')
      if(trackpoint.parentNode.parentNode!=anc) 
    { anc = trackpoint.parentNode.parentNode ; 
      courselen.push([trackno,anc]) ; 
    }
    lat = lon = alt = time = null ; 
    photo = [] ;
    for(validalt=valid=1,nodeno=0;nodeno<trackpoint.childNodes.length;nodeno++)
    { node = trackpoint.childNodes[nodeno] ;

      if(node.nodeName=='AltitudeMeters') alt = xmlfloat(node) ; 
      else if(node.nodeName=='Time') // '1970-01-01T03:040:08Z'
        time = new Date(node.childNodes[0].textContent) ; 
      else if(node.nodeName=='Position') for(j=0;j<node.childNodes.length;j++)
      { if(node.childNodes[j].nodeName=='LatitudeDegrees') 
          lat = xmlfloat(node.childNodes[j]) ; 
        else if(node.childNodes[j].nodeName=='LongitudeDegrees') 
          lon = xmlfloat(node.childNodes[j]) ;
      }
      else if(node.nodeName=='Extensions') for(j=0;j<node.childNodes.length;j++)
      { if(node.childNodes[j].nodeName=='Photo') 
          photo = node.childNodes[j].childNodes[0].textContent.split(' ') ;
        else if(node.childNodes[j].nodeName=='ValidTime') valid = 0 ;
        else if(node.childNodes[j].nodeName=='ValidAlt') validalt = 0 ;
      }
    }
    if(lat==null||lon==null) continue ; 
    if(!isvalidnum(alt)) validalt = 0 ; 
    pos = new google.maps.LatLng(lat,lon) ;
    data.push(new datatype(pos,validalt?alt:null,valid?time:null)) ; 
    for(ind=0;ind<photo.length;ind++) data[data.length-1].addphoto(photo[ind]) ;
  }
  if(track.length==0) { alert('no trackpoints') ; throw '' ; }
  if(courselen.length==0) courselen.push([0,null]) ; 
  courselen.push([track.length,null]) ; 

  // loop over coursepoints
  course = xmldoc.getElementsByTagName('CoursePoint') ;
  for(courseno=0;courseno<course.length;courseno++)
  { coursepoint = course[courseno] ;
    caption = type = lat = lon = null ;
    for(nodeno=0;nodeno<coursepoint.childNodes.length;nodeno++)
    { node = coursepoint.childNodes[nodeno] ;
      if(node.nodeName=='Name') caption = node.childNodes[0].textContent ; 
      else if(node.nodeName=='PointType') 
        type = node.childNodes[0].textContent ; 
      else if(node.nodeName=='Position') for(j=0;j<node.childNodes.length;j++)
      { if(node.childNodes[j].nodeName=='LatitudeDegrees') 
          lat = xmlfloat(node.childNodes[j]) ;
        else if(node.childNodes[j].nodeName=='LongitudeDegrees') 
          lon = xmlfloat(node.childNodes[j]) ;
      }
    }
    if(lat==null||lon==null||caption==null||type==null) 
    { alert('Badly formatted course point' ) ; throw '' ; }
    addlabel(data,new google.maps.LatLng(lat,lon),type,caption) ; 
  }

  for(segment=[],i=0;i<courselen.length-1;i++)
    segment.push(data.slice(courselen[i][0],courselen[i+1][0])) ;
  nsegment = segment.length ;

  // props fields
  props = new Array(nsegment) ; 
  for(i=0;i<nsegment;i++) 
  { props[i] = new propstype() ;
    props[i].inputlen = data.length ; 
    if(props[i].optim.origlen==0) props[i].optim.origlen = data.length ;
  }

  // optimised?
  xmlnodes = xmldoc.getElementsByTagName('Optimised') ;
  if(xmlnodes.length)
  { props[0].optim.already = 1 ; 
    props[0].optim.origlen = parseInt(xmlnodes[0].getAttribute('from')) ; 
    props[0].optim.ndel = 
      props[0].optim.origlen - parseInt(xmlnodes[0].getAttribute('to')) ; 
    props[0].optim.parms = 
      { tol: parseFloat(xmlnodes[0].getAttribute('tol')) ,
        maxsep: parseFloat(xmlnodes[0].getAttribute('maxsep')) ,
        wppenalty: parseFloat(xmlnodes[0].getAttribute('wppenalty')) ,
        vweight: parseFloat(xmlnodes[0].getAttribute('vweight')) 
      } ; 
    for(i=1;i<nsegment;i++) props[i].optim = props[0].optim ; 
  }

  // overview fields (different values for each segment) all in one big loop
  names = [ 'Name' ,     'LongTitle' , 'Description' , 'Stats' , 'PhotoList' ,
            'Overview' , 'Index' ,     'TrackLink' , 'Stars' ,       'Photo' ] ;
  fieldnames = [ 'title' ,    'desc' ,     'desc' ,      'stats' , 'list' ,
                 'overview' , 'overview' , 'tracklink' , 'stars' , 'photo' ] ;
  for(title=null,propno=0;propno<names.length;propno++)
  { xmlnodes = xmldoc.getElementsByTagName(names[propno]) ;
    for(i=0;i<xmlnodes.length;i++)
    { node = xmlnodes[i] ;
      if(propno==0) anc = node.parentNode ;
      else anc = node.parentNode.parentNode ;
      if(propno==4) txt = node.getAttribute('src') ;
      else if(propno==5||propno==6||propno==7) txt = node.getAttribute('href') ;
      else txt = node.childNodes[0].textContent ;
      for(j=0;j<nsegment&&anc!=courselen[j][1];j++) ;
      if(j==nsegment)
      { if(propno==0&&title==null)
          if( node.parentNode.nodeName=='Courses' 
           || node.parentNode.nodeName=='Lap' ) title = txt ;  
        continue ; 
      }
      if(names[propno]=='Photo') props[j].photo = txt.match(/\S+/g) ;
      else props[j][fieldnames[propno]] = txt ;
    }
  }

  return { title: title , props: props , segments: segment } ;
}
/* -------------------------------------------------------------------------- */

function readgpx(xmldoc)
{ var xmlcoords,nodeno,type,lat,lon,i,node,alt,pos,caption,data,time ; 
  var props = new propstype() ; 

  // get the route name
  xmlcoords = xmldoc.getElementsByTagName('name') ;
  for(i=0;props.title==null&&i<xmlcoords.length;i++)
    if(xmlcoords[i].parentNode.nodeName!='wpt') 
      props.title = xmlcoords[i].childNodes[0].textContent ;

  // get the route description
  xmlcoords = xmldoc.getElementsByTagName('desc') ;
  if(xmlcoords.length>0) 
    props.desc = xmlcoords[0].childNodes[0].textContent ;

  // loop over the track points to get the coords
  xmlcoords = xmldoc.getElementsByTagName('trkpt') ;
  if(xmlcoords.length==0) xmlcoords = xmldoc.getElementsByTagName('rtept') ;

  for(data=[],i=0;i<xmlcoords.length;i++)
  { lat = parseFloat(xmlcoords[i].getAttribute('lat')) ; 
    lon = parseFloat(xmlcoords[i].getAttribute('lon')) ; 
    pos = new google.maps.LatLng(lat,lon) ;

    for(time=alt=null,nodeno=0;nodeno<xmlcoords[i].childNodes.length;nodeno++)
    { node = xmlcoords[i].childNodes[nodeno] ;
      if(node.nodeName=='ele') alt = parseFloat(node.textContent) ; 
      else if(node.nodeName=='time')
        time = new Date(node.childNodes[0].textContent) ; 
    }
    if(!isvalidnum(alt)) alt = null ; 
    data.push(new datatype(pos,alt,time)); 
  }

  // loop over the course points to get the labels
  xmlcoords = xmldoc.getElementsByTagName('wpt') ;
  for(i=0;i<xmlcoords.length;i++)
  { caption = type = lat = lon = null ;
    lat = parseFloat(xmlcoords[i].getAttribute('lat')) ; 
    lon = parseFloat(xmlcoords[i].getAttribute('lon')) ; 
    for(nodeno=0;nodeno<xmlcoords[i].childNodes.length;nodeno++)
    { node = xmlcoords[i].childNodes[nodeno] ;
      if(node.nodeName=='name') caption = node.childNodes[0].textContent ; 
      else if(node.nodeName=='type') type = node.childNodes[0].textContent ; 
    }
    if(lat==null||lon==null||caption==null) 
    { alert('Badly formatted course point' ) ; throw '' ; }
    if(type==null) type = 'Generic' ; 
    addlabel(data,new google.maps.LatLng(lat,lon),type,caption) ; 
  }

  props.inputlen = data.length ; 
  return { props: [props] , segments: [data] } ;
}
/* -------------------------------------------------------------------------- */
/*page*/
/* -------------------------------------------------------------------------- */

function dist(x,y)
{ return google.maps.geometry.spherical.computeDistanceBetween(x,y) ; }

function angle(x,y)
{ return google.maps.geometry.spherical.computeHeading(x,y)*Math.PI/180 ; }

/* -------------------------------------------------------------------------- */

function optimise(idata,parms)
{ var stk,nnstk,stk2,clen=idata.length,i,j,k,m,step=new Array(clen-1) ; 
  var opos,oalt,npos,nalt,ndatum,mpos,malt,arccentre,arctol,pathpos ; 
  var legal,theta,omega,x,y,hyp,tdist,maxtheta,mintheta,d,dh,od,odh,odash ; 
  var bearings=new Array(clen),nstk=new Array(clen),pi=Math.PI,tol=parms.tol ;
  var backptr = new Array(clen) ; 

  stk = [ { err:0 , pathpos:1 , prev:-1 } ] ;
  for(i=0;i<clen-1;i++) step[i] = dist(idata[i].pos,idata[i+1].pos) ; 

  // this is a forwards dynamic program. in stk we have a list of hypotheses
  // each of which advances a different number of points through the data, 
  // sorted increasing on how far they've advanced. at each step we take the 
  // first item from the stack and try extending to each legal successor point.
  //    note that a hypothesis whose pathpos is k is one whose last point is
  // idata[k-1].
  while(stk[0].pathpos<clen)
  { pathpos = stk[0].pathpos ;
    backptr[pathpos-1] = stk[0].prev ;
    opos = idata[pathpos-1].pos ;
    oalt = idata[pathpos-1].h ; 
    // try extending to pathpos+i
    for(arctol=null,nnstk=i=0;i<clen-pathpos;i++)
    { ndatum = idata[pathpos+i] ; 
      npos = ndatum.pos ; 
      nalt = ndatum.h ; 
      if(i==0) hyp = step[pathpos-1] ;
      else if((hyp=dist(opos,npos))>parms.maxsep) break ; 
      omega = angle(opos,npos) ; 
      // find the min and max legal bearing
      if(hyp>tol) 
      { theta = Math.asin(tol/hyp) ; 
        if(arctol==null) { arccentre = omega ; arctol = theta ; } 
        else
        { for(odash=omega-arccentre;odash>pi;odash-=2*pi) ; 
          while(odash<-pi) odash += 2*pi ;
          maxtheta = Math.min(arctol,odash+theta) ; 
          mintheta = Math.max(-arctol,odash-theta) ; 
          if(maxtheta<mintheta) break ; 
          arccentre += (maxtheta+mintheta) /2 ; 
          arctol     = (maxtheta-mintheta) /2 ;
        }
      } 
      /* -------------------------------------------------------------------- */
      /*page*/
      /* -------------------------------------------------------------------- */

      bearings[i] = { hyp:hyp , omega:omega } ; 
      // see whether this breaches the max error on any intermediate point
      for(legal=1,od=odh=tdist=m=0;m<i;m++,od=d,odh=dh)
      { mpos = idata[pathpos+m].pos ;
        malt = idata[pathpos+m].h ; 
        x = bearings[m].hyp ; 
        theta = bearings[m].omega ; 
        d = x * Math.sin(theta-omega) ; 
        dh = 0 ;
        if(d*d<tol*tol&&oalt!=null&&nalt!=null&&malt!=null)  
        { y = hyp - x*Math.cos(theta-omega) ;
          y = Math.sqrt(d*d+y*y) ; 
          dh = parms.vweight * ( malt - (oalt*y+nalt*x)/(x+y) ) ; 
        }
        if(d*d+dh*dh>tol*tol) { legal = 0 ; break ; } 
        tdist += step[pathpos-1+m] * ( d*d+d*od+od*od + dh*dh+odh*dh+odh*odh ) ;
      }
      // if we emerge with 'legal' non-zero then we may advance to pathpos+i 
      // and tdist is the sum of squared errors
      if(legal) nstk[nnstk++] = 
      ( { err:      stk[0].err + pi*tdist/3 + parms.wppenalty , 
          pathpos:  stk[0].pathpos+i+1 ,
          prev:     pathpos-1 
        } ) ; 
      if(ndatum.type!=null||ndatum.photo.length>0) break ; 
    }  // end loop over i 

    // now we have in nstk the possible extensions of stk[0] in increasing
    // order of end point, so we merge with stk[0..stk.length-1]
    for(stk2=new Array(stk.length+nnstk),i=1,k=j=0;i<stk.length||j<nnstk;)
      if(i==stk.length) stk2[k++] = nstk[j++] ; 
      else if(j==nnstk||stk[i].pathpos<nstk[j].pathpos) stk2[k++] = stk[i++] ; 
      else if(stk[i].pathpos>nstk[j].pathpos) stk2[k++] = nstk[j++] ; 
      else if(stk[i].err<nstk[j].err) { stk2[k++] = stk[i++] ; j += 1 ; } 
      else { stk2[k++] = nstk[j++] ; i += 1 ; } 
    stk = stk2.slice(0,k) ; 
  }

  // thread backwards through the pointers
  for(m=1,i=stk[0].prev;i>=0;i=backptr[i],m++) ;
  nstk = new Array(m) ; 
  for(nstk[m-1]=idata[clen-1],j=m-2,i=stk[0].prev;i>=0;i=backptr[i],j--) 
    nstk[j] = idata[i] ;
  return nstk ; 
}
/* -------------------------------------------------------------------------- */
/*page*/
/* ------------------------------- writetcx  -------------------------------- */

function writetcx(props,idata) 
{ var i,j,k,xmldoc,course,lap,datum,filename,track,time,routelen,nnull,sum ; 
  var trackpoint,coursepoint,str,flag,clen=idata.length,tlast,di,dk,pointdist ;
  var origlen,ndel,ano,photo,thisuri,maxsep,sep,tdist,ttime,time,otime,x,y ; 
  var distance = new Array(clen) , msecs = new Array(clen) ;
  var valid = new Array(clen) , alt = new Array(clen) ;

  for(tlast=null,nnull=maxsep=tdist=ttime=i=flag=0;i<clen;otime=time,i++) 
  { if((alt[i]=idata[i].h)==null) nnull += 1 ; 
    time = idata[i].t ;
    if(time!=null) { msecs[i] = time = time.getTime() ; valid[i] = 1 ; } 
    if(tlast!=null&&time!=null&&time<tlast) flag = 1 ; // out of order
    if(time!=null) tlast = time ;
    if(i) 
    { sep = dist(idata[i-1].pos,idata[i].pos) ;
      distance[i] = distance[i-1] + sep ; 
      if(sep>maxsep) maxsep = sep ; 
      if(time!=null&&otime!=null) { tdist += sep ; ttime += time - otime ; }
    }
    else distance[i] = 0 ; 
  }
  routelen = distance[clen-1] ;

  if(maxsep>100&&!confirm('Some gaps between waypoints are >100m.\n'+
    'This will cause problems if used for navigation in a Garmin.\n'+
    'You can hit [OK] and I will proceed anyway, or\n'+
    'you can hit [Cancel] and interpolate extra points\n'+
    '(recommended \u2013 go to Route Info under the cogwheel).')) return null ; 

  // decide what to do if some points have no altitudes
  if(nnull==clen) { alert('no points have altitudes') ; return null ; }
  if(nnull>0&&!confirm(nnull+' waypoints have no associated altitudes.\n' +
  'You can hit [OK] and I will interpolate altitudes (not guaranteed),\n'+
  'or you can hit [Cancel] and try again later when the altitudes may be '+
  'available.')) return null ; 

  if(nnull) for(pointdist=new Array(clen),i=0;i<clen;i=j)
  { for(;i<clen&&idata[i].h!=null;i++) ;          // advance to null
    if(i==clen) break ; 
    for(j=i+1;j<clen&&idata[j]==null;j++) ;       // advance to non-null
    if(i==0) { for(y=idata[j].h;i<j;i++) alt[i] = y ; continue ; } 
    if(j==clen) { for(x=idata[i-1].h;i<j;i++) alt[i] = x ; continue ; } 
    for(sum=k=0;k<=j-i;k++) 
      sum = pointdist[k] = sum + dist(idata[i+k-1].pos,idata[i+k].pos) ; 
    for(x=idata[i-1].h,y=idata[j].h,k=0;k<j-i;k++) 
      alt[i+k] = ( x*(sum-pointdist[k]) + y*pointdist[k] ) / sum ; 
  }

  // fill in missing times
  if(tdist==0||flag!=0) for(i=0;i<clen;i++) msecs[i] = distance[i] * 333 ;
  else for(i=0;i<clen;i=k)
  { for(;i<clen&&idata[i].t!=null;i++) ;      // advance to null
    if(i==clen) break ;
    for(k=i+1;k<clen&&idata[k].t==null;k++) ; // advance to non-null
    for(j=i;j<k;j++) valid[i] = 0 ;
    if(i==0) for(time=msecs[k],j=i;j<k;j++)
      msecs[j] = time - (distance[k]-distance[j])*ttime/tdist ;
    else if(k==clen) for(time=msecs[i-1],j=i;j<clen;j++)
      msecs[j] = time + (distance[j]-distance[i-1])*ttime/tdist ;
    else for(j=i,di=distance[i-1],dk=distance[k];j<k;j++) 
      msecs[j] = ( msecs[i-1]*(dk-distance[j]) + msecs[k]*(distance[j]-di) ) 
                        / (dk-di) ;
  }

  str = '<?xml version="1.0" encoding="UTF-8" standalone="no" ?>\n' +
        '<TrainingCenterDatabase xmlns="http://www.garmin.com/xmlschemas/' +
        'TrainingCenterDatabase/v2"\n' +
        '          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"\n' +
        '          xsi:schemaLocation="http://www.garmin.com/' +
        'xmlschemas/TrainingCenterDatabase/v2 ' + 
        'http://www.garmin.com/xmlschemas/TrainingCenterDatabasev2.xsd">\n' +
        '<!-- http://www.masterlyinactivity.com/software/routemaster.html -->' ;
  str += '\n  <Folders><Courses><CourseFolder Name="Courses">\n' ;
  str += '        <CourseNameRef><Id>'+props.title+'</Id></CourseNameRef>\n' ;
  str += '  </CourseFolder></Courses></Folders>\n<Courses><Course>\n' ; 
  str += '  <Name>'+props.title+'</Name>\n  <Lap>\n' + adddist(routelen) ; 
  time = (msecs[clen-1]-msecs[0]) / 1000 ;
  str += '    <TotalTimeSeconds>' + time.toFixed(0) + '</TotalTimeSeconds>\n' ;
  str += addpos('Begin',idata[0].pos) ; 
  str += addpos('End',idata[clen-1].pos) ; 
  str += '    <Intensity>Active</Intensity>\n' + '  </Lap>\n  <Track>\n' ; 

  // loop over trackpoints
  for(i=0;i<idata.length;i++) 
  { str += '  <Trackpoint>\n' + addpos('',idata[i].pos) ; 
    str += adddist(distance[i]) + addalt(idata[i].h==null?alt[i]:idata[i].h) ;
    str += '    <Time>' + new Date(msecs[i]).toISOString() + '</Time>\n' ;
    if(idata[i].photo.length>0)
    { str += '    <Extensions><Photo>' ;
      for(k=0;k<idata[i].photo.length;k++)
      { if(k) str += ' ' ; str += idata[i].photo[k] ; }
      str += '</Photo></Extensions>\n' ; 
    }
    if(valid[i]==0||idata[i].h==null) 
    { str += '    <Extensions>' ; 
      if(valid[i]==0) str += '<ValidTime>False</ValidTime>' ;
      if((valid[i]==0&&idata[i].h==null)) str += '\n                ' ;
      if(idata[i].h==null) str += '<ValidAlt>False</ValidAlt>' ;
      str += '</Extensions>\n' ; 
    }
    str += '    <SensorState>Absent</SensorState>\n  </Trackpoint>\n' ; 
  }
  str += '  </Track>\n\n' ;

  if( props.optim.ndel>0 || props.list!=null || props.stars!=null
   || props.desc!=null   || props.overview != null) 
  { str += '  <Extensions>\n' ;
    if(props.optim.ndel) 
    { str += '    <Optimised from="' + props.optim.origlen +
                            '" to="' + (props.optim.origlen-props.optim.ndel) ;
      if(props.optim.parms!=null) 
        str += '" tol="' + props.optim.parms.tol.toFixed(0) +
                '" maxsep="' + props.optim.parms.maxsep.toFixed(0) +
                '" wppenalty="' + props.optim.parms.wppenalty.toFixed(0) +
                '" vweight="' + props.optim.parms.vweight.toFixed(1) ;
      str += '"/>\n'
    }
    if(props.list!=null) str += '    <PhotoList src="'+props.list+'"/>\n' ; 
    if(props.stars!=null) str += '    <Stars>'+props.stars+'</Stars>\n' ; 
    if(props.desc!=null) 
      str += '    <Description>'+props.desc+'</Description>\n' ; 
    if(props.overview!=null) 
      str += '    <Index href=">' + props.overview + '"/>\n' ; 
    str += '  </Extensions>\n\n' ;
  }
  
  // finally loop over coursepoints
  for(i=0;i<idata.length;i++) if(idata[i].type!=null)
  { datum = idata[i] ;
    str += '  <CoursePoint><Name>'+datum.marker.title+'</Name>\n' ; 
    str += '    <PointType>'+datum.type+'</PointType>\n' ; 
    str += addpos('',idata[i].pos) + addalt(idata[i].h) ;
    time = new Date(msecs[i]) ; 
    str += '    <Time>' + time.toISOString() + '</Time>\n  </CoursePoint>\n' ;
  }
  return str + '</Course></Courses></TrainingCenterDatabase>\n' ; 
}
/* -------------------------------------------------------------------------- */

function addpos(tag,pos)
{ var str = '    <'+tag+'Position>\n      <LatitudeDegrees>' ; 
  str += pos.lat().toFixed(5)+'</LatitudeDegrees>\n      <LongitudeDegrees>' ;
  str += pos.lng().toFixed(5)+'</LongitudeDegrees>\n    </'+tag+'Position>\n' ;
  return str ;
}
function addalt(x)
{ return '    <AltitudeMeters>' + x.toFixed(0) + '</AltitudeMeters>\n' ; }
function adddist(x)
{ return '    <DistanceMeters>' + x.toFixed(0) + '</DistanceMeters>\n' ; }

/* ----------------------------- writeoverview  ----------------------------- */

function writeoverview(segments,title,list) 
{ var i,j,h,oh,maxalt,minalt,routelen,up,down,ndata,photo,segno,idata,title ;
  var uri ; 
  var str = '<?xml version="1.0" encoding="UTF-8" standalone="no" ?>\n' +
        '<!-- http://www.masterlyinactivity.com/software/routemaster.html -->' +
        '\n<!-- **** routemaster index file **** not for navigation ****' +
        ' -->\n<TrainingCenterDatabase><Courses>\n' ;

  if(title!='Untitled Route'&&title!=null) str += '<Name>'+title+'</Name>\n' ;
  str += '\n' ; 

  for(segno=0;segno<segments.length;segno++)
  { idata = segments[segno].data ;
    for(oh=maxalt=minalt=null,routelen=up=down=i=0;i<idata.length;oh=h,i++)
    { if(i) routelen += dist(idata[i-1].pos,idata[i].pos) ;
      if((h=idata[i].h)==null) continue ; 
      if(maxalt==null||h>maxalt) maxalt = h ; 
      if(minalt==null||h<minalt) minalt = h ; 
      if(h!=null&&oh!=null) { if(h>oh) up += h-oh ; else down += oh-h ; } 
    }

    for(photo=[],ndata=new Array(idata.length),i=0;i<idata.length;i++)
    { ndata[i] = new datatype(idata[i].pos,0) ;
      for(j=0;j<idata[i].photo.length;j++) photo.push(idata[i].photo[j]) ; 
    }
    idata = optimise(ndata,{tol:500,maxsep:100000,wppenalty:10000,vweight:0}) ;

    str += '<Course>\n' ;
    if(segments[segno].props.title!=null) 
      str += '  <Name>' + segments[segno].props.title + '</Name>\n' ;
    str += '  <Track>\n' ; 

    // loop over trackpoints
    for(i=0;i<idata.length;i++) 
      str += '    <Trackpoint><Position>\n      <LatitudeDegrees>' +
             idata[i].pos.lat().toFixed(4)+'</LatitudeDegrees>\n      ' +
             '<LongitudeDegrees>' + idata[i].pos.lng().toFixed(4) +
             '</LongitudeDegrees>\n    </Position></Trackpoint>\n' ; 
    str += '  </Track>\n\n  <Extensions>\n' ;

    title = segments[segno].props.stars ;
    if(title!=null) str += '    <Stars>' + title + '</Stars>\n' ; 

    title = segments[segno].props.desc ;
    if(title!=null) str += '    <Description>' + title + '</Description>\n' ; 

    uri = document.URL ;  
    if((title=segments[segno].props.source)!=null) 
    { if(title[1]=='uri') uri = reluri(uri,'?'+title[0]) ; 
      else uri = reluri(uri,'?$FILE$/'+title[0]) ; 
    }
    str += '    <Stats>Distance ' + (routelen/1000).toFixed(1) + 'km;' +
           ' altitude ' + minalt.toFixed(0) + '-' + maxalt.toFixed(0) + 
           'm; \u2191' + up.toFixed(0) + 'm \u2193' + down.toFixed(0) + 
           'm</Stats>\n    <TrackLink href="' + uri + '"/>\n' ; 

    if(list!=null) str += '    <PhotoList src="' + list + '"/>\n' ; 

    if(photo.length)
    { str += '    <Photo>' ;
      for(i=0;i<photo.length;i++) 
      { if(i>0&&i%6==0) str += '\n           ' ; else if(i) str += ' ' ; 
        str += photo[i] ; 
      }
      if(photo.length%6==0) str += '\n           ' ; 
      str += '</Photo>\n' ;
    }
    str += '  </Extensions>\n</Course>\n\n' ;
  }

  return str + '</Courses></TrainingCenterDatabase>\n' ; 
}
/* -------------------------------------------------------------------------- */

function gencolours(n)
{ var colours=new Array(n) ;
  var ind,density,k,a,na,i,j,m,r,g,b ;

  for(ind=0,density=1;ind<n;density*=2)
  { if(density==1) k = 3 ; else k = Math.floor(0.5+0.75*density*(1+density/2)) ;
    a = new Array(k) ;
    if(density==1) { a = [ [0,0] , [0,1] , [1,0] ] ; na = 3 ; }
    else for(na=i=0;i<=density;i++) 
    { if((i&1)==0) for(j=1;i+j<=density;j+=2) a[na++] = [ i , j ] ;
      else for(j=density-i;j>=0;j--) a[na++] = [ i , j ] ;
    }
    if(na!=k) alert('logic error') ; 
    for(k=0;(1<<k)<na;k++) ;
    for(i=0;i<(1<<k)&&ind<n;i++)
    { for(m=j=0;j<k;j++) m |= ((i>>j)&1) << (k-1-j) ;
      if(m>=na) continue ;
      r = ( density - a[m][0] - a[m][1] ) * (255/density) ;
      g = a[m][1] * (180/density) ;
      b = a[m][0] * (300/density) ;
      r = ("00"+Math.floor(0.5+r).toString(16)).substr(-2) ;
      g = ("00"+Math.floor(0.5+g).toString(16)).substr(-2) ;
      b = ("00"+Math.floor(0.5+b>255?255:b).toString(16)).substr(-2) ;
      colours[ind++] = '#' + r + g + b ; 
    }      
  }
  return colours ;
}
/* -------------------------------------------------------------------------- */

Archived from pix.html

// www.masterlyinactivity.com/software/pix.html
/* The MIT License

   Copyright (c) 2016, by Colin Champion <colin.champion@masterlyinactivity.com>

   Permission is hereby granted, free of charge, to any person obtaining
   a copy of this software and associated documentation files (the
   "Software"), to deal in the Software without restriction, including
   without limitation the rights to use, copy, modify, merge, publish,
   distribute, sublicense, and/or sell copies of the Software, and to
   permit persons to whom the Software is furnished to do so, subject to
   the following conditions:

   The above copyright notice and this permission notice shall be
   included in all copies or substantial portions of the Software.

   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
   MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
   NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
   BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
   ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
   CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
   SOFTWARE.
*/
document.write( '<style>a:link{color:#66aaaa}' + 
                'a:visited{color:#cc3388}a:active{color:#404040}</style>' ) ; 
if((typeof pixlib)=="undefined") document.write
  ('<scri'+'pt src="http://www.masterlyinactivity.com/pixlib.js"></scri'+'pt>');
var thispage,full=0,nload=0,here,lind,rind,blink,pagetitle,retpage ;
var nitem=[],thumbshape=[],maxthumb,imagedir=null,infodiv=null ;
var llink,rlink,ulink,dlink,prevind,fromnotes,fullname ;

function uninfodiv()
{ if(infodiv!=null) 
  { document.getElementsByTagName("body")[0].removeChild(infodiv) ;
    infodiv = null ; 
  }
}
/* -------------------------------------------------------------------------- */

// pixresize responds to a window resize as follows:
// o. update 'full' if necessary
// o. for a table view, if the number of columns can change, change it;
// o. for an image view, if the new size permits a larger or smaller image, 
//    enlarge or reduce;
// o. or if the window is expanding or contracting but hasn't changed size 
//    enough to change the desired image, anticipatively preload a larger or
//    smaller one.

function pixresize()
{ if((full=queryfullscreen())==0&&here.full!=0&&fullname!=here.name)
    location.href = piclink(here.ind) ;

  if(here.name!=null) resize()
  else if(full!=here.full||here.ncol!=(k=getncol())) tabulate() ;
}
/* ------------------- construct a link to an image page -------------------- */

function piclink(ind)
{ if(full) return 'javascript:display("' + list[ind].name + '")' ;
  else return thispage + '?image=' + list[ind].name + (fromnotes?'&mode=n':'') ;
}
/* ----- getncol finds the number of table columns which fit the screen ----- */

function getncol()
{ var maxcol,ncol,k,ind ; // 17 pix for scrollbar, 19 pix for margin, border...
  maxcol = Math.floor((window.innerWidth-17)/(maxthumb[0]+19)) ; 
  if(maxcol<1) maxcol = 1 ;  
  for(ncol=ind=0;ind<nitem.length;ind++) 
  { k = Math.ceil(nitem[ind]/Math.ceil(nitem[ind]/maxcol)) ;
    if(k>ncol) ncol = k ; 
  }
  return ncol ;
}
/* -------------------------------------------------------------------------- */
/*page*/
/* -------------------- create links line of the table page ----------------- */

function linkp()
{ var p,a,span,astyle,i,flag=0 ; 
  astyle = 'font-family:arial;font-weight:normal;text-decoration:none;' ;
  

  p = document.createElement("p") ; 
  p.setAttribute('style',"text-align:center;font-size:100%;margin:6px;"+
                         "font-family:arial;color:silver") ; 

  if(retpage!=null)
  { a = document.createElement('a') ; 
    a.setAttribute('href',list[retpage].retpage+'.html') ; 
    a.setAttribute('style',astyle) ; 
    a.appendChild(document.createTextNode('notes')) ; 
    p.appendChild(a) ; 
    flag = 1 ;
  }

  if(links!=null) for(i=0;i<links.length;flag=1,i++)
  { if(flag) p.appendChild(document.createTextNode(' : ')) ; 
    a = document.createElement('a') ; 
    a.setAttribute('href',links[i].href) ; 
    a.setAttribute('style',astyle) ; 
    a.appendChild(document.createTextNode(links[i].name)) ; 
    p.appendChild(a) ; 
  }

  if(full==0&&querycanfullscreen())
  { if(flag) p.appendChild(document.createTextNode(' : ')) ; 
    a = document.createElement('a') ; 
    a.setAttribute('style',astyle) ; 
    a.setAttribute('href','javascript:{}') ; 
    a.setAttribute('onclick','enterfullscreen()') ; 
    a.appendChild(document.createTextNode('full screen')) ; 
    p.appendChild(a) ; 
    span = document.createElement('span') ;
    span.setAttribute('style',"color:gray") ; 
    span.appendChild(document.createTextNode(' [f key] ')) ;
    p.appendChild(span) ;
  }
  return p ;
}
/* -------------------------------------------------------------------------- */

function display(name)
{ var titlestr,body,title,i,k,fetchitem,s,ind=findimage(list,name) ;
  uninfodiv() ; 
  body = document.getElementsByTagName("body")[0] ; 

  if(name!=here.name) 
  { title = document.getElementsByTagName("title")[0] ; 
    while(title.childNodes.length>1) title.removeChild(title.firstChild) ;
    titlestr = document.createTextNode(list[ind].title)
    if(title.childNodes.length==0) title.appendChild(titlestr) ;
    else title.replaceChild(titlestr,title.childNodes[0]) ;
  }

  here = { name:name , ind:ind , ncol:0 , full:full } ;

  // navigation links: <
  for(lind=ind-1;
      lind>=0&&(list[lind].name==undefined||list[lind].display=='none');
      lind--) ; 
  if(lind<0) llink = null ; else llink = piclink(lind) ;

  // navigation links: >
  for(rind=ind+1;
      rind<list.length &&
        (list[rind].name==undefined||list[rind].display=='none');
      rind++) ; 
  if(rind==list.length) rlink = null ; else rlink = piclink(rind) ;

  // navigation links: return
  if(fromnotes==0) 
  { if(full) blink = 'javascript:tabulate()' ; else blink = thispage ; 
    s = "table" ;
  }
  else
  { blink = retlink(list,ind) ;
    if(blink==null) alert('no return page for '+list[ind].name) ; 
    s = "notes" ;
  }

  // decide which image if any to prefetch 
  if(prevind!=null&&prevind>ind&&lind>=0) k = lind ; 
  else if((prevind==null||prevind<=ind)&&rind<list.length) k = rind ; 
  else k = null ;
  if(k==null) fetchitem = null ; else fetchitem = list[k] ;

  gendisplay(body,list[ind],sizes,llink,blink,rlink,s,fetchitem) ; 
}
/* ------------------------ navigate using the arrow keys ------------------- */

function navigate(e) 
{ var d ; 
  if(infodiv!=null) { uninfodiv() ; if(e.keyCode==73) return ; }

  if(e.keyCode==37&&lind>=0)          // left arrow key
  { e.preventDefault() ; 
    if(full==0) location.href= llink ;
    else { prevind = lind+1 ; display(list[lind].name) ; }
  }
  else if(e.keyCode==39&&rind<list.length) // right arrow key
  { e.preventDefault() ; 
    if(full==0) location.href= rlink ;
    else { prevind = rind-1 ; display(list[rind].name) ; }
  }
  else if(e.keyCode==13&&here.ncol==0)  // return
  { e.preventDefault() ; 
    if(full&&fromnotes==0) tabulate() ; else location.href = blink ; 
  } 
  else if(e.keyCode==40&&here.ncol==0) { e.preventDefault() ; reduce() ; } 
  else if(e.keyCode==38&&here.ncol==0) { e.preventDefault() ; enlarge() ; } 
  else if(e.keyCode==70&&full==0)          // 'f' (full screen)
  { e.preventDefault() ; fullname = here.name ; enterfullscreen() ; full = 1 ; }
  else if(e.keyCode==73&&here.ncol==0)
  { infodiv = pixinfodiv(list,here.ind,sizes) ; 
    infodiv.setAttribute('style','position:fixed;bottom:20;right:20;'+
     'background:white;padding:8;opacity:0.8') ;
    document.getElementsByTagName("body")[0].appendChild(infodiv) ; 
  }
}
/* -------------------------------------------------------------------------- */
/*page*/
/* -------------------------------------------------------------------------- */

function tabulate()
{ var ind,ncol,p,body,table,tr,td,a,img,trflag,k,s,opts,thind=thumbind(sizes) ; 
  var prefetch,nimg,padflag,k,num ; 
  uninfodiv() ; 
  gendisplay() ; 
  rind = list.length ;
  lind = -1 ;

  body = document.getElementsByTagName("body")[0] ; 
  while(body.firstChild) body.removeChild(body.firstChild) ;

  ncol = getncol() ;
  here = { name:null , ind:null , ncol:ncol , full:full } ;
  // prefetch will be performed when nimg thumbs have been loaded
  for(nimg=ind=0;ind<list.length;ind++) 
    if(list[ind].name!=undefined&&list[ind].display!='none') nimg += 1 ; 

  p = document.createElement("p") ; 
  p.setAttribute('style',"text-align:center;font-size:140%;margin:2px 0 6px;"+
                 "font-family:arial;color:silver;") ; 
  p.appendChild(document.createTextNode(pagetitle)) ; 
  body.appendChild(p) ; 

  body.appendChild(linkp()) ; 

  table = document.createElement('table') ;
  table.setAttribute('cellspacing','0') ; 
  table.setAttribute('cellpadding','0') ; 
  table.setAttribute('align','center') ; 
  for(s='',ind=0;s==''&&ind<list.length;ind++) 
    if(list[ind].name==undefined) 
      s = "border-bottom:1px solid #444;padding-bottom:4px;" ; 
  table.setAttribute('style',s+'margin:0px auto') ; 

  for(prefetch=null,nload=trflag=colno=ind=0;ind<list.length;ind++)
    if(list[ind].display!='none') 
  { if(0==colno%ncol||list[ind].name==undefined) 
    { if(trflag>0) table.appendChild(tr) ; 
      tr = document.createElement('tr') ; 
      trflag = 0 ; 
    } 
    td = document.createElement('td') ;

    if(list[ind].name==undefined)
    { td.setAttribute("style","border-top:1px solid #444") ; 
      td.setAttribute('align','left') ;  
      td.setAttribute('colspan',ncol) ;  
      p = document.createElement("p") ; 
      p.setAttribute("style","font-size:110%;padding-top:6px;"+
                             "font-family:arial;color:silver") ; 
      if(list[ind].gps==undefined)
        p.appendChild(document.createTextNode(list[ind].title)) ; 
      else
      { p.appendChild(document.createTextNode(list[ind].title+' : ')) ;
        a = document.createElement('a') ; 
        a.setAttribute('href',list[ind].gps) ; 
        a.setAttribute('style','font-size:90%;text-decoration:none') ; 
        a.appendChild(document.createTextNode('[GPS track]')) ;
       p.appendChild(a) ;
      }
      td.appendChild(p) ; 
      tr.appendChild(td) ; 
      table.appendChild(tr) ; 
      tr = document.createElement('tr') ; 
      colno = trflag = 0 ; 
      continue ; 
    } 

    lind = ind ;
    llink = piclink(lind) ;
    if(rind==list.length) { rind = ind ; rlink = piclink(rind) ; }
    // extra padding at the bottom before a title row
    if(0==colno%ncol) 
    { padflag = 4 ; 
      for(num=0,k=ind;k<list.length&&num<ncol&&list[k].name!=undefined;k++) 
        if(list[k].display!='none') num += 1 ;
      if(k<list.length&&num<=ncol) 
      { padflag = 8 ; if(ncol>5) padflag += 2*(ncol-5) ; }
    } 
    td.setAttribute('align','center') ;  
    if(list[ind].display=='|'&&colno%ncol>0)
      td.setAttribute('style','border-left:1px solid #444') ;
    a = document.createElement('a') ; 
    a.setAttribute('href',piclink(ind)) ;
    a.setAttribute("class","box") ; 
    a.setAttribute("title",list[ind].title) ; 
    img = genimage(list[ind],sizes,thind,function() 
            { nload += 1 ; 
              if(nload==nimg&&prefetch!=null) genimage(list[prefetch],sizes) ; 
            } ) ;
    img.setAttribute('border',1) ; 

    // vertical bar: margin is t-r-b-l or t-lr-b
    if(colno%ncol>0&&list[ind].display!='|') 
      img.setAttribute('style',"padding:4px;margin:4px 4px "+padflag+"px 5px") ;
    else img.setAttribute('style',"padding:4px;margin:4px 4px "+padflag+"px") ; 

    a.appendChild(img) ; 
    td.appendChild(a) ; 
    tr.appendChild(td) ; 
    trflag = 1 ; 

    if(prefetch==null) prefetch = ind ;
    colno += 1 ;
  }
  table.appendChild(tr) ;
  body.appendChild(table) ; 
  body.appendChild(linkp()) ; 
}
/* -------------------------------------------------------------------------- */
/*page*/
/* -------------------------------------------------------------------------- */

function pix(h)
{ var ind,query,opts="",k,r,prev,body ;
  thispage = location.href ;
  pagetitle = document.getElementsByTagName("title")[0].textContent ; 
  body = document.getElementsByTagName("body")[0] ; 
  body.setAttribute('style','background:black;margin:0') ; 

  if((typeof links)=='undefined') links = null ;
  if(h!=undefined&&h!=null&&links==null)
    links = [ { name: 'home' , href: h } ] ;

  prevind = null ;
  here = { name:null , ind:null , ncol:0 , full:0 } ;

  if((ind=thispage.lastIndexOf('/'))>=0) thispage = thispage.substring(ind+1) ; 

  // set up the array nitem containing the number of items in each block
  for(nitem=[],ind=-1;ind<list.length;ind=k) 
  { for(k=ind+1;k<list.length&&list[k].name!=undefined;k++) ; 
    if(k>ind+1) nitem.push(k-(ind+1)) ; 
  }

  maxthumb = setthumbshape(list,sizes,thumbshape,imagedir) ; 

  if((ind=thispage.indexOf('?'))>=0)
  { query = thispage.substring(ind+1) ;      // 'image=garden&mode=n'
    thispage = thispage.substring(0,ind) ; 
    if(query.substring(0,6)=='image=')
    { query = query.substring(6) ; 
      if((ind=query.indexOf('&mode='))>=0)
      { opts = query.substring(ind+6) ; query = query.substring(0,ind) ; }
    }
    else if(query.substring(0,5)=='mode=')
    { opts = query.substring(5) ; query = null ; }
    else if(query=='') query = null ; 
    else
    { if((ind=query.indexOf('+'))>=0) 
      { opts = query.substring(ind+1) ; query = query.substring(0,ind) ; }
      thispage += '?image=' + query ;
      location.href = thispage + (opts==''?'':('&mode='+opts)) ; 
    }
  } 
  else query = null ; 

  if(query==null) ind = -1 ; 
  else for(ind=0;ind<list.length&&(list[ind].name!=query);ind++) ;

  for(k=0;
      k<list.length&&(list[k].name==undefined||list[k].retpage==undefined);
      k++) ;
  if(k<list.length) retpage = k ; else retpage = null ;

  if(ind>=0&&ind<list.length) // return to table if there is no retpage
  { // all the following code is finding whether we're stepping backwards
    if((k=document.referrer.lastIndexOf('/'))>=0)
    { prev = document.referrer.substring(k+1) ; k = prev.indexOf('?') ; }
    if(k>=0&&prev.substring(k+1,5)!='mode=')
    { prev = prev.substring(k+1) ; 
      k = prev.indexOf('&') ;
      if(k>=0) prev = prev.substring(0,k) ;
      for(k=0;k<list.length&&list[k].name!=prev;k++) ;
      if(k<list.length) prevind = k ;
    }
    else if(thispage==document.referrer) // came from table
    { for(k=ind+1;k<list.length&&list[ind].name==undefined;k++) ;
      if(k==list.length) prevind = list.length ;
    }
  }

  window.onresize = pixresize ;
  document.onkeydown = navigate ; 
  document.addEventListener('touchstart',startswipe,false) ;        
  document.addEventListener('touchmove',midswipe,false) ;        
  document.addEventListener('touchend',endswipe,false) ;
  if(ind<0||ind>=list.length) { fromnotes = 0 ; tabulate() ; }
  else { fromnotes = (opts.charAt(0)=='n'&&retpage!=null) ; display(query) ; }
}
/* -------------------------------------------------------------------------- */
/*page*/
/* ---------------------------------- swipes -------------------------------- */

function simulate(btn)
{ navigate({keyCode:btn,preventDefault:function(){}}) ; }

var xloc=null,yloc=null,xstart=null,ystart=null,fingersep=null,startsep=null ; 
var swipetime=null ;

function startswipe(e) 
{ var x0=e.touches[0].clientX,y0=e.touches[0].clientY,x1,y1 ;
  if(e.touches.length==1) 
  { xstart = x0 ; ystart = y0 ; swipetime = new Date().getTime() ; } 
  else if(e.touches.length==2&&here.ncol==0)
  { e.preventDefault() ;        // don't let iOS take control of pinches
    x1 = e.touches[1].clientX ;
    y1 = e.touches[1].clientY ;
    startsep = Math.sqrt((x1-x0)*(x1-x0)+(y1-y0)*(y1-y0)) ;
  }
}
function midswipe(e) 
{ var x0=e.touches[0].clientX,y0=e.touches[0].clientY,x1,y1 ;
  if(e.touches.length==1) 
  { xloc = x0 ; yloc = y0 ; fingersep = startsep = null ; } 
  else if(e.touches.length==2&&here.ncol==0)
  { e.preventDefault() ; 
    x1 = e.touches[1].clientX ;
    y1 = e.touches[1].clientY ;
    fingersep = Math.sqrt((x1-x0)*(x1-x0)+(y1-y0)*(y1-y0)) ;
    xloc = yloc = xstart = ystart = swipetime = null ; 
  }
}
function endswipe(e) 
{ var v ;
  if(fingersep!=null&&startsep!=null&&here.ncol==0)
  { e.preventDefault() ; 
    if(fingersep>startsep+50) simulate(38) ; 
    else if(startsep>fingersep+50) simulate(40) ; 
  }
  else if(xloc!=null&&yloc!=null&&xstart!=null&&ystart!=null&&swipetime!=null)
  { swipetime = new Date().getTime() - swipetime ; 
    v = Math.sqrt((xloc-xstart)*(xloc-xstart)+(yloc-ystart)*(yloc-ystart)) ;
    if(v>0.65*swipetime)
    { e.preventDefault() ; 
      if(Math.abs(xloc-xstart)>100&&Math.abs(yloc-ystart)<100)
      { if(xloc>xstart) simulate(37) ; else simulate(39) ; }
      else if(yloc>ystart+100&&Math.abs(xloc-xstart)<100) simulate(13) ; 
    }
  }
  xloc = yloc = xstart = ystart = fingersep = startsep = swipetime = null ; 
}

Archived from pix.html

// www.masterlyinactivity.com/software/pix.html
/* The MIT License

   Copyright (c) 2016, by Colin Champion <colin.champion@masterlyinactivity.com>

   Permission is hereby granted, free of charge, to any person obtaining
   a copy of this software and associated documentation files (the
   "Software"), to deal in the Software without restriction, including
   without limitation the rights to use, copy, modify, merge, publish,
   distribute, sublicense, and/or sell copies of the Software, and to
   permit persons to whom the Software is furnished to do so, subject to
   the following conditions:

   The above copyright notice and this permission notice shall be
   included in all copies or substantial portions of the Software.

   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
   MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
   NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
   BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
   ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
   CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
   SOFTWARE.
*/
var pixlib = 1 ; 

/* -------------------------------------------------------------------------- */

function enterfullscreen() 
{ if(document.documentElement.requestFullscreen) 
    document.documentElement.requestFullscreen() ;
  else if(document.documentElement.mozRequestFullScreen) 
    document.documentElement.mozRequestFullScreen() ;
  else if(document.documentElement.webkitRequestFullscreen) 
    document.documentElement.webkitRequestFullscreen() ;
  else if(document.documentElement.msRequestFullscreen) 
    document.documentElement.msRequestFullscreen() ;
}
function queryfullscreen() 
{ if(document.fullScreen||document.mozFullScreen||document.webkitIsFullScreen)
    return 1 ; 
  else return 0 ;
}
function querycanfullscreen()
{ if ( document.documentElement.requestFullscreen
    || document.documentElement.mozRequestFullScreen
    || document.documentElement.webkitRequestFullscreen 
    || document.documentElement.msRequestFullscreen ) return 1 ; 
  else return 0 ;
}
/* -------------------------------------------------------------------------- */

function thumbind(sizes)
{ var ind ;
  for(ind=0;ind<sizes.length&&sizes[ind].type!='thumb';ind++) ;
  if(ind==sizes.length) { alert('no sizes entry of type "thumb"') ; throw '' ; }
  return ind ;
}
/* -------------------------------------------------------------------------- */

function retlink(list,ind)
{ var i,k,blink ;
  for(i=ind;i>=0&&list[i].retpage==undefined;i--) ; 
  if(i<0) return null ; ; 
  blink = list[i].retpage+'.html' ;
  for(k=ind;k>=i&&list[k].retid==undefined;k--) ;
  if(k>=i) blink += '#' + list[k].retid ;
  return blink ;
}
/* -------------------------------------------------------------------------- */
/*page*/
/* ---- fill in missing thumbs and raws, return maximum thumb dimensions ---- */

function setthumbshape(list,sizes,thumbshape,imagedir,hithumb)
{ var ind,r0,r1,maxthumb,k,umax,doraw ;
  if(typeof thumbshape=='undefined'||thumbshape==undefined) thumbshape = null ; 
  if(typeof imagedir=='undefined'||imagedir==undefined) imagedir = null ; 
  if(typeof hithumb=='undefined'||hithumb==undefined) hithumb = null ; 

  ind = thumbind(sizes) ; 
  if(thumbshape==null||thumbshape.length!=2)
  { if(sizes[ind].scale>0) r0 = sizes[ind].scale / sizes[0].scale ;
    else { alert('thumbs have size '+sizes[ind].scale) ; throw '' ; }
  }

  for(doraw=r1=ind=0;ind<sizes.length&&sizes[ind].type!='raw';ind++) ;
  if(ind<sizes.length) { r1 = sizes[ind].scale / sizes[0].scale ; doraw = 1 ; }

  for(maxthumb=[0,0],ind=0;ind<list.length;ind++) if(list[ind].name!=undefined) 
  { if(list[ind].thumbshape==undefined)
    { if(thumbshape!=null&&thumbshape.length==2) 
        list[ind].thumbshape = thumbshape ;
      else for(list[ind].thumbshape=[0,0],k=0;k<2;k++) 
        list[ind].thumbshape[k] = Math.floor(0.5+list[ind].shape[k]*r0) ; 
    }
    else if(list[ind].hithumb==undefined) list[ind].hithumb = hithumb ;
    for(k=0;k<2;k++) if(list[ind].thumbshape[k]>maxthumb[k]) 
      maxthumb[k] = list[ind].thumbshape[k] ; 
    if(doraw&&list[ind].rawshape==undefined)
      for(list[ind].rawshape=[0,0],k=0;k<2;k++) 
        list[ind].rawshape[k] = Math.floor(0.5+list[ind].shape[k]*r1) ; 
    if(imagedir==null) list[ind].filename = list[ind].name ;
    else list[ind].filename = imagedir + '/' + list[ind].name ;
  }

  // fill in the fontsize field in sizes
  for(R=k=ind=0;ind<sizes.length;ind++) if(sizes[ind].type==undefined) 
  { R += Math.log(sizes[ind].scale) ; k += 1 ; }
  R /= k ;

  for(ind=0;ind<sizes.length;ind++) 
    if(sizes[ind].type==undefined&&sizes[ind].fontsize==undefined) 
      sizes[ind].fontsize = 16 * Math.exp((Math.log(sizes[ind].scale)-R)/3) ;

  for(umax=null,ind=0;ind<sizes.length;ind++) if(sizes[ind].type==undefined)
    if(umax==null||sizes[ind].fontsize>umax) umax = sizes[ind].fontsize ;
  for(ind=0;ind<sizes.length;ind++) 
    if(sizes[ind].type=='raw'&&sizes[ind].fontsize==undefined)
      sizes[ind].fontsize = umax ; 

  return maxthumb ;
}
/* ----------------------------- image functions ---------------------------- */

function imgshape(item,sizes,sizeno)
{ if(sizes[sizeno].type=='thumb') return item.thumbshape ; 
  else if(sizes[sizeno].type=='raw') return item.rawshape ; 
  return [ Math.floor(0.5+item.shape[0]*sizes[sizeno].scale/sizes[0].scale) ,
           Math.floor(0.5+item.shape[1]*sizes[sizeno].scale/sizes[0].scale) ] ;
}
function imgsize(item,sizes,sizeno) { return imgshape(item,sizes,sizeno) ; } 

/* -------------------------------------------------------------------------- */
/*page*/
/* ----- sparepix finds the margins left if item is displayed at usesize ---- */

function sparepix(item,sizes,sizeno)
{ var shape=imgsize(item,sizes,sizeno),w=shape[0],h=shape[1],r ;
  h += Math.floor(0.5+1.25*sizes[sizeno].fontsize) + 2 ;
  if(item.caption!=undefined) h += Math.floor(0.5+sizes[sizeno].fontsize) + 2 ;

  r = [ window.innerHeight-50-h , window.innerHeight-h ] ;
  if(window.innerWidth-w<r[0]) r[0] = window.innerWidth-w ;
  if(window.innerWidth-w-50<r[1]) r[1] = window.innerWidth-w-50 ;
  return r ;
}
/* ---------------------- construct the jpg path name ----------------------- */

function jpg(item,sizes,sizeno)
{ return item.filename + sizes[sizeno].suffix + '.jpg' ; }

/* ------------------- generate the srcset for an image --------------------- */

function srcset(item,sizes,sizeno)
{ var i,s,hith,scale ;
  if(sizes[sizeno].type=='raw') return '' ;

  if(sizes[sizeno].type=='thumb') 
  { hith = item.hithumb ;
    if(hith!=undefined&&hith!=null )
      return item.filename + hith.suffix + '.jpg ' + hith.scale + 'x' ;
    if( item.thumbshape[0]*item.shape[1] != item.thumbshape[1]*item.shape[0] ) 
      return '' ;
    scale = ( sizes[0].scale * item.thumbshape[0] ) / item.shape[0] ;
  }
  else scale = sizes[sizeno].scale ;

  for(s='',i=0;i<sizes.length;i++) if(sizes[i].type==undefined)
    if(sizes[i].scale>scale)
  { if(s!='') s += ', ' ;
    s += jpg(item,sizes,i) + ' ' +
           (sizes[i].scale/scale).toFixed(1) + "x" ;
  }
  return s ;
}
/* -------------------------------------------------------------------------- */

function preload(item,sizes,sizeno,loadaction) 
{ return genimage(item,sizes,sizeno,loadaction) ; }

/* ------- getsize finds the largest image size which fits the screen ------- */

function getsize(item,sizes,loadstatus,thresh) 
{ var i,ibest,ismall,spare ;

  for(ismall=ibest=null,i=0;i<sizes.length;i++) 
    if(sizes[i].type==undefined&&(loadstatus==undefined||loadstatus[i]>=thresh))
  { if(ismall==null||sizes[i].scale<sizes[ismall].scale) ismall = i ; 
    spare = sparepix(item,sizes,i) ;
    if(spare[0]>=0||spare[1]>=0)
      if(ibest==null||sizes[i].scale>sizes[ibest].scale) ibest = i ; 
  }
  if(ibest==null) return ismall ; else return ibest ;
}
/* -------------------------------------------------------------------------- */
/*page*/
/* --------------------------------- genimage ------------------------------- */

function genimage(item,sizes,sizeno,loadfunc) 
{ var img=document.createElement('img') , shape , s ;
  if(sizeno==undefined||sizeno==null||sizeno<0) sizeno = getsize(item,sizes) ;
  shape = imgshape(item,sizes,sizeno) ; 
  img.setAttribute('width',shape[0]) ; 
  img.setAttribute('height',shape[1]) ; 
  if((s=srcset(item,sizes,sizeno))!='') img.setAttribute("srcset",s) ; 
  if(loadfunc!=undefined&&loadfunc!=null) img.onload = loadfunc ;
  img.setAttribute("src",jpg(item,sizes,sizeno)) ; 
//  console.log(img.src+(s==""?"":' ['+s+']')) ;
  return img ;
}
/* ------- setimgpos sets the image position according to window size ------- */

function setimgpos(parms,shape)
{ var imgw=shape[0],imgh=shape[1],x,y,s,overflowx,overflowy ;
  var pad=parms.headh + parms.caph ;
  // set space to the amount of room we've got for the image div
  var space = [ window.innerWidth , window.innerHeight-pad ] ;
  if(parms.portrait) space[0] -= 50 ; else space[1] -= 50 ; 

  // set imgw and imgh to the desired dimensions of the image div
  if(imgh>space[1]) imgw += 20 ; // generous scrollbar
  if(imgw>space[0]) imgh += 20 ; 

  // set (x,y) to the coords of the main div
  x = (window.innerWidth-imgw)/2 ;
  y = (window.innerHeight-imgh)/2 - parms.headh;
  if(parms.portrait) { if(x<50) x = 50 ; if(y<0) y = 0 ; }
  else { if(x<0) x = 0 ; if(y<50) y = 50 ; } 
  x = Math.floor(x) ; 
  y = Math.floor(y) ; 

  // now constrain imgw, imgh by the window size
  if(imgw>window.innerWidth-x) imgw = window.innerWidth - x ;
  if(imgh>window.innerHeight-y-pad) imgh = window.innerHeight-y-pad ;

  s = 'position:absolute;left:'+x+'px;width:'+(window.innerWidth-x)+'px;top:' ;
  s += y+'px;height:'+(pad+imgh)+'px;overflow:hidden' ;
  parms.maindiv.setAttribute('style',s) ; 

  s = 'font-family:arial;color:silver;position:absolute;' ;
  s += 'left:0;width:'+(window.innerWidth-x)+'px;top:0;' ;
  s += 'height:'+parms.headh+'px;overflow:hidden;font-size:'+parms.headf+'px' ;
  parms.headdiv.setAttribute('style',s) ; 

  if(parms.capdiv!=null)
  { s = 'font-family:arial;color:silver;position:absolute;' ;
    s += 'left:0;width:'+(window.innerWidth-x)+'px;bottom:0;' ;
    s += 'height:'+parms.caph+'px;overflow:hidden;font-size:'+parms.capf+'px' ;
    parms.capdiv.setAttribute('style',s) ; 
  }

  if(imgw<shape[0]) overflowx = 'scroll' ; else overflowx = 'hidden' ; 
  if(imgh<shape[1]) overflowy = 'scroll' ; else overflowy = 'hidden' ; 
  s = 'position:absolute;left:0;width:'+imgw+'px;top:'+parms.headh+'px;' ;
  s += 'height:'+imgh+'px;overflow-x:'+overflowx+';overflow-y:'+overflowy ;
  parms.imgdiv.setAttribute('style',s) ; 
}
/* -------------------------------------------------------------------------- */
/*page*/
/* -------------------- find the list ind with a given name ----------------- */

function findimage(list,name)
{ var ind ; 
  for(ind=0;ind<list.length;ind++) if(list[ind].name==name) return ind ; 
  return null ;
}
/* ---- adjust element attributes in accordance with a change of img size --- */

function setimg(parms,item,sizes,sizeno,fetchitem,preloadstatus)
{ var font,spare,shape,img,s,i,fetcher ; 

  if(sizeno==null) return ;
  enredcell(parms.enlink,sizes,sizeno,2) ;
  enredcell(parms.redlink,sizes,sizeno,-1) ;

  spare = sparepix(item,sizes,sizeno) ;
  shape = imgsize(item,sizes,sizeno) ;
  font = sizes[sizeno].fontsize ;
  parms.headh = 2 + Math.floor(0.5+1.25*font) ;
  parms.headf = Math.floor(0.5+font) ;
  if(item.caption==undefined) parms.caph = parms.capf = 0 ; 
  else
  { parms.caph = 2+Math.floor(0.5+font) ; 
    parms.capf = Math.floor(0.5+0.8*font) ;
  }

  parms.portrait = spare[0]<spare[1] ;

  // create the image for the new size
  fetcher = null ; 
  if(fetchitem!=undefined&&fetchitem!=null) 
    if(preloadstatus[i=getsize(fetchitem,sizes)]==0)  
  { preloadstatus[i] = 1 ; 
    fetcher = function() { genimage(fetchitem,sizes,i) ; } ;
  }
  img = genimage(item,sizes,sizeno,fetcher) ; 

  if(parms.img==null) parms.imgdiv.appendChild(img) ;
  else parms.imgdiv.replaceChild(img,parms.img) ;
  parms.img = img ; 
  setimgpos(parms,shape) ;
  return sizeno ;
}
/* -------------------------------------------------------------------------- */
/*page*/
/* ------------ rescale finds the next larger/next smaller image ------------ */

function rescale(sizes,sizeno,dir)
{ var i,ind ;

  if(dir<0) 
  { for(ind=null,i=0;i<sizes.length;i++) if(sizes[i].type==undefined)
      if(sizes[i].scale<sizes[sizeno].scale||sizes[sizeno].type=='raw')
        if(ind==null||sizes[i].scale>sizes[ind].scale) ind = i ;
    return ind ;
  }

  if(sizes[sizeno].type=='raw') return null ; 
  for(ind=null,i=0;i<sizes.length;i++) 
    if(sizes[i].type==undefined||(sizes[i].type=='raw'&&dir==2))
      if( sizes[i].scale>sizes[sizeno].scale || sizes[i].type=='raw' )
  { if(ind==null) ind = i ; 
    else if(sizes[ind].type=='raw') ind = i ; 
    else if(sizes[i].scale<sizes[ind].scale&&sizes[i].type!='raw') ind = i ;
  }
  return ind ;
}
/* --------------- reposition image in response to a window resize ---------- */

function resizesub(iparms,dparms)
{ var k2,k0,dir,flag=0,shape,fetch,item=iparms.item,sizes=iparms.sizes ;
  var sizeno=iparms.sizeno ;
  k2 = getsize(item,sizes,iparms.loadstatus,2) ; 
  k0 = getsize(item,sizes) ; 

  // there's a better size already loaded
  if(iparms.holdsize==0&&k2!=sizeno) 
  { setimg(dparms,item,sizes,k2,iparms.fetchitem,iparms.preloadstatus) ; 
    iparms.sizeno = k2 ;
    return ; 
  } 
  // there's a better size not yet loaded
  else if(iparms.holdsize==0&&k0!=sizeno&&iparms.loadstatus[k0]==0) flag = 1 ; 
  else if(iparms.holdsize!=0&&k0==sizeno) iparms.holdsize = 0 ;
  else if(iparms.holdsize==0&&iparms.window!=null)
  { if( window.innerWidth>=iparms.window[0]
     && window.innerHeight>=iparms.window[1] ) dir = 1 ;
    else if( window.innerWidth<=iparms.window[0]
          && window.innerHeight<=iparms.window[1] ) dir = -1 ;
    else dir = null ; 
    if(dir!=null&&(k0=rescale(sizes,sizeno,dir))!=null) 
      if(iparms.loadstatus[k0]==0) flag = 1 ; 
  }
  if(flag)
  { iparms.window = null ;
    iparms.loadstatus[k0] = 1 ; 
    genimage(item,sizes,k0,genloadhandler(iparms.loadstatus,k0)) ; 
  }
  // redisplay if portrait<->landscape makes a fit possible, else redraw
  shape = imgsize(item,sizes,sizeno) ;
  spare = sparepix(item,sizes,sizeno) ;
  if(dparms.portrait!=(spare[0]<spare[1])&&(spare[0]<0)!=(spare[1]<0))
    dparms.portrait = 1-dparms.portrait ;
  setimgpos(dparms,imgsize(item,sizes,sizeno)) ;
}
/* -------------------------------------------------------------------------- */
/*page*/
/* ---------------- create a div containing an image title ------------------ */

function maketextdiv(title)
{ var div=document.createElement('div') ;
  div.appendChild(document.createTextNode(title)) ; 
  div.setAttribute('style','font-family:arial;color:silver') ;
  return div ; 
}
/* --------------------- create/reset enlarge/reduce icon ------------------- */

function enredcell(td,sizes,sizeno,dir)
{ var a,astyle ;
  astyle = 'font-family:arial;font-weight:normal;text-decoration:none;' ;
  while(td.firstChild) td.removeChild(td.firstChild) ;

  if(rescale(sizes,sizeno,dir)!=null) 
  { a = document.createElement('a') ; 
    a.setAttribute('href',dir>0?'javascript:enlarge()':'javascript:reduce()') ; 
    a.setAttribute("title",dir>0?"enlarge [\u2191 key]":"reduce [\u2193 key]") ; 
    a.setAttribute("style",astyle) ; 
    a.appendChild(document.createTextNode(dir>0?'\u2295':'\u2296')) ;
    td.appendChild(a) ; 
  }
}
/* --------------------- create a cell for navigation icon ------------------ */

function navcell(link,dir,string)
{ var a,td=document.createElement('td'),s='center',astyle ;
  if(dir=='l') s = 'left' ; else if (dir=='r') s = 'right' ;
  astyle = 'font-family:arial;font-weight:normal;text-decoration:none;' ;
  s = "text-align:" + s + (dir==0?'':';font-size:20px;') ;
  s += 'text-align:center;vertical-align:middle;font-size:16px;' +
                'line-height:16px;width:16px;height:16px' ;
  td.setAttribute("style",s) ; 
  if(dir!=0) td.setAttribute("rowspan",3) ; 

  if(link!=null) 
  { a = document.createElement('a') ; 
    a.setAttribute('href',link) ; 
    if(dir>0) s = "next [\u2192 key]" ; 
    else if(dir==0) s = "back to " + string + " [\u21b5 key]" ;
    else s = "prev [\u2190 key]" ;
    a.setAttribute("title",s) ; 
    a.setAttribute("style",astyle) ; 
    if(dir>0) s = '>' ; else if(dir==0) s = '\u21b5' ; else s = '<' ;
    a.appendChild(document.createTextNode(s)) ;
    td.appendChild(a) ; 
  }
  return td ; 
}
/* -------------------------------------------------------------------------- */

var maxthumb,thumbshape,imagedir,hithumb,thind=null,sfx=null ;

function thumb(name) 
{ var ind,s ; 
  if(maxthumb==undefined||maxthumb==null) 
    maxthumb = setthumbshape(list,sizes,thumbshape,imagedir,hithumb) ;
  if(thind==null) thind = thumbind(sizes) ;
  if(sfx==null) sfx = sizes[thind].suffix ;

  if(null==(ind=findimage(list,name))) alert('Missing image: '+name) ;
  s = srcset(list[ind],sizes,thind) ;
  document.write('<a href="'+pixpage+'?image='+name+'&mode=n">'+
       '<img class=pix src="'+
       jpg(list[ind],sizes,thind)+'" width='+list[ind].thumbshape[0]+
       ' height='+list[ind].thumbshape[1]+' title="'+list[ind].title+
       ((s==''||s==null)?'':('" srcset="'+s)) + '"></a>') ; 
}
/* -------------------------------------------------------------------------- */

var drawparms=null,itemparms=null ; 

function setsize(k)  
{ if(itemparms==null||(k=rescale(itemparms.sizes,itemparms.sizeno,k))==null) 
    return ;
  itemparms.sizeno = setimg(drawparms,itemparms.item,itemparms.sizes,k) ; 
  itemparms.holdsize = 1 ;  
}
function genloadhandler(loadstatus,k) 
{ return function() { if(itemparms!=null) { loadstatus[k] = 2 ; resize() ; } } ;
}
function resize() { if(itemparms!=null) resizesub(itemparms,drawparms) ; } 
function enlarge() { setsize(2) ; }
function reduce() { setsize(-1) ; }

/* -------------------------------------------------------------------------- */

function gendisplay(element,item,sizes,llink,blink,rlink,fromstring,fetchitem) 
{ var i,sizeno,captioned,table,tr,td,a,utd,dtd ; 
  if(element==undefined||element==null) 
  { itemparms = drawparms = null ; return ; }
  sizeno = getsize(item,sizes) ;
  if(item.caption==undefined) captioned = 0 ; else captioned = 1 ; 

  itemparms = { item:          item,
                sizes:         sizes,
                sizeno:        sizeno,
                holdsize:      0,
                loadstatus:    new Array(sizes.length),
                fetchitem:     fetchitem,
                preloadstatus: new Array(sizes.length),
                window:        [ window.innerWidth , window.innerHeight ] 
              } ;

  for(i=0;i<sizes.length;i++) 
    itemparms.loadstatus[i] = itemparms.preloadstatus[i] = 0 ;
  itemparms.loadstatus[sizeno] = 2 ;

  // make the navigation table
  table = document.createElement('table') ;
  table.setAttribute('cellpadding',0) ; 
  table.setAttribute('cellspacing',0) ; 

  // the '<' link
  tr = document.createElement('tr') ;
  tr.appendChild(navcell(llink,-1)) ; 

  // the enlarge link
  utd = document.createElement('td') ;
  utd.setAttribute("style",'text-align:center;font-size:16px;' +
                           'line-height:16px;width:16px;height:16px') ; 
  tr.appendChild(utd) ; 
  table.appendChild(tr) ; 

  // the '>' link
  tr.appendChild(navcell(rlink,1)) ; 
  table.appendChild(tr) ; 

  // the return link
  tr = document.createElement('tr') ;
  tr.appendChild(navcell(blink,0,fromstring)) ; 
  table.appendChild(tr) ; 

  // the reduce link
  tr = document.createElement('tr') ;
  dtd = document.createElement('td') ;
  dtd.setAttribute("style",'text-align:center;font-size:16px;' +
                          'line-height:16px;width:16px;height:16px') ; 
  tr.appendChild(dtd) ; 
  table.appendChild(tr) ; 

  drawparms = { headh:     null, 
                caph:      null, 
                headf:     null, 
                capf:      null, 
                enlink:    utd,
                redlink:   dtd,
                img:       null,
                portrait:  null,
                maindiv:   document.createElement('div'), 
                headdiv:   maketextdiv(item.title),
                imgdiv:    document.createElement('div'),
                capdiv:    captioned==0?null:maketextdiv(item.caption)
              } ;

  setimg(drawparms,item,sizes,sizeno,fetchitem,itemparms.preloadstatus) ; 

  drawparms.imgdiv.appendChild(drawparms.img) ;
  drawparms.maindiv.appendChild(drawparms.headdiv) ; 
  drawparms.maindiv.appendChild(drawparms.imgdiv) ; 
  if(captioned) drawparms.maindiv.appendChild(drawparms.capdiv) ; 

  while(element.firstChild) element.removeChild(element.firstChild) ;
  element.appendChild(table) ; 
  element.appendChild(drawparms.maindiv) ; 
}
/* -------------------------------------------------------------------------- */

function pixinfodiv(list,i,sizes)
{ var d=document.createElement('div'),ind,hind,r,k,shape,s ;

  s = 'Name: ' + list[i].name ;
  if(list[i].display=='none') s += ' [hidden]' ;
  d.appendChild(document.createTextNode(s)) ;
  d.appendChild(document.createElement('br')) ;

  d.appendChild(document.createTextNode('Title: '+list[i].title)) ;
  d.appendChild(document.createElement('br')) ;

  for(hind=null,ind=0;ind<i;ind++) if(list[ind].name==null) hind = ind ;
  if(hind!=null) 
  { d.appendChild(document.createTextNode('Under: '+list[hind].title)) ;
    d.appendChild(document.createElement('br')) ;
  }

  // how many shapes?
  for(r=ind=0;ind<sizes.length;ind++) if(sizes[ind].type==undefined) r += 1 ;
  s = 'Available in ' + r + ' size' + (r>1?'s: ':': ') ;

  // print the shapes
  for(k=ind=0;ind<sizes.length;ind++) if(sizes[ind].type==undefined)
  { if(k>0) { if(k==r-1) s += ' and ' ; else s += ', ' ; }
    shape = imgsize(list[i],sizes,ind) ;
    s += shape[0] + 'x' + shape[1] ;
    k += 1 ; 
  }
  d.appendChild(document.createTextNode(s)) ;
  d.appendChild(document.createElement('br')) ;

  // print the thumb shape
  shape = list[i].thumbshape ;
  s = 'Thumb: ' + shape[0] + 'x' + shape[1] ;
  hith = list[i].hithumb ;
  if(hith!=undefined&&hith!=null )
    s += '(hi-res: '+ shape[0]*hith.scale + 'x' + shape[1]*hith.scale + ')' ;
  d.appendChild(document.createTextNode(s)) ;
  d.appendChild(document.createElement('br')) ;

  // print the raw shape
  for(ind=0;ind<sizes.length;ind++) if(sizes[ind].type=='raw')
  { shape = imgsize(list[i],sizes,ind) ;
    s = 'Raw: ' + shape[0] + 'x' + shape[1] ;
    d.appendChild(document.createTextNode(s)) ;
    d.appendChild(document.createElement('br')) ;
  }
  return d ; 
}

Archived from pix.html

// www.masterlyinactivity.com/software/pix.html
/* The MIT License

   Copyright (c) 2016, by Colin Champion <colin.champion@masterlyinactivity.com>

   Permission is hereby granted, free of charge, to any person obtaining
   a copy of this software and associated documentation files (the
   "Software"), to deal in the Software without restriction, including
   without limitation the rights to use, copy, modify, merge, publish,
   distribute, sublicense, and/or sell copies of the Software, and to
   permit persons to whom the Software is furnished to do so, subject to
   the following conditions:

   The above copyright notice and this permission notice shall be
   included in all copies or substantial portions of the Software.

   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
   MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
   NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
   BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
   ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
   CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
   SOFTWARE.
*/
var pixlib = 1 ; 

/* -------------------------------------------------------------------------- */

function enterfullscreen() 
{ if(document.documentElement.requestFullscreen) 
    document.documentElement.requestFullscreen() ;
  else if(document.documentElement.mozRequestFullScreen) 
    document.documentElement.mozRequestFullScreen() ;
  else if(document.documentElement.webkitRequestFullscreen) 
    document.documentElement.webkitRequestFullscreen() ;
  else if(document.documentElement.msRequestFullscreen) 
    document.documentElement.msRequestFullscreen() ;
}
function queryfullscreen() 
{ if(document.fullScreen||document.mozFullScreen||document.webkitIsFullScreen)
    return 1 ; 
  else return 0 ;
}
function querycanfullscreen()
{ if ( document.documentElement.requestFullscreen
    || document.documentElement.mozRequestFullScreen
    || document.documentElement.webkitRequestFullscreen 
    || document.documentElement.msRequestFullscreen ) return 1 ; 
  else return 0 ;
}
/* -------------------------------------------------------------------------- */

function thumbind(sizes)
{ var ind ;
  for(ind=0;ind<sizes.length&&sizes[ind].type!='thumb';ind++) ;
  if(ind==sizes.length) { alert('no sizes entry of type "thumb"') ; throw '' ; }
  return ind ;
}
/* -------------------------------------------------------------------------- */

function retlink(list,ind)
{ var i,k,blink ;
  for(i=ind;i>=0&&list[i].retpage==undefined;i--) ; 
  if(i<0) return null ; ; 
  blink = list[i].retpage+'.html' ;
  for(k=ind;k>=i&&list[k].retid==undefined;k--) ;
  if(k>=i) blink += '#' + list[k].retid ;
  return blink ;
}
/* -------------------------------------------------------------------------- */
/*page*/
/* ---- fill in missing thumbs and raws, return maximum thumb dimensions ---- */

function setthumbshape(list,sizes,thumbshape,imagedir,hithumb)
{ var ind,r0,r1,maxthumb,k,umax,doraw ;
  if(typeof thumbshape=='undefined'||thumbshape==undefined) thumbshape = null ; 
  if(typeof imagedir=='undefined'||imagedir==undefined) imagedir = null ; 
  if(typeof hithumb=='undefined'||hithumb==undefined) hithumb = null ; 

  ind = thumbind(sizes) ; 
  if(thumbshape==null||thumbshape.length!=2)
  { if(sizes[ind].scale>0) r0 = sizes[ind].scale / sizes[0].scale ;
    else { alert('thumbs have size '+sizes[ind].scale) ; throw '' ; }
  }

  for(doraw=r1=ind=0;ind<sizes.length&&sizes[ind].type!='raw';ind++) ;
  if(ind<sizes.length) { r1 = sizes[ind].scale / sizes[0].scale ; doraw = 1 ; }

  for(maxthumb=[0,0],ind=0;ind<list.length;ind++) if(list[ind].name!=undefined) 
  { if(list[ind].thumbshape==undefined)
    { if(thumbshape!=null&&thumbshape.length==2) 
        list[ind].thumbshape = thumbshape ;
      else for(list[ind].thumbshape=[0,0],k=0;k<2;k++) 
        list[ind].thumbshape[k] = Math.floor(0.5+list[ind].shape[k]*r0) ; 
    }
    else if(list[ind].hithumb==undefined) list[ind].hithumb = hithumb ;
    for(k=0;k<2;k++) if(list[ind].thumbshape[k]>maxthumb[k]) 
      maxthumb[k] = list[ind].thumbshape[k] ; 
    if(doraw&&list[ind].rawshape==undefined)
      for(list[ind].rawshape=[0,0],k=0;k<2;k++) 
        list[ind].rawshape[k] = Math.floor(0.5+list[ind].shape[k]*r1) ; 
    if(imagedir==null) list[ind].filename = list[ind].name ;
    else list[ind].filename = imagedir + '/' + list[ind].name ;
  }

  // fill in the fontsize field in sizes
  for(R=k=ind=0;ind<sizes.length;ind++) if(sizes[ind].type==undefined) 
  { R += Math.log(sizes[ind].scale) ; k += 1 ; }
  R /= k ;

  for(ind=0;ind<sizes.length;ind++) 
    if(sizes[ind].type==undefined&&sizes[ind].fontsize==undefined) 
      sizes[ind].fontsize = 16 * Math.exp((Math.log(sizes[ind].scale)-R)/3) ;

  for(umax=null,ind=0;ind<sizes.length;ind++) if(sizes[ind].type==undefined)
    if(umax==null||sizes[ind].fontsize>umax) umax = sizes[ind].fontsize ;
  for(ind=0;ind<sizes.length;ind++) 
    if(sizes[ind].type=='raw'&&sizes[ind].fontsize==undefined)
      sizes[ind].fontsize = umax ; 

  return maxthumb ;
}
/* ----------------------------- image functions ---------------------------- */

function imgshape(item,sizes,sizeno)
{ if(sizes[sizeno].type=='thumb') return item.thumbshape ; 
  else if(sizes[sizeno].type=='raw') return item.rawshape ; 
  return [ Math.floor(0.5+item.shape[0]*sizes[sizeno].scale/sizes[0].scale) ,
           Math.floor(0.5+item.shape[1]*sizes[sizeno].scale/sizes[0].scale) ] ;
}
function imgsize(item,sizes,sizeno) { return imgshape(item,sizes,sizeno) ; } 

/* -------------------------------------------------------------------------- */
/*page*/
/* ----- sparepix finds the margins left if item is displayed at usesize ---- */

function sparepix(item,sizes,sizeno)
{ var shape=imgsize(item,sizes,sizeno),w=shape[0],h=shape[1],r ;
  h += Math.floor(0.5+1.25*sizes[sizeno].fontsize) + 2 ;
  if(item.caption!=undefined) h += Math.floor(0.5+sizes[sizeno].fontsize) + 2 ;

  r = [ window.innerHeight-50-h , window.innerHeight-h ] ;
  if(window.innerWidth-w<r[0]) r[0] = window.innerWidth-w ;
  if(window.innerWidth-w-50<r[1]) r[1] = window.innerWidth-w-50 ;
  return r ;
}
/* ---------------------- construct the jpg path name ----------------------- */

function jpg(item,sizes,sizeno)
{ return item.filename + sizes[sizeno].suffix + '.jpg' ; }

/* ------------------- generate the srcset for an image --------------------- */

function srcset(item,sizes,sizeno)
{ var i,s="",hith,scale=sizes[sizeno].scale ;

  if(sizes[sizeno].type=='raw') return '' ;
  if(sizes[sizeno].type=='thumb') 
  { hith = sizes[sizeno].hithumb ;
    if(hith!=undefined&&hith!=null )
      return item.filename + hith.suffix + '.jpg ' + hith.scale + 'x, ' ;
    if( item.thumbshape[0]*item.shape[1] != item.thumbshape[1]*item.shape[0] ) 
      return '' ;
    scale *= item.thumbshape[0] / item.shape[0] ;
  }

  for(i=0;i<sizes.length;i++) if(sizes[i].type==undefined)
    if(sizes[i].scale>scale)
      s += jpg(item,sizes,i) + ' ' +
           (sizes[i].scale/scale).toFixed(1) + "x, " ;
  if(s!='') s = s.substring(0,s.length-2) ;
  return s ;
}
/* -------------------------------------------------------------------------- */

function preload(item,sizes,sizeno,loadaction) 
{ return genimage(item,sizes,sizeno,loadaction) ; }

/* ------- getsize finds the largest image size which fits the screen ------- */

function getsize(item,sizes,loadstatus,thresh) 
{ var i,ibest,ismall,spare ;

  for(ismall=ibest=null,i=0;i<sizes.length;i++) 
    if(sizes[i].type==undefined&&(loadstatus==undefined||loadstatus[i]>=thresh))
  { if(ismall==null||sizes[i].scale<sizes[ismall].scale) ismall = i ; 
    spare = sparepix(item,sizes,i) ;
    if(spare[0]>=0||spare[1]>=0)
      if(ibest==null||sizes[i].scale>sizes[ibest].scale) ibest = i ; 
  }
  if(ibest==null) return ismall ; else return ibest ;
}
/* -------------------------------------------------------------------------- */
/*page*/
/* --------------------------------- genimage ------------------------------- */

function genimage(item,sizes,sizeno,loadfunc) 
{ var img=document.createElement('img') , shape , s ;
  if(sizeno==undefined||sizeno==null||sizeno<0) sizeno = getsize(item,sizes) ;
  shape = imgshape(item,sizes,sizeno) ; 
  img.setAttribute('width',shape[0]) ; 
  img.setAttribute('height',shape[1]) ; 
  if((s=srcset(item,sizes,sizeno))!='') img.setAttribute("srcset",s) ; 
  if(loadfunc!=undefined&&loadfunc!=null) img.onload = loadfunc ;
  img.setAttribute("src",jpg(item,sizes,sizeno)) ; 
//  console.log(img.src+(s==""?"":' ['+s+']')) ;
  return img ;
}
/* ------- setimgpos sets the image position according to window size ------- */

function setimgpos(parms,shape)
{ var imgw=shape[0],imgh=shape[1],x,y,s,overflowx,overflowy ;
  var pad=parms.headh + parms.caph ;
  // set space to the amount of room we've got for the image div
  var space = [ window.innerWidth , window.innerHeight-pad ] ;
  if(parms.portrait) space[0] -= 50 ; else space[1] -= 50 ; 

  // set imgw and imgh to the desired dimensions of the image div
  if(imgh>space[1]) imgw += 20 ; // generous scrollbar
  if(imgw>space[0]) imgh += 20 ; 

  // set (x,y) to the coords of the main div
  x = (window.innerWidth-imgw)/2 ;
  y = (window.innerHeight-imgh)/2 - parms.headh;
  if(parms.portrait) { if(x<50) x = 50 ; if(y<0) y = 0 ; }
  else { if(x<0) x = 0 ; if(y<50) y = 50 ; } 
  x = Math.floor(x) ; 
  y = Math.floor(y) ; 

  // now constrain imgw, imgh by the window size
  if(imgw>window.innerWidth-x) imgw = window.innerWidth - x ;
  if(imgh>window.innerHeight-y-pad) imgh = window.innerHeight-y-pad ;

  s = 'position:absolute;left:'+x+'px;width:'+(window.innerWidth-x)+'px;top:' ;
  s += y+'px;height:'+(pad+imgh)+'px;overflow:hidden' ;
  parms.maindiv.setAttribute('style',s) ; 

  s = 'font-family:arial;color:silver;position:absolute;' ;
  s += 'left:0;width:'+(window.innerWidth-x)+'px;top:0;' ;
  s += 'height:'+parms.headh+'px;overflow:hidden;font-size:'+parms.headf+'px' ;
  parms.headdiv.setAttribute('style',s) ; 

  if(parms.capdiv!=null)
  { s = 'font-family:arial;color:silver;position:absolute;' ;
    s += 'left:0;width:'+(window.innerWidth-x)+'px;bottom:0;' ;
    s += 'height:'+parms.caph+'px;overflow:hidden;font-size:'+parms.capf+'px' ;
    parms.capdiv.setAttribute('style',s) ; 
  }

  if(imgw<shape[0]) overflowx = 'scroll' ; else overflowx = 'hidden' ; 
  if(imgh<shape[1]) overflowy = 'scroll' ; else overflowy = 'hidden' ; 
  s = 'position:absolute;left:0;width:'+imgw+'px;top:'+parms.headh+'px;' ;
  s += 'height:'+imgh+'px;overflow-x:'+overflowx+';overflow-y:'+overflowy ;
  parms.imgdiv.setAttribute('style',s) ; 
}
/* -------------------------------------------------------------------------- */
/*page*/
/* -------------------- find the list ind with a given name ----------------- */

function findimage(list,name)
{ var ind ; 
  for(ind=0;ind<list.length;ind++) if(list[ind].name==name) return ind ; 
  return null ;
}
/* ---- adjust element attributes in accordance with a change of img size --- */

function setimg(parms,item,sizes,sizeno,fetchitem,preloadstatus)
{ var font,spare,shape,img,s,i,fetcher ; 

  if(sizeno==null) return ;
  enredcell(parms.enlink,sizes,sizeno,2) ;
  enredcell(parms.redlink,sizes,sizeno,-1) ;

  spare = sparepix(item,sizes,sizeno) ;
  shape = imgsize(item,sizes,sizeno) ;
  font = sizes[sizeno].fontsize ;
  parms.headh = 2 + Math.floor(0.5+1.25*font) ;
  parms.headf = Math.floor(0.5+font) ;
  if(item.caption==undefined) parms.caph = parms.capf = 0 ; 
  else
  { parms.caph = 2+Math.floor(0.5+font) ; 
    parms.capf = Math.floor(0.5+0.8*font) ;
  }

  parms.portrait = spare[0]<spare[1] ;

  // create the image for the new size
  fetcher = null ; 
  if(fetchitem!=undefined&&fetchitem!=null) 
    if(preloadstatus[i=getsize(fetchitem,sizes)]==0)  
  { preloadstatus[i] = 1 ; 
    fetcher = function() { genimage(fetchitem,sizes,i) ; } ;
  }
  img = genimage(item,sizes,sizeno,fetcher) ; 

  if(parms.img==null) parms.imgdiv.appendChild(img) ;
  else parms.imgdiv.replaceChild(img,parms.img) ;
  parms.img = img ; 
  setimgpos(parms,shape) ;
  return sizeno ;
}
/* -------------------------------------------------------------------------- */
/*page*/
/* ------------ rescale finds the next larger/next smaller image ------------ */

function rescale(sizes,sizeno,dir)
{ var i,ind ;

  if(dir<0) 
  { for(ind=null,i=0;i<sizes.length;i++) if(sizes[i].type==undefined)
      if(sizes[i].scale<sizes[sizeno].scale||sizes[sizeno].type=='raw')
        if(ind==null||sizes[i].scale>sizes[ind].scale) ind = i ;
    return ind ;
  }

  if(sizes[sizeno].type=='raw') return null ; 
  for(ind=null,i=0;i<sizes.length;i++) 
    if(sizes[i].type==undefined||(sizes[i].type=='raw'&&dir==2))
      if( sizes[i].scale>sizes[sizeno].scale || sizes[i].type=='raw' )
  { if(ind==null) ind = i ; 
    else if(sizes[ind].type=='raw') ind = i ; 
    else if(sizes[i].scale<sizes[ind].scale&&sizes[i].type!='raw') ind = i ;
  }
  return ind ;
}
/* --------------- reposition image in response to a window resize ---------- */

function resizesub(iparms,dparms)
{ var k2,k0,dir,flag=0,shape,fetch,item=iparms.item,sizes=iparms.sizes ;
  var sizeno=iparms.sizeno ;
  k2 = getsize(item,sizes,iparms.loadstatus,2) ; 
  k0 = getsize(item,sizes) ; 

  // there's a better size already loaded
  if(iparms.holdsize==0&&k2!=sizeno) 
  { setimg(dparms,item,sizes,k2,iparms.fetchitem,iparms.preloadstatus) ; 
    iparms.sizeno = k2 ;
    return ; 
  } 
  // there's a better size not yet loaded
  else if(iparms.holdsize==0&&k0!=sizeno&&iparms.loadstatus[k0]==0) flag = 1 ; 
  else if(iparms.holdsize!=0&&k0==sizeno) iparms.holdsize = 0 ;
  else if(iparms.holdsize==0&&iparms.window!=null)
  { if( window.innerWidth>=iparms.window[0]
     && window.innerHeight>=iparms.window[1] ) dir = 1 ;
    else if( window.innerWidth<=iparms.window[0]
          && window.innerHeight<=iparms.window[1] ) dir = -1 ;
    else dir = null ; 
    if(dir!=null&&(k0=rescale(sizes,sizeno,dir))!=null) 
      if(iparms.loadstatus[k0]==0) flag = 1 ; 
  }
  if(flag)
  { iparms.window = null ;
    iparms.loadstatus[k0] = 1 ; 
    genimage(item,sizes,k0,genloadhandler(iparms.loadstatus,k0)) ; 
  }
  // redisplay if portrait<->landscape makes a fit possible, else redraw
  shape = imgsize(item,sizes,sizeno) ;
  spare = sparepix(item,sizes,sizeno) ;
  if(dparms.portrait!=(spare[0]<spare[1])&&(spare[0]<0)!=(spare[1]<0))
    dparms.portrait = 1-dparms.portrait ;
  setimgpos(dparms,imgsize(item,sizes,sizeno)) ;
}
/* -------------------------------------------------------------------------- */
/*page*/
/* ---------------- create a div containing an image title ------------------ */

function maketextdiv(title)
{ var div=document.createElement('div') ;
  div.appendChild(document.createTextNode(title)) ; 
  div.setAttribute('style','font-family:arial;color:silver') ;
  return div ; 
}
/* --------------------- create/reset enlarge/reduce icon ------------------- */

function enredcell(td,sizes,sizeno,dir)
{ var a,astyle ;
  astyle = 'font-family:arial;font-weight:normal;text-decoration:none;' ;
  while(td.firstChild) td.removeChild(td.firstChild) ;

  if(rescale(sizes,sizeno,dir)!=null) 
  { a = document.createElement('a') ; 
    a.setAttribute('href',dir>0?'javascript:enlarge()':'javascript:reduce()') ; 
    a.setAttribute("title",dir>0?"enlarge [\u2191 key]":"reduce [\u2193 key]") ; 
    a.setAttribute("style",astyle) ; 
    a.appendChild(document.createTextNode(dir>0?'\u2295':'\u2296')) ;
    td.appendChild(a) ; 
  }
}
/* --------------------- create a cell for navigation icon ------------------ */

function navcell(link,dir,string)
{ var a,td=document.createElement('td'),s='center',astyle ;
  if(dir=='l') s = 'left' ; else if (dir=='r') s = 'right' ;
  astyle = 'font-family:arial;font-weight:normal;text-decoration:none;' ;
  s = "text-align:" + s + (dir==0?'':';font-size:20px;') ;
  s += 'text-align:center;vertical-align:middle;font-size:16px;' +
                'line-height:16px;width:16px;height:16px' ;
  td.setAttribute("style",s) ; 
  if(dir!=0) td.setAttribute("rowspan",3) ; 

  if(link!=null) 
  { a = document.createElement('a') ; 
    a.setAttribute('href',link) ; 
    if(dir>0) s = "next [\u2192 key]" ; 
    else if(dir==0) s = "back to " + string + " [\u21b5 key]" ;
    else s = "prev [\u2190 key]" ;
    a.setAttribute("title",s) ; 
    a.setAttribute("style",astyle) ; 
    if(dir>0) s = '>' ; else if(dir==0) s = '\u21b5' ; else s = '<' ;
    a.appendChild(document.createTextNode(s)) ;
    td.appendChild(a) ; 
  }
  return td ; 
}
/* -------------------------------------------------------------------------- */

var maxthumb,thumbshape,imagedir,hithumb,thind=null,sfx=null ;

function thumb(name) 
{ var ind,s ; 
  if(maxthumb==undefined||maxthumb==null) 
    maxthumb = setthumbshape(list,sizes,thumbshape,imagedir,hithumb) ;
  if(thind==null) thind = thumbind(sizes) ;
  if(sfx==null) sfx = sizes[thind].suffix ;

  if(null==(ind=findimage(list,name))) alert('Missing image: '+name) ;
  s = srcset(list[ind],sizes,thind) ;
  document.write('<a href="'+pixpage+'?'+name+'+n"><img class=pix src="'+
       jpg(list[ind],sizes,thind)+'" width='+list[ind].thumbshape[0]+
       ' height='+list[ind].thumbshape[1]+' title="'+list[ind].title+
       ((s==''||s==null)?'':('" srcset="'+s)) + '"></a>') ; 
}
/* -------------------------------------------------------------------------- */

var drawparms=null,itemparms=null ; 

function setsize(k)  
{ if(itemparms==null||(k=rescale(itemparms.sizes,itemparms.sizeno,k))==null) 
    return ;
  itemparms.sizeno = setimg(drawparms,itemparms.item,itemparms.sizes,k) ; 
  itemparms.holdsize = 1 ;  
}
function genloadhandler(loadstatus,k) 
{ return function() { if(itemparms!=null) { loadstatus[k] = 2 ; resize() ; } } ;
}
function resize() { if(itemparms!=null) resizesub(itemparms,drawparms) ; } 
function enlarge() { setsize(2) ; }
function reduce() { setsize(-1) ; }

/* -------------------------------------------------------------------------- */

function gendisplay(element,item,sizes,llink,blink,rlink,fromstring,fetchitem) 
{ var i,sizeno,captioned,table,tr,td,a,utd,dtd ; 
  if(element==undefined||element==null) 
  { itemparms = drawparms = null ; return ; }
  sizeno = getsize(item,sizes) ;
  if(item.caption==undefined) captioned = 0 ; else captioned = 1 ; 

  itemparms = { item:          item,
                sizes:         sizes,
                sizeno:        sizeno,
                holdsize:      0,
                loadstatus:    new Array(sizes.length),
                fetchitem:     fetchitem,
                preloadstatus: new Array(sizes.length),
                window:        [ window.innerWidth , window.innerHeight ] 
              } ;

  for(i=0;i<sizes.length;i++) 
    itemparms.loadstatus[i] = itemparms.preloadstatus[i] = 0 ;
  itemparms.loadstatus[sizeno] = 2 ;

  // make the navigation table
  table = document.createElement('table') ;
  table.setAttribute('cellpadding',0) ; 
  table.setAttribute('cellspacing',0) ; 

  // the '<' link
  tr = document.createElement('tr') ;
  tr.appendChild(navcell(llink,-1)) ; 

  // the enlarge link
  utd = document.createElement('td') ;
  utd.setAttribute("style",'text-align:center;font-size:16px;' +
                           'line-height:16px;width:16px;height:16px') ; 
  tr.appendChild(utd) ; 
  table.appendChild(tr) ; 

  // the '>' link
  tr.appendChild(navcell(rlink,1)) ; 
  table.appendChild(tr) ; 

  // the return link
  tr = document.createElement('tr') ;
  tr.appendChild(navcell(blink,0,fromstring)) ; 
  table.appendChild(tr) ; 

  // the reduce link
  tr = document.createElement('tr') ;
  dtd = document.createElement('td') ;
  dtd.setAttribute("style",'text-align:center;font-size:16px;' +
                          'line-height:16px;width:16px;height:16px') ; 
  tr.appendChild(dtd) ; 
  table.appendChild(tr) ; 

  drawparms = { headh:     null, 
                caph:      null, 
                headf:     null, 
                capf:      null, 
                enlink:    utd,
                redlink:   dtd,
                img:       null,
                portrait:  null,
                maindiv:   document.createElement('div'), 
                headdiv:   maketextdiv(item.title),
                imgdiv:    document.createElement('div'),
                capdiv:    captioned==0?null:maketextdiv(item.caption)
              } ;

  setimg(drawparms,item,sizes,sizeno,fetchitem,itemparms.preloadstatus) ; 

  drawparms.imgdiv.appendChild(drawparms.img) ;
  drawparms.maindiv.appendChild(drawparms.headdiv) ; 
  drawparms.maindiv.appendChild(drawparms.imgdiv) ; 
  if(captioned) drawparms.maindiv.appendChild(drawparms.capdiv) ; 

  while(element.firstChild) element.removeChild(element.firstChild) ;
  element.appendChild(table) ; 
  element.appendChild(drawparms.maindiv) ; 
}
/* -------------------------------------------------------------------------- */

Archived from routemaster.html

var segments=[],selected,actions,nactions,sel,dragging,overviewing=0 ; 
var mouseopt=0,resuri,xmlfile=null,shifted=null,unsavedchanges=[] ; 
var body,mapdiv,pro,starsdiv=null,routeprops,map=null,clickhandle=null ; 
var scroller=null,imgdiv,imghandle,imginfo,imgind ;
var cursorbtn,scissorsbtn,binbtn,undobtn,redobtn,penbtn,setbtn=null,dlbtn ;

var defparms = {tol:10,maxsep:95,wppenalty:1000,vweight:1} ;
var parser = new DOMParser() ;

var infowindow = 
{ handle: null , 
  type: null , 
  open: function(s,pos,type)
  { this.handle = new google.maps.InfoWindow({content:s,position:pos}) ;
    this.handle.open(map) ; 
    google.maps.event.addListener(this.handle,'closeclick',function() 
    { if(infowindow.type=='highlight')
      { redraw(selected[0]) ; 
        if(scroller!=null) { clearInterval(scroller) ; scroller = null ; }
      }
      else if(infowindow.type=='phinfo') walkto(selected[0],selected[1]) ;
      infowindow.handle = infowindow.type = starsdiv = null ; 
    } ) ;
    this.type = type ; 
  } , 
  close: function() 
  { if(this.handle==null) return null ;
    var response = this.type ;
    this.handle.close() ; 
    if(response=='highlight') 
    { redraw(selected[0]) ; 
      if(scroller!=null) { clearInterval(scroller) ; scroller = null ; }
    }
    else if(response=='phinfo') walkto(selected[0],selected[1]) ; 
    this.handle = this.type = starsdiv = null ; 
    return response ; 
  } 
} ; 
/* --------------- construct a segment from an xml document ----------------- */

function genseg(a,b) 
{ this.data = a ; 
  this.props = b ; 
  this.route = this.routehandler = this.dots = this.dothandler = null ; 
  this.colour = "red" ;
}
/* -------------------------------------------------------------------------- */
/*                                CONSTRUCTORS                                */
/* -------------------------------------------------------------------------- */

function dotpath(a,b)
{ this.path = [a,b] ;
  this.cursor = 'default' ;
  this.geodesic = true ;
  this.strokeOpacity = 0 ;
  this.icons = [ { icon:   { path: 'M 0 0 L 1 0',strokeOpacity:1,scale:1 } , 
                   offset: '1px' , 
                   repeat: '4px' 
                  } ] ;
  this.zIndex = 0 ;
}
/* -------------------------------------------------------------------------- */

function linepath(s0,start,end,colour,width)
{ var i,len=(start<0?segments[s0].data.length:end-start) ; 
  if(width==undefined) width = 2 ; 
  this.path = new Array(len) ; 
  if(start<0) for(i=0;i<len;i++) this.path[i] = segments[s0].data[i].pos ;
  else for(i=0;i<len;i++) this.path[i] = segments[s0].data[start+i].pos ; 
  this.clickable = 'false' ;
  this.cursor = 'default' ;
  this.geodesic = true ;
  this.strokeColor = colour ;
  this.strokeOpacity = 1.0 ;
  this.strokeWeight = width ;
  if(width==2) this.zIndex = 0 ; else this.zIndex = 1 ; 
}
/* -------------------------------------------------------------------------- */

function listinfo()
{ this.list = [] ; 
  this.sizes = [] ;
  this.uri = null ; 
  this.thumbind = this.scale = this.status = this.type = this.pixpage = null ; 
}
/* -------------------------------------------------------------------------- */
/*page*/
/* -------------------------------------------------------------------------- */
/*                             UTILITY FUNCTIONS                              */
/* -------------------------------------------------------------------------- */

function interp(x,y,lamda)
{ return google.maps.geometry.spherical.interpolate(x,y,lamda) ; }

function bearing(x,y)
{ return google.maps.geometry.spherical.computeHeading(x,y) ; }

/* --------------------------- button handlers  ----------------------------- */

function greyout(btn)
{ if(overviewing||btn.active==0) return 0 ; 
  btn.btn.setAttribute('src',btn.greyimg) ; 
  btn.ui.removeEventListener('click',btn.handler) ;
  if(btn==penbtn) btn.ui.removeEventListener('contextmenu',photoprompt) ;
  btn.ui.style.cursor = 'default' ; 
  btn.active = 0 ; 
  return 1 ; 
}
function blackout(btn)
{ if(overviewing||btn.active) return ; 
  btn.btn.setAttribute('src',btn.blackimg) ; 
  btn.ui.addEventListener('click',btn.handler) ;
  if(btn==penbtn) btn.ui.addEventListener('contextmenu',photoprompt) ; 
  btn.ui.style.cursor = 'pointer' ; 
  btn.active = 1 ; 
}
/* ------------------------ enter/exit full screen -------------------------- */

// most of the code is available from pixlib
function enterFullscreen() { infowindow.close() ; enterfullscreen() ; } 

function exitFullscreen() 
{ infowindow.close() ; 
  if(document.exitFullscreen) document.exitFullscreen() ;
  else if(document.mozCancelFullScreen) document.mozCancelFullScreen() ;
  else if(document.webkitExitFullscreen) document.webkitExitFullscreen() ;
}
/* -------------------------------------------------------------------------- */

function findimg(id)
{ var i ; 
  for(i=0;i<imginfo.list.length;i++) 
    if(imginfo.list[i].name!=undefined&&imginfo.list[i].name==id) return i ; 
  return -1 ; 
}
/* ------------------- message warning of unsaved changes ------------------- */

function unsavedmsg(ok)
{ var msg , len = unsavedchanges.length , i ; 
  if(len==0) return null ; 
  msg = 'You have ' + len + ' unsaved change' + (len==1?"":'s') ; 
  if(len<=3) for(i=0;i<len;i++)
     msg += (i?',':' (') + unsavedchanges[i] + (i==len-1?')':'') ;
  msg += '\nIf you hit ' + (ok?'[OK]':'[Leave page]') ; 
  return msg + (len==1?' this change':' these changes') + ' will be lost.' ;
}
/* -------------------------------------------------------------------------- */
/*page*/
/* --------------- selpoint: choose the clicked waypoint  ------------------- */

function selpoint(event)
{ var i,j,closest,d,mindist,s0,s1 ; 
  if(dragging) return ; 
  var flag = (infowindow.close()=='wpinfo') && (shifted==0) ; 

  if(overviewing==0&&shifted)
  { s0 = selected[0] ;
    s1 = segments[s0].data.length ;
    insert(s0,s1,1) ;
    segments[s0].data[s1].setpos(event.latLng) ;
    redrawconnect(s0,s1) ;
    done(['move',s0,s1,event.latLng,event.latLng,1]) ; 
  }
  else for(s1=s0=-1,i=0;i<segments.length;i++) 
    for(j=0;j<segments[i].data.length;j++)
  { d = dist(segments[i].data[j].pos,event.latLng) ;
    if(s0<0||d<mindist) { s0 = i ; s1 = j ; mindist = d ; } 
  }
  walkto(s0,s1,flag) ; 
}
/* -------------------------- track highlighter  ---------------------------- */

function highlight()
{ var s0=selected[0],scroll,thind=null ;
  infowindow.close() ;
  undraw(s0) ; 
  draw(s0,4) ;
  if(imginfo.uri!=null) thind = thumbind(imginfo.sizes) ;
  scroll = highdiv(segments[s0].props,imginfo.list,imginfo.sizes,
                   thind,segments[s0].props.photo) ;
  scroller = scroll.scroller ;
  infowindow.open(scroll.div,northernmost(segments[s0].data),'highlight') ; 
}
/* ------------------------------- getbtnpos -------------------------------- */

function getbtnpos(btnno)
{ var bounds=map.getBounds(),sw,ne,lat,lon,lam ;
  sw = bounds.getSouthWest() ; 
  ne = bounds.getNorthEast() ; 
  lam = 52.0 / window.innerHeight ; 
  lat = lam*ne.lat() + (1-lam)*sw.lat() ; 
  lam = 0.5 + (btnno*32-112.0)/window.innerWidth ;
  lon = lam*ne.lng() + (1-lam)*sw.lng() ;
  return new google.maps.LatLng(lat,lon) ; 
}
/* ----- unambig: does the selected waypoint determine a unique segment? ---- */

function unambig() // does the selected waypoint determine a unique segment? 
{ var s0=selected[0],s1=selected[1] ; 
  if(segments.length==1) return 1 ; 
  if( ( s0==segments.length-1 || s1!=segments[s0].data.length-1 ||
        ! segments[s0].data[s1].pos.equals(segments[s0+1].data[0].pos) ) && 
      ( s0==0 || s1!=0 ||
        ! segments[s0].data[s1].pos.equals
          (segments[s0-1].data[segments[s0-1].data.length-1].pos) ) )
    return 1 ; 
  else return 0 ;
}
/* -------------------------------------------------------------------------- */
/*page*/
/* --------------------------------- walkto --------------------------------- */

// draw a selection point (and possibly an info box) at [s0,s1], bringing up
// a wpinfo window if flag = 0 or a selinfo window if flag = 1 

function walkto(s0,s1,flag) 
{ var datum = segments[s0].data[s1] , pos = datum.pos ; 
  if(flag==undefined) flag = 0  ;
  selected = [ s0,s1 ] ;
  if(overviewing) return highlight() ; 
  map.panToBounds(new google.maps.LatLngBounds(pos,pos)) ; 
  drawsel(0,[s0,s1]) ; 
  if(flag||(datum.type==null&&datum.photo.length==0)) 
  { if(flag==1) wpinfo() ; 
    else if(flag==2) seginfo() ; 
    else if(flag==3) highlight() ; 
    return ; 
  }
  infowindow.open(walktodiv(datum),pos,'walking') ; 
}
/* -------------------------------------------------------------------------- */
/*page*/
/* -------------------------- keystroke handler  ---------------------------- */

function keystroke(e)
{ var s0=selected[0],s1=selected[1],slast,flag ;

  if(e.keyCode==16) { shiftkey(1) ; return ; } 
  if(e.keyCode==40&&overviewing==0) 
  { map.panTo(segments[s0].data[s1].pos) ; return ; } 

  flag = infowindow.close() ;
  if(flag=='highlight') flag = 3 ; 
  else if(flag=='wpinfo') flag = 1 ; 
  else flag = 0 ; 

  if(e.keyCode==32) { selclick() ; return ; } // space
  if(overviewing) 
  { if(e.keyCode==70) enterfullscreen() ; 
    if(flag!=3||(e.keyCode!=39&&e.keyCode!=37&&e.keyCode!=8&&e.keyCode!=46)) 
      return ; 
  }
  if(e.keyCode==13) // return 
  { if(dragging) undraggit() ; else if(s0>=0) draggit(0) ; return ; } 
  if(dragging) return ; 

  if(e.keyCode==8||e.keyCode==46) // delete/backspace
  { e.preventDefault() ; 
    if(!e.shiftKey&&(segments.length>1||segments[0].data.length>1)) wpdel() ;
    else if(e.shiftKey&&binbtn.active) discard() ; 
    return ; 
  }
  if(e.keyCode==9) { e.preventDefault() ; inswp(1) ; return ; } // tab

  if(e.keyCode==39) // forwards
  { e.preventDefault() ;
    if(e.shiftKey) { s1 = 0 ; s0 += 1 ; if(s0==segments.length) s0 = 0 ; }
    else if(s1<segments[s0].data.length-1) s1 += 1 ; 
    else { s0 += 1 ; if(s0==segments.length) s0 = 0 ; s1 = 0 ; } 
  }
  else if(e.keyCode==37) // backwards 
  { e.preventDefault() ;  
    if(e.shiftKey) { s1 = 0 ; s0 -= 1 ; if(s0<0) s0 = segments.length-1 ; }
    else if(s1>0) s1 -= 1 ; 
    else 
    { s0 -= 1 ; 
      if(s0<0) s0 = segments.length-1 ; 
      s1 = segments[s0].data.length-1 ; 
    } 
  }
  else return ; 
  walkto(s0,s1,e.shiftKey?2:flag) ;
}
/* -------------------------- shift key handler  ---------------------------- */

function shiftkey(val)
{ shifted = val ; 
  if(mouseopt==1&&map!=null) 
    map.setOptions({draggableCursor:val?'crosshair':'default'}) ;
  if(val==0&&overviewing==0) getalts(200) ;
}
/* --------------------- undraw & redraw segments  -------------------------- */

function undraw(i) 
{ segments[i].route.setMap(null) ; 
  if(segments[i].clickhandler!=null) 
  { google.maps.event.removeListener(segments[i].clickhandler) ;
    segments[i].clickhandler = null ; 
  }
}
function redraw(i) { undraw(i) ; draw(i) ; }

function recolour(i) 
{ if(overviewing) return ; 
  else if(i&1) segments[i].route.setOptions({strokeColor:"#ff9999"}) ; 
  else segments[i].route.setOptions({strokeColor:"#ff0000"}) ; 
}
function obliterate(s0) // undraw route and all labels
{ var i ;
  for(i=0;i<segments[s0].data.length;i++) segments[s0].data[i].setmap(null) ; 
  undraw(s0) ; 
  disconnect(s0-1) ; 
  disconnect(s0) ; 
}
/* ----------------------------- draw segments ------------------------------ */

function draw(i,width)
{ var colour,poly ; 
  if(overviewing) colour = segments[i].colour ; 
  else if(i&1) colour = "#ff9999" ; 
  else colour = "#ff0000" ; 
  if(width==undefined) poly = new linepath(i,-1,0,colour) ;
  else poly = new linepath(i,-1,0,colour,width) ;
  segments[i].route = new google.maps.Polyline(poly) ;
  segments[i].route.setMap(map) ;
  if(segments[i].clickhandler==null)
    segments[i].clickhandler = 
      google.maps.event.addListener(segments[i].route,"click",selpoint) ;
}
/* ----------------------- connect and disconnect segments ------------------ */

function disconnect(i) 
{ if(overviewing||i<0||i>=segments.length-1||segments[i].dots==null) return ;
  segments[i].dots.setMap(null) ; 
  if(segments[i].dothandler!=null) 
  { google.maps.event.removeListener(segments[i].dothandler) ;
    segments[i].dothandler = null ; 
  }
}
function reconnect(i) { disconnect(i) ; connect(i) ; }

function connect(i)
{ if(overviewing||i<0||i>=segments.length-1) return ; 
  var opos = segments[i].data[segments[i].data.length-1].pos ; 
  var npos = segments[i+1].data[0].pos ;
  if(opos.equals(npos)) return ;
  segments[i].dots = new google.maps.Polyline(new dotpath(opos,npos)) ;
  segments[i].dots.setMap(map) ;
  segments[i].dothandler = 
    google.maps.event.addListener(segments[i].dots,"click",selpoint) ;
}
function redrawconnect(s0,s1) 
{ redraw(s0) ; 
  if(s1==0) reconnect(s0-1) ; 
  if(s1=segments[s0].data.length-1) reconnect(s0) ; 
}
/* ---------------------- draw the selection point -------------------------- */

// note: there's no point in allowing clicking on a marker because the 
// event position is always the marker position rather than the click position

function drawsel(opt,selection)
{ if(selection!=undefined) selected = selection ;
  var ind,clen,s0=selected[0],s1=selected[1],pos=segments[s0].data[s1].pos ;
  if(opt) reprofile() ; 
  clen = segments[s0].data.length ; 
  if(clen==1) arrow.rotation = 90 ; 
  else
  { if(s1==clen-1) ind = s1-1 ; else ind = s1 ;
    icons.arrow.rotation = 
      bearing(segments[s0].data[ind].pos,segments[s0].data[ind+1].pos) ;
  }

  if(sel.marker==null) sel.marker = new google.maps.Marker
    ({ position:pos, map:map, cursor:'default', icon:icons.arrow , zIndex:2 }) ;
  else // avoid unnecessary redraws
  { if(icons.arrow.rotation!=sel.orientation) sel.marker.setIcon(icons.arrow) ;
    if(!pos.equals(sel.marker.getPosition())) sel.marker.setPosition(pos) ; 
  }
  sel.orientation = icons.arrow.rotation ; 
  drawxcur(pro,selected) ;

  blackout(penbtn) ;
  if(segments.length>1&&unambig()) blackout(binbtn) ; else greyout(binbtn) ;  
  if(s1!=0&&s1!=clen-1) blackout(scissorsbtn) ; else greyout(scissorsbtn) ;
}
/* ------------- selclick: respond to click of cursor button  --------------- */

function selclick()
{ mouseopt = 1-mouseopt ; 
  infowindow.close() ;  
  if(mouseopt)
  { map.setOptions({draggable:false, draggableCursor:'default'}) ;
    if(overviewing==0) cursorbtn.btn.setAttribute('src',resuri+'hand.png') ; 
    clickhandle = google.maps.event.addListener(map,"click",selpoint) ;
  }
  else
  { map.setOptions({draggable:true, draggableCursor:''}) ;
    if(overviewing==0) cursorbtn.btn.setAttribute('src',resuri+'arrow.png') ; 
    google.maps.event.removeListener(clickhandle) ;
  }
}
/* -------------------------------------------------------------------------- */

function genhead(uri,key)
{ if(uri==undefined||uri==null)  
    resuri = 'http://www.masterlyinactivity.com/routemaster/resources/' ;
  else
  { resuri = uri + '/' ;
    document.write('<script src="http://maps.google.com/maps/api/js?' +
                    ((key==null||key==undefined)?'':('key='+key+'&')) +
                   'libraries=geometry"></scr' + 'ipt>') ;
  }
  document.write
  ('<script src="' + resuri + 'dms.js"></scr' + 'ipt>' +
   '<script src="' + resuri + 'vector3d.js"></scr' + 'ipt>' +
   '<script src="' + resuri + 'latlon-ellipsoidal.js"></scr' + 'ipt>' +
   '<script src="' + resuri + 'utm.js"></scr' + 'ipt>' +
   '<script src="' + resuri + 'osgridref.js"></scr' + 'ipt>' +
   '<script src="' + resuri + 'FileSaver.js"></scr' + 'ipt>' +
   '<script src="' + resuri + 'Blob.js"></scr' + 'ipt>' +
   '<script src="' + resuri + 'tcxlib.js"></scr' + 'ipt>' +
   '<script src="' + resuri + 'routemasterlib.js"></scr' + 'ipt>' +
   '<script src="http://www.masterlyinactivity.com/pixlib.js"></scr' + 'ipt>' +
   '<style type="text/css">html, body {width: 100%; height: 100%}' +
       'body {margin:0px}a:link{color:#66aaaa}' + 
       'a:visited{color:#cc3388}a:active{color:#404040}' +
       '</style><title>Routemaster</title>' + 
   '<link rel="shortcut icon" href=' + resuri + 'bus.gif>' ) ;
}
/* -------------------------------------------------------------------------- */
/*page*/
/* -------------------------------------------------------------------------- */
/*                FUNCTIONS TO GENERATE THE INITIAL MAP                       */
/* -------------------------------------------------------------------------- */

function genpage() 
{ var thispage=document.URL,xhttp,quotind,plusind,listxhttp,div,x ;
  imginfo = new listinfo() ; 
  imgdiv = null ; 

  window.onload = function() 
  { window.addEventListener("beforeunload",function(e) 
    { var msg = unsavedmsg(0) ; 
      if(msg==null) return undefined ; 
      (e || window.event).returnValue = msg ; //Gecko + IE
      return msg ; //Gecko + Webkit, Safari, Chrome etc. (msg is ignored by ffx)
    } ) ;
  } ;
  window.onfocus = function() { shiftkey(0) ; } ; 

  body = document.getElementsByTagName("body")[0] ;
  while(body.childNodes.length>0) 
    body.removeChild(body.childNodes[body.childNodes.length-1]) ;
  mapdiv = document.createElement('div') ; 
  mapdiv.setAttribute('id','map') ; 
  mapdiv.setAttribute('style','width:100%;height:100%;position:absolute') ; 
  body.appendChild(mapdiv) ;

  if((quotind=thispage.indexOf('?'))>=0)
  { thispage = thispage.substring(quotind+1) ; 
    plusind = thispage.indexOf('+'); 
    if(plusind>0) 
    { getlist(thispage.substring(plusind+1),'uri') ; 
      thispage = thispage.substring(0,plusind) ;
    }
    if(plusind>0||thispage.substring(thispage.length-3)!='.js')
    { xhttp = new XMLHttpRequest() ;
      xhttp.onreadystatechange = function() 
      { if(xhttp.readyState==4) 
        { if(xhttp.status==200)
          { x = parser.parseFromString(xhttp.responseText,"application/xml") ;
            render(x,thispage,0,'uri') ; 
          }
          else alert("Unable to read "+thispage+": error code "+xhttp.status) ;
        }
      }
      xhttp.open("GET",thispage,true) ;
      xhttp.send() ;
      return ;
    }
    else getlist(thispage,'uri') ; 
  }

  // come here if we need to browse the filesystem for input route
  mapdiv.appendChild(filedialogue(0)) ; 
  div = blurbdiv(resuri) ;
  div.setAttribute('style','font-family:helvetica;margin:4px;'+
                           'border-top:solid 1px silver;padding-top:2px') ; 
  mapdiv.appendChild(div) ; 
  div = helpdiv(resuri,1) ; 
  div.setAttribute('style','font-family:helvetica;margin:4px;font-size:90%') ;
  mapdiv.appendChild(div) ; 
}
/* -------------------------------------------------------------------------- */
/*page*/
/* -------------------------------- getlist --------------------------------- */

function getlist(uri,imgtype) 
{ var xhttp = new XMLHttpRequest(),list=null,sizes=null,pixpage=null,i,r ; 
  var imagedir=null,thumbshape = [] ;
  imginfo.status = 'waiting' ; 
  imginfo.type = imgtype ; 

  xhttp.onreadystatechange = function() 
  { if(xhttp.readyState==4)
    { if(xhttp.status!=200)
      { alert("Unable to read "+uri+": error code "+xhttp.status) ; return ; }
      eval(xhttp.responseText) ; 
      imginfo.uri = uri ; 
      imginfo.list = list ; 
      imginfo.sizes = sizes ; 
      imginfo.pixpage = reluri(uri,pixpage) ; 
      imginfo.thumbind = thumbind(sizes) ;
      setthumbshape(list,sizes,thumbshape,reluri(uri,imagedir)) ;
      for(i=0;i<list.length;i++) if(list[i].retpage!=undefined)
        list[i].retpage = reluri(uri,list[i].retpage) ;
      imginfo.status = 'ready' ; 
    }
  }
  xhttp.open("GET",uri,true) ;
  xhttp.send() ;
}
/* ----------------------------- file dialogue ------------------------------ */

function filedialogue(overwrite)
{ var input = document.createElement('input') ; 
  var para = document.createElement('p') ; 
  para.appendChild(document.createTextNode
                             ('\u00a0\u00a0\u00a0Select TCX/GPX file: ')) ; 
  input.setAttribute('type','file') ; 
  input.setAttribute('accept','.tcx,.gpx') ; 
  input.addEventListener('change',function(e)
  { reader = new FileReader() ;
    reader.onload = function(e) 
    { var xmldoc = parser.parseFromString(reader.result,"application/xml") ;
      render(xmldoc,input.files[0].name,overwrite,'file') ; 
    } 
    reader.readAsText(input.files[0]) ;	
  } ) ;
  para.appendChild(input) ; 
  return para ;
}
/* -------------------------------------------------------------------------- */
/*page*/
/* --------------------- set up the map and buttons ------------------------- */

function render(xmldoc,filename,overwrite,origin) 
{ var i,opts,centre,lat,lon,minlon,maxlon,minlat,maxlat,newseg,s0,d ;
  var colours,segno,istcx ; 

  infowindow.close() ;
  document.onkeydown = keystroke ;
  document.onkeyup = function(e) { if(e.keyCode==16) shiftkey(0) ; } ;
  xmlfile = filename ;

  // read data
  i = filename.length ; 
  if(filename.substring(i-4,i).toLowerCase()=='.tcx') istcx = 1 ; 
  else if(filename.substring(i-4,i).toLowerCase()=='.gpx') istcx = 0 ; 
  else { alert(filename+' is neither .tcx nor .gcx') ; throw '' ; }

  if(istcx) newseg = readtcx(xmldoc) ; else newseg = readgpx(xmldoc) ;
  if(newseg.segments.length==0||newseg.segments[0].length==0) 
  { alert('no data returned') ; return ; }
  if(newseg.segments.length>1&&setbtn!=null)
  { alert('trying to add a multitrack index... not permitted') ; return ; } 

  // check and process photo list
  if(imginfo.type!='uri') for(i=0;i<newseg.segments.length;i++)
    if(newseg.props[i].list!=null)
  { if(imginfo.uri!=null&&newseg.props[i].list!=imginfo.uri)
    { alert('inconsistent photo lists: ' + imginfo.uri + ' and ' + 
            newseg.props[i].list) ; 
      return ; 
    }
    imginfo = new listinfo() ; 
    getlist(newseg.props[i].list,'tcx') ; 
  }

  if(overwrite)
  { for(i=0;i<segments.length;i++) obliterate(i) ; 
    unprofile() ; 
    if(sel.marker!=null) sel.marker.setMap(null) ;
    segments = [] ; 
    if(imginfo.type=='tcx') imginfo = new listinfo() ; 
  } 

  s0 = segments.length ; 
  if(s0==0)
  { sel = { marker:null, orientation: null } ; 
    pending = [] ; 
    xpending = [] ; 
    actions = [] ; 
    unsavedchanges = [] ; 
    nactions = dragging = 0 ; 
    pro = starsdiv = null ; 
    routeprops = new propstype() ;
  }

  if(newseg.segments.length>1) 
  { overviewing = 1 ; 
    colours = gencolours(newseg.segments.length) ; 
    routeprops.stars = -1 ; 
  } 
  if(istcx&&routeprops.title==null&&newseg.title!=null) settitle(newseg.title) ;

  // process the new segments
  for(i=0;i<newseg.segments.length;i++)
  { newseg.props[i].source = [ filename , origin ] ; 
    if(routeprops.stars==null) routeprops.stars = newseg.props[i].stars ;
    if(routeprops.overview==null) 
      routeprops.overview = newseg.props[i].overview ;
    if(routeprops.title==null||routeprops.title=='Untitled Route')
      if(newseg.props[i].title!=null) settitle(newseg.props[i].title) ; 
    segments.push(new genseg(newseg.segments[i],newseg.props[i])) ; 
    if(overviewing) segments[segments.length-1].colour = colours[i] ;
    actions[nactions++] = 
      [ 'load',s0+i,newseg.segments[i].slice(),newseg.props[i] ] ;
  }
  if(routeprops.title==null) settitle('Untitled Route') ; 

  if(!newseg.props[0].optim.already&&!overviewing) 
    optimaction(segments.length-1,defparms,0) ; 
  if(overviewing) 
  { for(d='',i=0;i<newseg.segments.length;i++) if(newseg.props[i].title!=null)
    { if(d!='') d += ' | ' ; d += newseg.props[i].title ; }
    if(d!=null) routeprops.desc = d ; 
  }
  else { routeprops.desc = newseg.props[0].desc ; getalts(100) ; }
  if(routeprops.desc!=null) setdesc(routeprops.desc) ;

  // find max and min lat and long - have to look at all segs, not just
  // newly loaded to avoid google's repeatedly adding a margin 
  for(maxlat=null,segno=0;segno<segments.length;segno++)
    for(i=0;i<segments[segno].data.length;i++)
  { lat = segments[segno].data[i].pos.lat() ; 
    lon = segments[segno].data[i].pos.lng() ; 
    if(maxlat==null||lon<minlon) minlon = lon ; 
    if(maxlat==null||lon>maxlon) maxlon = lon ; 
    if(maxlat==null||lat<minlat) minlat = lat ; 
    if(maxlat==null||lat>maxlat) maxlat = lat ; 
  }

  if(s0==0)
    centre = new google.maps.LatLng((minlat+maxlat)/2,(minlon+maxlon)/2) ;

  if(map==null) // all this only done on first call
  { opts = { zoom: 22,
             center: centre,
             scaleControl: true,
             rotateControl: false,
             streetViewControl: false,
             mapTypeId: overviewing?
                google.maps.MapTypeId.ROADMAP:google.maps.MapTypeId.TERRAIN,
             disableDoubleClickZoom: true,
             styles: [ { "featureType": "poi", 
                         "stylers": [{ "visibility": "off" }]
                        } ],
             mapTypeControl:true,
             mapTypeControlOptions: 
               { style:google.maps.MapTypeControlStyle.HORIZONTAL_BAR }, 
             mapTypeIds: [ google.maps.MapTypeId.ROADMAP,
                           google.maps.MapTypeId.TERRAIN,
                           google.maps.MapTypeId.SATELLITE
                         ]
           } ;

    map = new google.maps.Map(mapdiv,opts) ;

    // set up buttons
    if(overviewing==0)
    { setbtn = genbutton('settings') ;
      cursorbtn = genbutton('cursor') ;
      cursorbtn.ui.addEventListener('click',selclick) ;
      scissorsbtn = genbutton('scissors') ;
      binbtn = genbutton('bin') ;
      penbtn = genbutton('pen') ;
      undobtn = genbutton('undo') ;
      redobtn = genbutton('redo') ;
      dlbtn = genbutton('dl') ;
    }
    selclick() ;
  }

  map.fitBounds(new google.maps.LatLngBounds
                             (new google.maps.LatLng(minlat,minlon),
                              new google.maps.LatLng(maxlat,maxlon))) ; 

  for(segno=s0;segno<segments.length;segno++)
    for(i=0;i<segments[segno].data.length;i++) 
      segments[segno].data[i].setmap(map) ;

  if(nactions>1) donesomething() ; // specifically, done loading & optimisation
  else actions.length = nactions ; // load with no optimisation hence no undo

  if(s0==0) { selected = [0,0] ; if(overviewing==0) drawsel(1) ; } 
  else greyout(dlbtn) ; 
  for(segno=s0;segno<segments.length;segno++)
  { draw(segno) ; connect(segno-1) ; }
  connect(segments.length-1) ; 
  reprofile() ; 
}
/* ------------------------------- settitle --------------------------------- */

function settitle(newtitle) 
{ routeprops.title = newtitle ; 
  var h = document.getElementsByTagName('title') ;
  if(h.length==0) h = document.createElement('title') ;
  else { h = h[0] ; while(h.firstChild) h.removeChild(h.firstChild) ; }
  h.appendChild(document.createTextNode(newtitle)) ;
}
/* -------------------------------------------------------------------------- */
/*page*/
/* -------------------------------- setdesc --------------------------------- */

function setdesc(newdesc) 
{ routeprops.desc = newdesc ; 
  var i , d='' , m = document.getElementsByTagName('meta') ;
  if(routeprops.stars!=null) for(i=0;i<=routeprops.stars;i++)
  { if(i<routeprops.stars) d += '\u2605' ; else d += ' | ' ; }
  for(i=0;i<m.length&&m[i].getAttribute('name')!='description';i++) ;
  if(i<m) { m[i].setAttribute('content',newdesc) ; return ; }
  m = document.createElement('meta') ;
  m.setAttribute('name','description') ; 
  m.setAttribute('content',d+newdesc) ; 
  document.getElementsByTagName('head')[0].appendChild(m) ;
}
/* ------------------------------- retitle ---------------------------------- */

function retitle(opt) 
{ var newval , msg , field = (opt=='title'?'title':'desc') ; 
  var oldval = routeprops[field] ;
  infowindow.close() ; 
  if(oldval==null) msg = 'Add ' + opt ; else msg = 'Modify ' + opt ;
  if(opt=='title') msg += ' (max 15 chars):' ; else msg += ':' ;
  newval = window.prompt(msg,oldval) ;
  if(newval==null) return ; 
  if(opt=='title') newval = newval.substring(0,15) ; 
  if(newval==oldval) return ; 
  if(opt=='title') settitle(newval) ; else setdesc(newval) ; 
  actions[nactions++] = [ 'edit'+opt , oldval , newval ] ; 
  donesomething() ;
  routeinfo() ; 
}
/* ------------------------------- restars ---------------------------------- */

function restars(oldstars,newstars) 
{ actions[nactions++] = [ 'stars' , oldstars , newstars ] ; 
  donesomething() ;
  starsline(routeprops.stars=newstars,1) ; 
}
/* ------------------------------- genbutton -------------------------------- */

function genbutton(name)
{ var u,v,w,b,g,k,h,div=document.createElement('div'),act ;
  u = document.createElement('div') ;
  u.style.backgroundColor = '#ffffff' ;
  u.style.border = '2px solid #ffffff' ;
  u.style.borderRadius = '3px' ;
  u.style.boxShadow = '0 2px 6px rgba(0,0,0,.3)' ;
  if(name=='dl'||name=='settings'||name=='cursor') u.style.cursor = 'pointer' ;
  else u.style.cursor = 'default' ;
  u.style.marginBottom = '12px' ;
  if(name!='dl') u.style.marginRight = '4px' ;
  u.style.textAlign = 'center' ;
  div.appendChild(u) ;

  if(name=='scissors') { h = snip ; div.index = 3 ; } 
  else if(name=='bin') { h = discard ; div.index = 4 ; }
  else if(name=='pen') { h = labelprompt ; div.index = 5 ; }
  else if(name=='undo') { h = undo ; div.index = 6 ; }
  else if(name=='redo') { h = redo ; div.index = 7 ; }
  else if(name=='dl') { h = function() { dl(0) ; }  ; div.index = 8 ; }
  else if(name=='settings') { h = popup ; div.index = 1 ; }
  else if(name=='cursor') { h = null ; div.index = 2 ; }
  g = greybtn(resuri,name) ;
  k = blackbtn(resuri,name) ;
  if(name=='dl'||name=='settings'||name=='cursor') b = buttonimg(k) ;
  else b = buttonimg(g) ;
  u.appendChild(b) ;

  map.controls[google.maps.ControlPosition.BOTTOM_CENTER].push(div) ;

  if(name=='dl'||name=='settings') u.addEventListener('click',h) ; 
  if(name=='dl'||name=='settings') act = 1 ; else act = 0 ; 

  return { ui:u , btn:b , active:act , greyimg:g , blackimg:k , handler:h } ; 
}
/* -------------------------------------------------------------------------- */
/*page*/
/* -------------------------------------------------------------------------- */
/*                             OPTIMISATION                                   */
/* -------------------------------------------------------------------------- */

function optimaction(segno,parms,force)
{ var s = segments[segno], result = optimise(s.data,parms) , loadno ; 
  var ndel = s.data.length - result.length ; 
  for(loadno=nactions-1;loadno>=0&&actions[loadno][0]!='load';loadno--) ; 
  if(loadno<0||(force==0&&ndel<s.data.length/2)||ndel==0) return 0 ; 
  actions[loadno][3].optim.ndel = ndel ; 
  actions[nactions++] = [ 'optimise' , segno , parms ] ; 
  segments[segno] = new genseg(result,segments[segno].props) ;
  actions[loadno][3].optim.parms = { tol: parms.tol , 
                                     maxsep: parms.maxsep , 
                                     wppenalty: parms.wppenalty , 
                                     vweight: parms.vweight 
                                   } ; 
  return 1 ; 
}
/* -------------------------------------------------------------------------- */

function optimprompt()
{ var msg = 'Enter 4 optimisation parms (tol, maxsep, wppenalty and vweight)' ; 
  var parmstr = defparms.tol + ' ' + 
                defparms.maxsep.toFixed(0) + ' ' +
                defparms.wppenalty.toFixed(0) + ' ' +
                defparms.vweight.toFixed(1) ;
  var parms,i ; 
  infowindow.close() ;  

  for(i=0;;i++)
  { newparms = prompt(msg,parmstr) ; 
    if(newparms==null) return ;
    if(newparms=='') { parms = defparms ; break ; }
    newparms = newparms.split(' ') ;
    if(newparms.length==0) { parms = defparms ; break ; }
    parms = { tol: parseFloat(newparms[0]) , 
              maxsep: parseFloat(newparms[1]) , 
              wppenalty: parseFloat(newparms[2]) , 
              vweight: parseFloat(newparms[3]) } ; 
    if( isNaN(parms.tol)==0 && isNaN(parms.maxsep)==0 && 
        isNaN(parms.wppenalty)==0 && isNaN(parms.vweight)==0 ) break ; 
    if(i==0) msg = '*** Illegal parms ***\n' + msg ; 
  }
  if(optimaction(segments.length-1,parms,1)) 
  { donesomething() ; draw(segments.length-1) ; } 
  routeinfo() ; 
}  
/* -------------------------------------------------------------------------- */
/*page*/
/* -------------------------------------------------------------------------- */

function popup()
{ infowindow.close() ;
  var d=cogwheelmenu(dragging,pro!=null&&pro.prodiv!=null,routeprops.overview) ;
  infowindow.open(d,getbtnpos(0),'settings') ; 
}
/* ------------------------------- calwork --------------------------------- */

function calwork(s0,y)
{ var i,s1 ; 
  for(s1=0;s1<segments[s0].data.length;s1++)
    if(segments[s0].data[s1].h!=null) segments[s0].data[s1].h += y ; 
  reprofile() ; 
}
/* ------------------------------ manualcal --------------------------------- */

function manualcal()
{ infowindow.close() ; 
  var x,y,s0=selected[0] ;
  x = prompt('Enter offset in metres to add to altidudes:') ;
  if(x==null) return ; 
  y = parseFloat(x) ; 
  if(isNaN(y)) { alert(x+' is not a number') ; return ; }
  calwork(s0,y) ; 
  done(['recal',s0,y]) ; 
}  
/* --------------------------------- help ----------------------------------- */

function help() 
{ infowindow.close() ; infowindow.open(helpdiv(resuri),getbtnpos(0),'help') ; }

/* --------------------------------- wpdel ---------------------------------- */

function wpdelwork(s0,s1)
{ var i,response=segments[s0].data[s1],clen=segments[s0].data.length ;
  response.setmap(null) ;
  for(i=s1;i<clen-1;i++) segments[s0].data[i] = segments[s0].data[i+1] ;
  segments[s0].data.length = clen-1 ; 
  selected = [s0,s1] ; 
  if(s1==segments[s0].data.length) selected[1] -= 1 ;  
  redrawconnect(s0,s1) ; 
  drawsel(1) ; 
  return response ;
}
function wpdel()
{ var s0=selected[0],s1=selected[1],i ;
  var flag = infowindow.close() ; 
  done(['wpdel',s0,s1,wpdelwork(s0,s1)]) ; 
  if(flag=='wpinfo') wpinfo() ; 
}
/* --------------------------------- revseg --------------------------------- */

function revsegwork(s0)
{ var i,s=segments[s0],j,x,len=s.data.length ;
  disconnect(s0-1) ; disconnect(s0) ; 
  for(i=0;i<len/2;i++)
  { j = (len-1) - i ; 
    x = s.data[i] ; s.data[i] = s.data[j] ; s.data[j] = x ; 
  }
  for(i=0;i<s.data.length;i++) 
    if(s.data[i].type=='Right') s.data[i].settype('Left') ;
    else if(s.data[i].type=='Left') s.data[i].settype('Right') ;

  if(s0==selected[0]) selected[1] = (len-1) - selected[1] ; 
  connect(s0-1) ; connect(s0) ; 
  drawsel(1) ; 
}
/* -------------------------------------------------------------------------- */

function revseg()
{ infowindow.close() ; 
  revsegwork(selected[0]) ; 
  done(['revseg',selected[0]]) ; 
}
/* -------------------------------------------------------------------------- */

function addload(overwrite)
{ var msg ; 
  infowindow.close() ; 
  if(overwrite) 
  { msg = unsavedmsg(1) ; if(msg!=null) if(!confirm(msg)) return ; }
  infowindow.open(filedialogue(overwrite),getbtnpos(0),'addload') ; 
}
/* -------------------------------------------------------------------------- */
/*page*/
/* -------------------------------------------------------------------------- */
/*    DRAGGING A (POSSIBLY NEWLY INSERTED) WAYPOINT IS QUITE A LOT OF WORK    */
/* -------------------------------------------------------------------------- */

function insert(s0,s1,n)
{ var i ;
  for(i=segments[s0].data.length+n-1;i>s1;i--)
    segments[s0].data[i] = segments[s0].data[i-n] ;
  for(i=0;i<n;i++) segments[s0].data[s1+i] = new datatype(null,null) ;  
}
/* --------------------------------- inswp ---------------------------------- */

function inswp(dir)
{ var s0=selected[0],s1=selected[1],bounds,del,pos,data=segments[s0].data ;
  var len = data.length ;
  if(len==1) pos = data[0].pos ;

  if(dir>=0) s1 = selected[1] += 1 ; 
  insert(s0,s1,1) ;  
  if(len==1)
  { bounds = map.getBounds() ;
    del = bounds.getNorthEast().lng() - bounds.getSouthWest().lng() ; 
    pos = new google.maps.LatLng(pos.lat(),pos.lng()+dir*del/10) ; 
  }
  else if(s1==0) pos = interp(data[2].pos,data[1].pos,1.5) ; 
  else if(s1<len) pos = interp(data[s1-1].pos,data[s1+1].pos,0.5) ;
  else pos = interp(data[s1-2].pos,data[s1-1].pos,1.5) ;
  data[s1].setpos(pos) ; 
  draggit(1) ; 
}
/* -------------------------------- draggit --------------------------------- */

// draggit makes the current waypoint draggable

var l1,l2,startpos,seg0,seg1,seg2,colour,inserted ; 

function draggit(insparm)
{ var s0=selected[0],s1=selected[1],start,end,i,len=segments[s0].data.length ;
  startpos = segments[s0].data[s1].pos ;
  inserted = insparm ; 
  infowindow.close() ; 
  greyout(scissorsbtn) ;
  greyout(binbtn) ;
  greyout(penbtn) ;
  greyout(undobtn) ;
  greyout(redobtn) ;
  greyout(dlbtn) ;
  map.panToBounds(new google.maps.LatLngBounds(startpos,startpos)) ;

  sel.marker.setMap(null) ; 
  sel.marker = new google.maps.Marker(
                  { position: segments[s0].data[s1].pos,
                    map: map,
                    cursor: 'default',
                    icon: icons.concircle ,
                    draggable: true ,
                    zIndex: 2
                  } ) ;

  if(s0&1) colour = "#ff9999" ; else colour = "#ff0000" ; 
  segments[s0].route.setMap(null) ;
  if(segments[s0].clickhandler!=null) 
  { google.maps.event.removeListener(segments[s0].clickhandler) ;
    segments[s0].clickhandler = null ; 
  }

  seg0 = seg2 = null; 
  if(s1>1)
  { seg0 = new google.maps.Polyline(new linepath(s0,0,s1,colour)) ;
    seg0.setMap(map) ;
  }

  if(s1==0) start = 0 ; else start = s1-1 ; 
  if(s1==len-1) end = s1+1 ; else end = s1+2 ; 
  seg1 = new google.maps.Polyline(new linepath(s0,start,end,colour)) ;
  seg1.setMap(map) ;

  if(s1<segments[s0].data.length-2)
  { seg2 = new google.maps.Polyline(new linepath(s0,s1+1,len,colour)) ;
    seg2.setMap(map) ;
  }

  l1 = google.maps.event.addListener(sel.marker,'drag',function()
  { segments[s0].data[s1].setpos(this.getPosition()) ; 
    seg1.setMap(null) ;
    seg1 = new google.maps.Polyline(new linepath(s0,start,end,colour)) ;
    seg1.setMap(map) ;
    if(s1==0&&s0>0) { disconnect(s0-1) ; connect(s0-1) ; }
    if(s1==len-1&&s0<segments.length-1) { disconnect(s0) ; connect(s0) ; }
  } ) ;
 
  dragging = 1 ; 
}  
/* ------------------------------- undraggit -------------------------------- */

// undraggit is invoked by [return] to terminate waypoint dragging

function undraggit()
{ var s0=selected[0],s1=selected[1],i,s1dash,pos=segments[s0].data[s1].pos ; 
  var xpos ; 
  google.maps.event.removeListener(l1) ;
  dragging = 0 ; 
  if(seg0!=null) seg0.setMap(null) ;
  seg1.setMap(null) ;
  if(seg2!=null) seg2.setMap(null) ;
  segments[s0].route = new google.maps.Polyline(new linepath(s0,-1,0,colour)) ;
  segments[s0].route.setMap(map) ;
  segments[s0].data[s1].h = null ;
  getalts(100) ; 

  sel.marker.setMap(null) ; 
  sel.marker = null ; // force a redraw
  drawsel(1) ; 
  if(inserted||dist(startpos,pos)>5) 
    done(['move',s0,s1,startpos,pos,inserted]) ; 
  if(segments.length==1) blackout(dlbtn) ; 
}
/* -------------------------------------------------------------------------- */

function seginfo()
{ var pos = segments[selected[0]].data[selected[1]].pos ;
  infowindow.close() ;
  infowindow.open(seginfodiv(segments,selected[0]),pos,'seginfo') ; 
}
/* -------------------------------------------------------------------------- */

function deltimes()
{ var s0,s1,task=[] ;
  for(s0=0;s0<segments.length;s0++) for(s1=0;s1<segments[s0].data.length;s1++)
    if(segments[s0].data[s1].t!=null)
  { task.push([s0,s1,segments[s0].data[s1].t]) ; 
    segments[s0].data[s1].t = null ; 
  }
  infowindow.close() ;  
  done(['deltimes',task]) ;
}  
/* -------------------------------------------------------------------------- */
/*page*/
/* ------------------------- interpolate extra points ----------------------- */

function extrapts(opt)
{ var s0,s1,sep,data,n,opos,npos,i,lambda ;
  var task = [ 'extra' , selected[0] , selected[1] ] ;
  infowindow.close() ; 

  for(s0=0;s0<segments.length;s0++) 
    for(data=segments[s0].data,s1=1;s1<data.length;s1++)
      if((sep=dist(opos=data[s1-1].pos,npos=data[s1].pos))>100) 
  { n = Math.floor(sep/95) ;
    insert(s0,s1,n) ; 
    for(i=0;i<n;i++) 
    { lambda = (i+1) / (n+1) ; 
      data[s1+i].setpos(new google.maps.
                          LatLng(lambda*npos.lat()+(1-lambda)*opos.lat(),
                                 lambda*npos.lng()+(1-lambda)*opos.lng())) ;
    }
    if(selected[0]==s0&&s1<=selected[1]) selected[1] += n ; 
    task.push([s0,s1,data.slice(s1-1,s1+n+1)]) ; 
    s1 += n ;
  }

  getalts(1) ; 
  done(task) ; 
  routeinfo() ; 
}
/* -------------------------------------------------------------------------- */
/*                 FUNCTIONS FOR HANDLING THE ALTITUDE PROFILE                */
/* -------------------------------------------------------------------------- */

function drawprofile()
{ infowindow.close() ; 
  if((pro=procoords(segments))==null) return ; 
  drawpro(pro) ;
  body.appendChild(pro.prodiv) ; 
  body.appendChild(pro.curdiv) ;
  drawxcur(pro,selected) ;
} 
/* ------------------------------- unprofile -------------------------------- */

function unprofile()
{ var i,match,node ; 
  infowindow.close() ; 
  if(pro==null||pro.prodiv==null) return ; 
  pro.curdiv.removeEventListener('click',pro.curhandle) ;
  for(match=0,i=body.childNodes.length-1;match==0&&i>=0;i--)
  { node = body.childNodes[i] ; 
    match = (node==pro.prodiv) ; 
    body.removeChild(node) ; 
  }
  pro = null ; 
} 
function reprofile() 
{ if(pro!=null&&pro.prodiv!=null) { unprofile() ; drawprofile() ; } } 

/* -------------------------------------------------------------------------- */
/*       ROUTEINFO IS A MENU WHICH ITSELF SUPPORTS THE COMBINE FUNCTION       */
/* -------------------------------------------------------------------------- */

function routeinfo() 
{ infowindow.close() ; 
  infowindow.open(routediv(routeprops),getbtnpos(0),'routeinfo') ; 
}
/* -------------------------------------------------------------------------- */
/*page*/
/* ------------------------------- combine1 --------------------------------- */

function combine1(sa,sb)
{ var i,ca,cb,calen,cblen,la=null,lb=null,cdup=0 ; 
  undraw(sb) ; 
  disconnect(sb-1) ; 
  calen = segments[sa].data.length ;
  cblen = segments[sb].data.length ;
  cb = segments[sb].data[0].pos ; 
  cdup = ( ca = cb.equals(segments[sa].data[calen-1].pos) ) ; 
  if(cdup) 
  { la = segments[sa].data[calen-1] ; 
    lb = segments[sb].data[0] ; 
    segments[sa].data.length = ( calen -= 1 ) ;
  }

  if(selected[0]==sb) selected = [ sa , selected[1]+calen ] ; 
  segments[sa].data = segments[sa].data.concat(segments[sb].data) ; 

  return [ cblen , cdup , la , lb ] ; 
}
function combinework()
{ var task,s0 ;
  for(task=['combine',segments.length],s0=1;s0<segments.length;s0++) 
    task.push(combine1(0,s0)) ;
  segments.length = 1 ; 
  return task ; 
}
/* -------------------------------------------------------------------------- */

function combine()
{ infowindow.close() ; 
  done(combinework()) ; 
  drawsel(1) ; 
  redraw(0) ; 
  blackout(dlbtn) ; 
}  
function recombine()
{ var s0 ;
  for(s0=1;s0<segments.length;s0++) { undraw(s0) ; combine1(0,s0) ;}
  segments.length = 1 ; 
  drawsel(1) ;  
  redraw(0) ;
  blackout(dlbtn) ; 
}
/* -------------------------------------------------------------------------- */

//  combine returns [ cblen , cdup , la , lb ] ; 

function uncombine(task)
{ var i,j,llen,flag,subtask ; 

  for(flag=0,s0=task[1]-1,i=task.length-1;i>=2;i--,s0--)
  { subtask = task[i] ; 
    cblen = subtask[0] ; 
    cdup = subtask[1] ; 
    llen = segments[0].data.length ;
    segments[s0] = { data: segments[0].data.slice(llen-cblen,llen) , 
                     route: null , 
                     clickhandler: null
                   } ;
    llen = segments[0].data.length = llen+cdup-cblen ;
    if(cdup) 
    { segments[0].data[llen-1] = subtask[2] ; 
      segments[s0].data[0] = subtask[3] ; 
    }
    if(flag==0&&selected[1]>=llen)
    { selected = [ s0 , selected[1]-llen ] ; flag = 1 ; } 
  }
  drawsel(1) ; 
  undraw(0) ; 
  for(i=0;i<segments.length;i++) { draw(i) ; connect(i) ; } 
  greyout(dlbtn) ; 
}
/* -------------------------------------------------------------------------- */
/*page*/
/* -------------------------------------------------------------------------- */
/*           WPINFO IS A MENU GIVING ACCESS TO THE SETALT FUNCTION            */
/* -------------------------------------------------------------------------- */

function wpinfo() 
{ infowindow.close() ; 
  infowindow.open
    (wpinfodiv(),segments[selected[0]].data[selected[1]].pos,'wpinfo') ; 
}
/* -------------------------------- setalt ---------------------------------- */

function setalt(edit)
{ infowindow.close() ; 
  var s0=selected[0],s1=selected[1],x,y=null,oldalt ; 
  oldalt = segments[s0].data[s1].h.toFixed(0) ;
  if(edit) x = prompt('Enter altitude (m):',oldalt) ;
  else x = prompt('Enter altitude (m):') ;
  if(x==null) return ; 
  if(x!=''&&isNaN(y=parseFloat(x))) { alert(x+' is not a number') ; return ; }
  if(y==null&&oldalt==null) return ; 
  if(y!=null&&Math.abs(y-oldalt)<0.1) return ; 
  segments[s0].data[s1].h = y ; 
  done(['setalt',s0,s1,oldalt,y]) ; 
  reprofile() ; 
  wpinfo() ; 
}  
/* -------------------------------------------------------------------------- */
/*   THE LABELS ARE ACCESSED FROM THE PEN BUTTON OR BY CLICKING ON THE MAP    */
/* -------------------------------------------------------------------------- */

function labelprompt()
{ var oldcaption='',oldtype,type='Generic',s0=selected[0],s1=selected[1] ;
  var str , flag = (infowindow.close()=='wpinfo') ; 
  var datum = segments[s0].data[s1] ;
  
  oldtype = datum.type ; 
  if(oldtype!=null) oldcaption = datum.marker.title ; 
  if(oldcaption==null) oldcaption = '' ;
  if(oldtype!=null) type = oldtype ;
  if(oldtype==null) str = 'Enter' ; else str = 'Modify or delete' ; 
  var caption = window.prompt(str+' label:',oldcaption) ;

  if(caption==null) { if(flag) wpinfo() ; else walkto(s0,s1) ; return ; } 
  else if(caption=='') type = null ; 
  else caption = caption.substring(0,10) ;
  if(caption==oldcaption) 
  { if(flag) wpinfo() ; else walkto(s0,s1) ; return ; } 

  segments[s0].data[s1].setlabel(type,caption) ; 
  done(['editlabel',s0,s1,oldcaption,caption,oldtype,type]) ; 
  if(flag) wpinfo() ; else walkto(s0,s1) ; 
}
/* -------------------------------------------------------------------------- */
/*page*/
/* ------------------------------ labelcycle -------------------------------- */

function labelcycle()
{ var s0,s1,datum,types,caption,flag=(infowindow.close()=='wpinfo') ;  ;
  for(s0=0;s0<segments.length;s0++) for(s1=0;s1<segments[s0].data.length;s1++)
  { datum = segments[s0].data[s1] ;
    if(datum.marker!=this) continue ; 
    types = datum.labelcycle() ;
    caption = datum.marker.title ;
    selected = [s0,s1] ; 
    done(['editlabel',s0,s1,caption,caption,types[0],types[1]]) ; 
    if(flag) wpinfo() ; 
    return ;
  }
}
function photoprompt(e) 
{ var s0=selected[0],s1=selected[1] ;
  if(e!=null) e.preventDefault() ;
  var flag = (infowindow.close()=='wpinfo') ; 
  var datum = segments[s0].data[s1] ;
  var photo = window.prompt('Enter photo name:','') ;

  if(photo!=null&&photo!='') 
  { done(['editphoto',s0,s1,datum.photo.length,null,photo]) ; 
    datum.addphoto(photo) ; 
  }
  if(flag) wpinfo() ; else walkto(s0,s1) ; 
}
function photoedit(ind)
{ var s0=selected[0],s1=selected[1],i ;
  var flag = (infowindow.close()=='wpinfo') ; 
  var datum = segments[s0].data[s1] ;
  var photo = window.prompt('New photo name:',datum.photo[ind]) ;

  if(photo!=null&&photo!='') for(i=0;i<datum.photo.length;i++)
    if(datum.photo[i]==photo) { photo = null ; break ; }
  if(photo!=null)
  { if(photo=='') photo = null ;
    done(['editphoto',s0,s1,ind,datum.photo[ind],photo]) ; 
    datum.setphoto(ind,photo) ; 
  }
  if(flag) wpinfo() ; else walkto(s0,s1) ; 
}
/* -------------------------------------------------------------------------- */
/*page*/
/* ----------------------------- display photo ------------------------------ */

var lmove,rmove ; 

function advance(s0,s1,ind)
{ for(ind++;;ind++)
  { if(ind>=segments[s0].data[s1].photo.length) { ind = 0 ; s1 += 1 ; }
    if(s1==segments[s0].data.length) 
    { s0 += 1 ; if(s0==segments.length) return null ; s1 = 0 ; }
    if(ind<segments[s0].data[s1].photo.length) return [s0,s1,ind] ;
  }
}
function retreat(s0,s1,ind)
{ for(ind--;;ind--)
  { if(ind<0) { s1 -= 1 ; ind = null ; }
    if(s1<0)
    { if(s0==0) return null ; else s0 -= 1 ; 
      s1 = segments[s0].data.length-1 ; 
    }
    if(ind==null) ind = segments[s0].data[s1].photo.length - 1 ;
    if(ind>=0) return [s0,s1,ind] ;
  }
}
function prev() { dodisplay(lmove[0],lmove[1],lmove[2],-1) ; }
function backtogps() 
{ document.onkeydown = keystroke ; 
  window.removeEventListener('resize',resize) ; 
  body.removeChild(imgdiv) ; 
  walkto(selected[0],selected[1],0) ;
}
function next() { dodisplay(rmove[0],rmove[1],rmove[2],1) ; }

function display(ind)
{ document.onkeydown = imgwalk ;
  window.addEventListener('resize',resize) ; 
  infowindow.close() ; 
  imgdiv = document.createElement('div') ; 
  imgdiv.setAttribute('style','position:fixed;width:100%;height:100%;'+
                      'left:0;top:0;background:black') ;
  dodisplay(selected[0],selected[1],ind,1) ; 
  body.appendChild(imgdiv) ; 
}
function dodisplay(s0,s1,ind,dir)
{ var phind=findimg(segments[s0].data[s1].photo[ind]) , pre=null ;
  selected[0] = s0 ; 
  selected[1] = s1 ; 
  lmove = retreat(s0,s1,ind) ;
  rmove = advance(s0,s1,ind) ;
  if(dir<0&&lmove!=null)
    pre = findimg(segments[lmove[0]].data[lmove[1]].photo[lmove[2]]) ;
  else if(dir>=0&&rmove!=null)
    pre = findimg(segments[rmove[0]].data[rmove[1]].photo[rmove[2]]) ;
  if(pre!=null) pre = imginfo.list[pre] ;

  gendisplay(imgdiv,imginfo.list[findimg(segments[s0].data[s1].photo[ind])],
             imginfo.sizes,lmove==null?null:'javascript:prev()',
             'javascript:backtogps()',rmove==null?null:'javascript:next()',
             'GPS track',pre) ; 
}
/* -------------------------------------------------------------------------- */
/*page*/
/* ------------------------------ image walk -------------------------------- */

function imgwalk(e)
{ e.preventDefault() ; 

  if(e.keyCode==39) { if(rmove!=null) next() ; return ; }
  else if(e.keyCode==37) { if(lmove!=null) prev() ; return ; }
  else if(e.keyCode==40) reduce() ; 
  else if(e.keyCode==38) enlarge() ; 
  else if(e.keyCode==70) enterfullscreen() ; 
  else backtogps() ;
}
/* --------------------------------- photo info ----------------------------- */

function phinfo(i) 
{ infowindow.close() ; 
  var s0=selected[0],s1=selected[1] ; 
  infowindow.open(phdiv(i),segments[s0].data[s1].pos,'phinfo') ; 
}
/* ------------------------- snip: apply scissors  -------------------------- */

function snipwork(s0,s1)
{ var i,k,newlen ; 
  undraw(s0) ; 
  segments.length += 1 ; 
  for(i=segments.length-1;i>s0+1;i--) segments[i] = segments[i-1] ; 

  newlen = segments[s0].data.length - s1 ;
  segments[s0+1] =  new genseg(segments[s0].data.slice(s1),segments[s0].props) ;
  segments[s0+1].dots = segments[s0].dots ;
  segments[s0+1].dothandler = segments[s0].dothandler ;
  segments[s0].dots = segments[s0].dothandler = null ; 

  segments[s0].data.length = s1 + 1 ; 
  segments[s0].data[s1] = 
    new datatype(segments[s0].data[s1].pos,segments[s0].data[s1].h) ; 
  draw(s0) ;
  draw(s0+1) ; 
  for(i=s0+2;i<segments.length;i++) recolour(i) ;
  drawsel(1,[s0+1,0]) ; 
  greyout(dlbtn) ; 
}
function snip()
{ var i,s0=selected[0],s1=selected[1] ; 
  infowindow.close() ; 
  done(['snip',s0,s1]) ; 
  snipwork(s0,s1) ; 
}
/* ------------------------ discard: bin a segment  ------------------------- */

function binwork(s0)
{ var i ; 
  obliterate(s0) ; 
  for(i=s0;i<segments.length-1;i++) segments[i] = segments[i+1] ; 
  segments.length -= 1 ; 

  for(i=s0;i<segments.length;i++) recolour(i) ;
  connect(s0-1) ;

  selected[1] = 0 ; 
  if(selected[0]==segments.length) selected[0] = 0 ; 
  drawsel(1) ; 
  if(segments.length==1) blackout(dlbtn) ; 
}
function discard()
{ var i,s0=selected[0] ; 
  infowindow.close() ;  
  done(['bin',s0,segments[s0]]) ; 
  binwork(s0) ; 
}
/* -------------------------------------------------------------------------- */

function actiontype(x)
{ if( x=='snip'||x=='combine'||x=='interpolate'
   || x=='optimise'||x=='load' ) return 0 ; else return 1 ;
}
function done(something) 
{ if( nactions>0 && unsavedchanges.length>0 && something[0]=='editlabel'
   && actions[nactions-1][0]==something[0]
   && actions[nactions-1][1]==something[1] // don't merge change with delete
   && actions[nactions-1][2]==something[2] && something[6]!=null )
  { actions[nactions-1][4] = something[4] ; // caption
    actions[nactions-1][6] = something[6] ; // type
  }
  else { actions[nactions++] = something ; donesomething() ; }
}
function donesomething()
{ actions.length = nactions ; 
  blackout(undobtn) ; 
  greyout(redobtn) ; 
  if(actiontype(actions[nactions-1][0])!=0) 
  { if(unsavedchanges.length>=3) unsavedchanges.push(null) ; 
    else unsavedchanges.push(actionname(actions[nactions-1])) ;
  }
}
/* -------------------------------------------------------------------------- */
/*page*/
/* --------------------------------- undo  ---------------------------------- */

function undo()
{ infowindow.close() ;  
  var opts = 'Undo ' + actionname(actions[nactions-1]) ;
  infowindow.open(genclickable('confirmedundo()',opts),getbtnpos(5),'undo') ; 
}
function confirmedundo()
{ var i,ano=nactions-1,action=actions[ano][0],s0=actions[ano][1],s1,caption ;
  var oldcaption,task,ind ; 
  infowindow.close() ;  

  if(action!='revseg'&&action!='interpolate'&&action!='deltimes') 
    s1 = actions[ano][2] ;

  if(action=='bin') 
  { disconnect(s0-1) ; 
    for(i=segments.length;i>s0;i--) 
    { segments[i] = segments[i-1] ; recolour(i) ; } 
    segments[s0] = s1 ;
    for(s1=0;s1<segments[s0].data.length;s1++)
      segments[s0].data[s1].setmap(map) ; 
    draw(s0) ; 
    connect(s0-1) ; 
    connect(s0) ; 
    if(selected[0]>=s0) selected[0] += 1 ; 
    drawsel(1) ; 
    greyout(dlbtn) ; 
  }
  else if(action=='snip') // undo snip
  { selected = [ s0 , segments[s0].data.length-1 ] ; 
    combine1(s0,s0+1) ; 
    for(i=s0+1;i<segments.length-1;i++) 
    { segments[i] = segments[i+1] ; recolour(i) ; } 
    segments.length -= 1 ; 
    if(segments.length==1) blackout(dlbtn) ; 
    draw(s0) ;
    drawsel(1) ; 
  } 
  else if(action=='editlabel')  // undo create/edit/delete label
    segments[s0].data[s1].setlabel(actions[ano][5],actions[ano][3]) ;
  else if(action=='edittitle') settitle(s0) ; 
  else if(action=='editdescription') setdesc(s0) ; 
  else if(action=='wpdel')      // ['wpdel',s0,s1,wpdelwork(s0,s1)]
  { insert(s0,s1,1) ; 
    segments[s0].data[s1] = actions[ano][3] ;
    segments[s0].data[s1].setmap(map) ;
    redrawconnect(s0,s1) ;
    drawsel(1,[s0,s1]) ; 
  }
  else if(action=='move')
  { if(actions[ano][5]) wpdelwork(s0,s1) ; else move(s0,s1,actions[ano][3]) ; }
  else if(action=='recal') calwork(s0,-s1) ; 
  else if(action=='setalt') segments[s0].data[s1].h = actions[ano][3] ;
  else if(action=='combine') uncombine(actions[ano]) ; 
  else if(action=='revseg') revsegwork(s0) ; 
  else if(action=='stars') routeprops.stars = s0 ; 
  else if(action=='deltimes') for(i=0;i<s0.length;i++) 
    segments[s0[i][0]].data[s0[i][1]].t = s0[i][2] ;
  else if(action=='optimise') // [ 'load' , s0 , data.slice() , props ]
  { for(ano--;ano>=0&&actions[ano][0]!='load';ano--) ;
    segments[s0].data = actions[ano][2] ;
    actions[loadno][3].optim.ndel = 0 ; 
    redraw(s0) ;
    drawsel(1,[s0,0]) ; 
  }
  else if(action=='editphoto') 
  { ind = actions[ano][3] ;
    if(actions[ano][5]==null)      // undo delete
      for(i=segments[s0].data[s1].photo.length;i>ind;i--)
        segments[s0].data[s1].photo[i] = segments[s0].data[s1].photo[i-1] ;
    if(ind>=segments[s0].data[s1].photo.length)
      segments[s0].data[s1].addphoto(actions[ano][4]) ;
    else segments[s0].data[s1].setphoto(ind,actions[ano][4]) ;
  }
  else if(action=='extra') 
    for(selected=[s0,s1],i=actions[ano].length-1;i>=3;i--)
  { task = actions[ano][i]
    segments[task[0]].data.splice(task[1],task[2].length-2) ; 
  }

  nactions -= 1 ; 
  if(nactions<=1) greyout(undobtn) ; 
  blackout(redobtn) ; 
  if(actiontype(actions[nactions][0])!=0&&unsavedchanges.length>0)
    unsavedchanges.length -= 1 ;  ;
  if( action=='optimise' || action=='dltimes' || action=='stars'
   || action=='editdescription') routeinfo() ; 
  else if(action=='editphoto'||action=='editlabel') walkto(s0,s1) ;
}
/* --------------------------------- move ----------------------------------- */

function move(s0,s1,pos)
{ segments[s0].data[s1].setpos(pos) ; redrawconnect(s0,s1) ; drawsel(1) ; }

/* -------------------------------------------------------------------------- */
/*page*/
/* --------------------------------- redo  ---------------------------------- */

function redo()
{ infowindow.close() ;  
  var opts = 'Redo ' + actionname(actions[nactions]) ;
  infowindow.open(genclickable('confirmedredo()',opts),getbtnpos(6),'redo') ; 
}
function confirmedredo()
{ var i,action=actions[nactions][0],s0=actions[nactions][1],s1,caption,a,b,c ;
  var task,ind,photo ; 
  if(action!='bin'&&action!='revseg'&&action!='interpolate'&&action!='deltimes')
    s1 = actions[nactions][2] ;
  infowindow.close() ; 

  if(action=='bin') binwork(s0) ; 
  else if(action=='snip') snipwork(s0,s1) ; 
  else if(action=='editlabel') // redo create/edit/delete label
    segments[s0].data[s1].setlabel(actions[nactions][6],actions[nactions][4]) ;
  else if(action=='edittitle') settitle(s1) ; 
  else if(action=='editdescription') setdesc(s1)  ; 
  else if(action=='wpdel') wpdelwork(s0,s1) ; 
  else if(action=='move')     // ['move',s0,s1,oldpos,newpos,inserted]
  { if(actions[nactions][5]) insert(s0,s1,1) ; 
    move(s0,s1,actions[nactions][4]) ; 
  }
  else if(action=='recal') calwork(s0,s1) ; 
  else if(action=='setalt') segments[s0].data[s1].h = actions[nactions][4] ;
  else if(action=='combine') recombine(actions[nactions]) ; 
  else if(action=='revseg') revsegwork(s0) ; 
  else if(action=='stars') routeprops.stars = s1 ; 
  else if(action=='deltimes') for(i=0;i<s0.length;i++) 
    segments[s0[i][0]].data[s0[i][1]].t = null ;
  else if(action=='optimise') 
  { result = optimise(segments[s0].data,actions[nactions][2]) ; 
    actions[loadno][3].optim.ndel = segments[s0].data.length - result.length ;
    segments[s0].data = result ; 
    redraw(s0) ;
    drawsel(1,[s0,0]) ; 
    routeinfo() ; 
  }
  else if(action=='editphoto') 
  { ind = actions[nactions][3] ;
    photo = actions[nactions][5] ;
    if(actions[nactions][4]==null) segments[s0].data[s1].addphoto(photo) ;
    else segments[s0].data[s1].setphoto(ind,photo) ; 
  }
  else if(action=='extra') 
    for(selected=[s0,s1],i=3;i<actions[nactions].length;i++)
  { task = actions[nactions][i] ;
    a = segments[task[0]].data.slice(0,task[1]) ;
    b = task[2].slice(1,task[2].length-1) ; 
    c = segments[task[0]].data.slice(task[1]) ;
    segments[task[0]].data = a.concat(b,c) ; 
  }

  nactions += 1 ; 
  if(nactions==actions.length) greyout(redobtn) ; 
  blackout(undobtn) ; 
  if(actiontype(actions[nactions-1][0])!=0) unsavedchanges.push(action) ;
  if(action=='stars'||action=='editdescription') routeinfo() ; 
  else if(action=='editphoto'||action=='editlabel') walkto(s0,s1) ;
}
/* -------------------------------------------------------------------------- */
/*page*/
/* ---------------------------------- dl  ----------------------------------- */

function dl(opt) 
{ var str,i,npix,s0,s1,filename ;
  infowindow.close() ; 
  if(opt==undefined||opt==0) { opt = 0 ; getalts(1) ; }

  // filename
  i = routeprops.title.indexOf(' ') ;
  if(i<=0) filename = routeprops.title ; 
  else filename = routeprops.title.substring(0,i) ;
  if(filename==''||filename==null) filename = 'Untitled' ; 
  filename += '.tcx' ; 

  // check for photos
  for(npix=s0=0;s0<segments.length;s0++) 
    for(s1=0;s1<segments[s0].data.length;s1++) 
      npix += segments[s0].data[s1].photo.length ;

  // photo list
  if(npix>0&&imginfo.status=='ready')
  { if(imginfo.type=='tcx') routeprops.list = imginfo.uri ; // 'tcx' vice 'uri'
    else 
    { routeprops.list = document.URL ;
      if((i=routeprops.list.lastIndexOf('?'))>=0) 
        routeprops.list = routeprops.list.substring(0,i) ; 
      routeprops.list = reluri(routeprops.list,imginfo.uri) ; 
    }
  }

  if(opt) // write index and return 
  { str = writeoverview(segments,routeprops.title,routeprops.list) ;  
    saveAs(new Blob([str],{type: "text/plain;charset=utf-8"}),filename) ;
    return ;
  }

  // record optimisation 
  routeprops.optim.origlen = routeprops.optim.ndel = 0 ; 
  routeprops.optim.parms = null ; 
  for(i=0;i<nactions;i++) if(actions[i][0]=='load')
  { routeprops.optim.ndel += actions[i][3].optim.ndel ; 
    routeprops.optim.origlen += actions[i][3].optim.origlen ; 
    routeprops.optim.parms = actions[i][3].optim.parms ;
  }
  
  str = writetcx(routeprops,segments[0].data) ;  
  if(str==null) return ; 
  unsavedchanges = [] ; 
  saveAs(new Blob([str],{type: "text/plain;charset=utf-8"}),filename) ;
}
/* -------------------------------------------------------------------------- */

Archived from routemaster.html

/* ---------------------------- relative uri  ------------------------------- */

function reluri(u1,u2) 
{ var last = u1.lastIndexOf('/') ; 
  if(last<0) return u2 ; 
  u1 = u1.substring(0,last) ; 

  while(u2.substring(0,3)=='../')
  { last = u1.lastIndexOf('/') ; 
    if(last<0) return u2 ; 
    u2 = u2.substring(3) ; 
    u1 = u1.substring(0,last) ; 
  }
  return u1 + '/' + u2 ; 
}
/* -------------------------------------------------------------------------- */

function underline(d) 
{ d.setAttribute('style',
      'margin-bottom:2px;border-bottom:solid 1px silver;padding-bottom:2px') ; 
  return d ; 
}

function textdiv(title,body,lim)
{ var div=document.createElement('div'),b,nobr,flag=0 ;
  if(lim==undefined||lim==0) lim = null ; 
  else if(lim<0) { flag = 1 ; lim = -lim ; } 

  if(title!=null) 
  { b = document.createElement('b') ;
    b.appendChild(document.createTextNode(title+': ')) ;
  }

  if(lim==null||body.length<lim)
  { nobr = document.createElement('nobr') ;
    if(title!=null) nobr.appendChild(b) ;
    nobr.appendChild(document.createTextNode(body)) ;
    div.appendChild(nobr) ;
  }
  else 
  { if(title!=null) div.appendChild(b) ; 
    div.appendChild(document.createTextNode(body)) ; 
    if(flag==0) underline(div) ;  
  }
  return div ; 
}
/* -------------------------------------------------------------------------- */

function seginfodiv(segments,segno)
{ var div=document.createElement('div'),props=segments[segno].props,prose,lim ; 
  div.appendChild(textdiv(null,'Segment ' + segno + ' of ' +
    segments.length + ' (' + segments[segno].data.length + ' points)')) ;
  if(props.title!=null) div.appendChild(textdiv('Title',props.title)) ;
  if(props.source!=null) div.appendChild(textdiv('Source',props.source[0])) ;
  if(props.stats==null) lim = -50 ; else lim = 50 ; 
  if(props.desc!=null) div.appendChild(textdiv('Description',props.desc,lim)) ;
  if(props.stats!=null) div.appendChild(textdiv('Stats',props.stats)) ;
  return div ; 
}
/* -------------------------------------------------------------------------- */
/*page*/
/* -------------------------------------------------------------------------- */

function highdiv(props,list,sizes,sizeno,names) 
{ var div,scroll,p,a,d,dwid,nfetched=2 ;
  var items,i,ind,maxh,minw,sum,scroll,image=[null,null,null] ;
  for(items=[],i=0;i<names.length;i++) if((ind=findimage(list,names[i]))!=null)
  { scroll =  { ind:     ind,
                shape:   imgshape(list[ind],sizes,sizeno),
                top:     0 } ;
    items.push(scroll) ;
  }
  div = document.createElement('div') ;
  div.setAttribute('style','font-family:helvetica') ; 

  if(items.length>0)  
  { minw = items[0].shape[0] ;
    if(items.length>1) minw += items[items.length-1].shape[0] ;
    for(i=0;i<2&&i<items.length;i++) image[i] = new scrolltype(i) ; 

    for(maxh=i=0;i<items.length;i++)
    { if(items[i].shape[1]>maxh) maxh = items[i].shape[1] ;
      if(i)
      { sum = items[i].shape[0] + items[i-1].shape[0] ;
        if(sum<minw) minw = sum ;
      }
    }
    for(i=0;i<items.length;i++) 
      items[i].top = Math.floor(0.5+(maxh-items[i].shape[1])/2) ;

    scroll = document.createElement('div') ; 
    if(items.length==1) minw -= 4 ; 
    scroll.setAttribute('style','position:absolute;width:'+(minw+4)+'px;'+
                                'height:'+maxh+'px;overflow:hidden') ; 
    for(sum=i=0;i<2&&i<items.length;sum+=items[i].shape[0]+4,i++)
    { image[i].addimage(sum) ; scroll.appendChild(image[i].img) ; }
    div.appendChild(scroll) ;

    p = document.createElement('div') ; 
    p.setAttribute('style','width:'+(minw+4)+'px;'+'height:'+(maxh+4)+'px') ;
    div.appendChild(p) ;
  }

  if(props.title!=null) div.appendChild(textdiv('Title',props.title)) ; 
  if(props.stars!=null) div.appendChild(starsline(props.stars,0)) ;
  if(props.desc !=null) 
  { d = textdiv('Description',props.desc,50) ;
    if(items.length>0&&props.desc.length>=50) 
    { if(minw+4<400) dwid = 400 ; else dwid = minw+4 ;  
      d.setAttribute('style','width:'+dwid+'px') ; 
    }
    div.appendChild(d) ; 
  }
  if(props.stats!=null) div.appendChild(textdiv('Stats',props.stats)) ; 

  if(props.tracklink!=null)
  { nobr = document.createElement('nobr') ;
    a = document.createElement('a') ;
    a.setAttribute('style',
                   'cursor:pointer;color:#0000bd;text-decoration:none') ; 
    a.setAttribute('href',props.tracklink) ; 
    a.setAttribute('target',"_blank") ; 
    a.setAttribute('onclick',"infowindow.close()") ; 
    a.appendChild(document.createTextNode('View track')) ;
    nobr.appendChild(a) ;
    nobr.appendChild(document.createTextNode(' (opens in new tab/window)')) ;
    div.appendChild(nobr) ;
  }
  return { div:div , scroller: items.length<=2?null:setInterval(scroller,30) } ;

  function scrolltype(i)
  { this.img = this.top = this.pos = null ; 
    this.ind = i ; // index into items
    this.wid = items[i].shape[0] ;
    this.addimage = function(pos)
    { var fetch=null,ind=this.ind ; 
      if(ind<items.length-1&&nfetched<=ind)
      { nfetched += 1 ; 
        fetch = function() 
        { new Image().src = jpg(list[items[ind+1].ind],sizes,sizeno) ; } ;
      }
      this.img = genimage(list[items[ind].ind],sizes,sizeno,fetch) ;
      this.pos = pos ; 
      this.scrollimage() ; 
    }
    this.scrollimage = function()
    { this.img.setAttribute('style',
        'position:absolute;top:'+items[this.ind].top+'px;left:'+this.pos+'px') ;
    }
  }
  function scroller()
  { var i,ind,offset ; 
    for(i=0;i<3;i++) if(image[i]!=null) image[i].pos -= 1 ; 
    if(image[0].pos+image[0].wid<=0)
    { scroll.removeChild(image[0].img) ; 
      for(i=0;i<2;i++) image[i] = image[i+1] ; 
      image[2] = null ; 
    } 
    for(i=0;i<3&&image[i]!=null;i++) image[i].scrollimage() ;
    offset = image[i-1].pos + image[i-1].wid + 4 ;
    if(offset<minw)
    { if(image[i-1].ind==items.length-1) ind = 0 ; 
      else ind = image[i-1].ind + 1 ; 
      image[i] = new scrolltype(ind) ;
      image[i].addimage(offset) ;
      scroll.appendChild(image[i].img) ; 
    }
  }
}
/* -------------------------------------------------------------------------- */
/*page*/
/* -------------------------------------------------------------------------- */

function greybtn(uri,name)
{ if(name=='cursor') name = 'arrow' ; else name = 'grey' + name ; 
  return uri + name + '.png' ;
}
function blackbtn(uri,name)
{ if(name=='cursor') name = 'hand' ; else name = 'black' + name ; 
  return uri + name + '.png' ;
}
function buttonimg(gif)
{ var img = document.createElement('img') ;
  img.setAttribute('src',gif) ; 
  img.setAttribute('width','24') ; 
  img.setAttribute('height','24') ; 
  return img ;
}
function buttoncell(gif1,gif2) 
{ var td=document.createElement('td'),nobr=document.createElement('nobr') ;
  td.setAttribute('style','padding-bottom:4px') ; 
  nobr.appendChild(buttonimg(gif1)) ; 
  if(gif2!=null&&gif2!=undefined)
  { nobr.appendChild(document.createTextNode(' ')) ; 
    nobr.appendChild(buttonimg(gif2)) ;
  }
  td.appendChild(nobr) ; 
  return td ;
}
function textcell(p1,p2) 
{ var td=document.createElement('td'),nobr ;
  td.setAttribute('style','padding-bottom:4px') ; 
  nobr = document.createElement('nobr') ;
  nobr.appendChild(document.createTextNode(p1)) ;
  td.appendChild(nobr) ;
  if(p2!=null&&p2!=undefined)
  { td.appendChild(document.createElement('br')) ;
    nobr = document.createElement('nobr')
    nobr.appendChild(document.createTextNode(p2)) ;
    td.appendChild(nobr) ;
  }
  return td ;
}
function appendrow(td,p)
{ var nobr = document.createElement('nobr') ;
  nobr.appendChild(document.createTextNode(p)) ; 
  td.appendChild(nobr) ;
  td.appendChild(document.createElement('br')) ;
}
function genlink(uri,legend,blank)
{ var a = document.createElement('a') ;
  a.setAttribute('style','cursor:pointer;color:#0000bd;text-decoration:none') ; 
  a.setAttribute('href',uri) ; 
  if(blank!=undefined) a.setAttribute('target','_blank') ; 
  a.appendChild(document.createTextNode(legend)) ; 
  return a ;
}
function genspan(legend,bropt,spanstyle)
{ var span = document.createElement(bropt=='hr'?'div':'span') ; 
  span.appendChild(document.createTextNode(legend)) ; 
  if(spanstyle==undefined) spanstyle = '' ; 
  else if(spanstyle!='') spanstyle += ';' ; 
  if(bropt==']br') 
  { span.appendChild(document.createTextNode(']')) ; bropt = 'br' ;} 
  if(bropt=='br') span.appendChild(document.createElement('br')) ; 
  else if(bropt=='hr') spanstyle += 
    'margin-bottom:2px;border-bottom:solid 1px silver;padding-bottom:2px' ;
  if(spanstyle!='') span.setAttribute('style',spanstyle) ; 
  return span ;
}
function genclickable(action,legend,bropt)
{ var span = genspan(legend,bropt,'cursor:pointer;color:#0000bd') ; 
  span.setAttribute('onclick',action) ; 
  return span ;
}
/* -------------------------------------------------------------------------- */
/*page*/
/* -------------------------------------------------------------------------- */

function blurbdiv(uri)
{ var div=document.createElement('div'),img=document.createElement('img'),b,p ;
  img.setAttribute('width',16) ; 
  img.setAttribute('height',16) ; 
  img.setAttribute('src',uri+'bus.gif') ; 
  div.appendChild(img) ;
  b = document.createElement('b') ;
  b.appendChild(document.createTextNode
                ('\u00a0\u00a0Routemaster GPS track editor:')) ;
  div.appendChild(b) ;
  div.appendChild(document.createElement('br')) ;
  p = document.createElement('div') ;
  p.appendChild(document.createTextNode
    ('The main use of routemaster is to load GPS tracks, display them, allow'+
     ' them to be edited in various ways, and to save them back to disc.')) ;
  p.setAttribute('style','padding-top:4px') ;
  div.appendChild(p) ; 
  p = document.createElement('div') ;
  p.appendChild(document.createTextNode
    ('It may also be used to display tracks on a website (see the example '+
     'track in the links below) or to display an index of tracks in an '+
     'area (for which there is also an example). However these uses require '+
     'you to host your own instance of the tool.')) ;
  p.setAttribute('style','text-indent:14px;border-bottom:solid 1px silver;'+
                         'padding-bottom:2px') ;
  div.appendChild(p) ; 
  return div ; 
}
/* -------------------------------------------------------------------------- */

function northernmost(data)
{ var i,maxlat,maxi ; 
  for(i=0;i<data.length;i++) if(i==0||data[i].pos.lat()>maxlat)
  { maxi = i ; maxlat = data[i].pos.lat() ; }
  return data[maxi].pos ;
}
/* -------------------------------------------------------------------------- */

function helpdiv(uri,noblank)
{ var div=document.createElement('div'),d,t,tr,td,a ; 
  if(noblank==undefined) noblank = 0 ; 
  t = document.createElement('table') ;
  t.setAttribute('cellpadding',0) ; 
  t.setAttribute('cellspacing',0) ; 
  t.setAttribute('style','font-size:100%') ; 

  tr = document.createElement('tr') ;
  tr.appendChild(buttoncell(greybtn(uri,'cursor'),blackbtn(uri,'cursor'))) ;

  td = document.createElement('td') ;
  td.setAttribute('rowspan',100) ; 
  td.appendChild(document.createTextNode('\u00a0\u00a0\u00a0')) ; // &nbsp;
  tr.appendChild(td) ; 

  tr.appendChild(textcell
    ('toggle between using the mouse to select waypoints and to drag the map',
     '(the space bar has the same function)')) ;
  t.appendChild(tr) ; 

  tr = document.createElement('tr') ;
  tr.appendChild(buttoncell(blackbtn(uri,'settings'),blackbtn(uri,'dl'))) ; 
  tr.appendChild(textcell('access to help menu and to various tools and ' + 
                          'functions /','download route as .tcx')) ; 
  t.appendChild(tr) ; 

  tr = document.createElement('tr') ;
  tr.appendChild(buttoncell(blackbtn(uri,'scissors'),blackbtn(uri,'bin'))) ; 
  tr.appendChild(textcell
    ('split the current segment at the selected point /',
     'delete the currrent segment (or use the [shift delete] '+
     'or [shift backspace] key)')) ;
  t.appendChild(tr) ; 

  tr = document.createElement('tr') ;
  tr.appendChild(buttoncell(blackbtn(uri,'pen'))) ; 
  tr.appendChild(textcell
    ('add a labelled coursepoint at the current position (1-10chars)',
     'click on flag to edit; right-click to change symbol; '+
     'delete label to delete')) ;
  t.appendChild(tr) ; 

  /* ------------------------------------------------------------------------ */
  /*page*/
  /* ------------------------------------------------------------------------ */

  tr = document.createElement('tr') ;
  td = document.createElement('td') ;
  td.setAttribute('valign','top') ; 
  td.appendChild(document.createTextNode('Keyboard: ')) ; 
  tr.appendChild(td) ; 

  td = document.createElement('td') ;
  appendrow(td,
            '\u2190/\u2192 move the current waypoint forwards or backwards;') ;
  appendrow(td,
       '[shift \u2190]/[shift \u2192] move it to the previous/next segment;') ;
  appendrow(td,'\u2193 centres the map on the current waypoint;') ;
  appendrow(td,'[return] makes the current waypoint draggable;') ;
  appendrow(td,'[tab] inserts a draggable waypoint;') ;
  appendrow(td,'[space] = toggle cursor mode;') ;
  appendrow(td,'[del], [backspace] = delete waypoint;') ;
  appendrow(td,
       '[shift del], [shift backspace] = delete segment (=bin button).') ;
  tr.appendChild(td) ; 
  t.appendChild(tr) ; 

  tr = document.createElement('tr') ;
  td = document.createElement('td') ;
  td.setAttribute('valign','top') ; 
  td.appendChild(document.createTextNode('Mouse: ')) ; 
  tr.appendChild(td) ; 

  td = document.createElement('td') ;
  appendrow(td,'when the cursor is in selection mode:') ;
  appendrow(td,
         '[shift click] extends the current segment by the cursor position.') ;
  tr.appendChild(td) ; 
  t.appendChild(tr) ; 

  d = document.createElement('div') ;
  d.appendChild(t) ; 
  div.appendChild(underline(d)) ; 

  /* ------------------------------------------------------------------------ */

  d = document.createElement('div')

  if(noblank)
  { a = genlink('http://www.masterlyinactivity.com/routemaster/?routes/'+
                'Caibros.tcx','Example track to experiment with') ; 
    d.appendChild(a) ; 
    d.appendChild(document.createElement('br')) ;
    a = genlink('http://www.masterlyinactivity.com/routemaster/?routes/'+
                'capeverde.tcx','Example of a route index') ; 
    d.appendChild(a) ; 
    d.appendChild(document.createElement('br')) ;
  }

  a = genlink('http://www.masterlyinactivity.com/software/routemaster.html',
              'Technical documentation and source code') ; 
  if(noblank==0) a.setAttribute('target','_blank') ;
  d.appendChild(a) ; 
  if(noblank==0) 
    d.appendChild(document.createTextNode(' (opens in new tab/window)')) ; 

  div.appendChild(d) ; 
  return div ; 
}
/* -------------------------------------------------------------------------- */
/*page*/
/* -------------------------------------------------------------------------- */
/*            THE MAIN POPUP MENU AND THE FUNCTIONS IT GOVERNS                */
/* -------------------------------------------------------------------------- */

function cogwheelmenu(dragopt,prof,ov)
{ var d = document.createElement('div') ;

  // dragging
  if(dragopt) 
  { d.appendChild(genspan('Hit [return] when you\'ve finished dragging','br')) ;
    return d ;
  }

  // route options
  d.appendChild(genclickable('routeinfo()','Route info','br')) ; 
  if(prof==0) 
    d.appendChild(genclickable('drawprofile()','Show altitude profile','br')) ;
  else d.appendChild(genclickable('unprofile()','Hide altitude profile','br')) ;
  d.appendChild(genclickable('addload(1)','Load new route','br')) ; 
  if(ov!=null)
  { d.appendChild(genlink(ov,'View route index','blank')) ;
    d.appendChild(genspan(' (opens in new tab/window)','br')) ; 
  }
  d.appendChild(genclickable('dl(1)','Download track as route index','hr')) ; 

  // segment options
  d.appendChild(genclickable('seginfo()','Segment info','br')) ; 
  if(unambig()) d.appendChild(genclickable('revseg()','Reverse segment','br')) ;
  else d.appendChild(genspan('Reverse segment','br','color:silver')) ;
  d.appendChild
    (genclickable('manualcal()','Calibrate segment altitudes','br')) ; 
  d.appendChild(genclickable('addload(0)','Load route as a new segment','hr')) ;

  // waypoint options
  d.appendChild(genclickable('wpinfo()','Waypoint info','br')) ; 
  if(segments[selected[0]].data.length>1) 
    d.appendChild(genclickable('wpdel()','Delete waypoint','br')) ; 
  else d.appendChild(genspan('Delete waypoint','br','color:silver')) ;
  d.appendChild(genclickable('draggit(0)','Make waypoint draggable','br')) ; 
  d.appendChild
    (genclickable('inswp(1)','Insert draggable waypoint ahead','br')) ; 
  d.appendChild
    (genclickable('inswp(-1)','Insert draggable waypoint behind','hr')) ; 

  // tool options
  if(querycanfullscreen())
  { if(queryfullscreen()==0) d.appendChild
                (genclickable('enterFullscreen()','Enter full screen','br')) ; 
    else d.appendChild
                (genclickable('exitFullscreen()','Leave full screen','br')) ; 
  } 
  d.appendChild(genclickable('help()','Help','br')) ; 
  return d ; 
}
/* -------------------------------------------------------------------------- */
/*page*/
/* -------------------------------------------------------------------------- */

function walktodiv(datum) 
{ var s,d=document.createElement('div'),dd,k,ind,imgname ; 

  if(datum.type!=null)
  { dd = document.createElement('div') ;
    s = datum.type + ': ' + datum.marker.title + ' [' ;
    dd.appendChild(document.createTextNode(s)) ;
    dd.appendChild(genclickable('labelprompt()','Edit',']br')) ;
    if(datum.photo.length>0) underline(dd) ; 
    d.appendChild(dd) ; 
  }

  for(ind=0;ind<datum.photo.length;ind++)
  { dd = document.createElement('div') ;
    if(imginfo.status!='ready') k = -1 ; 
    else k=findimg(datum.photo[ind])
    if(k>=0) 
    { s = genimage(imginfo.list[k],imginfo.sizes,imginfo.thumbind) ;
      dd.appendChild(s) ; 
      dd.appendChild(document.createElement('br')) ;
    }
    else 
    { s = 'Photo: ' + datum.photo[ind] + ' (' ;
      if(imginfo.status=='null') s += 'no list provided)' ;
      else if(imginfo.status=='ready') 
      { imgname = imginfo.uri ;
        k = imgname.lastIndexOf('/') ;
        if(k>=0) imgname = imgname.substring(k+1) ;
        s += 'not present in ' + imgname + ')' ; 
      }
      else if(imginfo.status=='waiting') s += imgname + ' is not available)' ;
      else s += 'imginfo.status = ' + imginfo.status + ')' ; 
      dd.appendChild(genspan(s),'br') ;
    }
    dd.appendChild(document.createTextNode('[')) ;
    dd.appendChild(genclickable('photoedit('+ind+')','Edit')) ;
    dd.appendChild(document.createTextNode(']')) ;
    if(k>=0)
    { dd.appendChild(document.createTextNode(' : [')) ;
      dd.appendChild(genclickable('phinfo('+k+')','Info')) ;
      dd.appendChild(document.createTextNode('] : [')) ;
      dd.appendChild(genclickable('display('+ind+')','Enlarge')) ;
      dd.appendChild(document.createTextNode(']')) ;
    }
    d.appendChild(underline(dd)) ; 
  }
  if(datum.photo.length>0) 
  { dd = document.createElement('div') ;
    dd.appendChild(document.createTextNode('[')) ;
    dd.appendChild(genclickable('photoprompt(null)','Add photo',']br')) ;
    d.appendChild(dd) ; 
  }
  return d ; 
}
/* -------------------------------------------------------------------------- */
/*page*/
/* -------------------------------------------------------------------------- */
/*           WPINFO IS A MENU GIVING ACCESS TO THE SETALT FUNCTION            */
/* -------------------------------------------------------------------------- */

function wpinfodiv() 
{ var s0=selected[0],s1=selected[1],alt,nalt,s,x,lat,lng,grad,gradstr,time ;
  var datum = segments[s0].data[s1] , pos = datum.pos ;
  var s , d = document.createElement('div') ; 

  lat = pos.lat() ; 
  lng = pos.lng() ; 
  if(lat>=0) s = lat.toFixed(5) + '\u00b0 N, ' ; 
  else { lat = -lat ; s = lat.toFixed(5) + '\u00b0 S, ' ; }
  if(lng>=0) s += lng.toFixed(5) + '\u00b0 E' ; 
  else { lng = -lng ; s += lng.toFixed(5) + '\u00b0 W' ; }
  d.appendChild(genspan(s,'br')) ;
    
  x = new LatLon(lat,lng) ; 
  if(lat>49.9&&lat<62&&lng>-12&&lng<2.5&&lat-1.5*lng<75) 
    s = 'OS grid ref: ' + OsGridRef.latLonToOsGrid(x) ;
  else s = 'UTM coords = ' + x.toUtm() ; 
  d.appendChild(genspan(s,'br')) ;

  alt = segments[s0].data[s1].h ;
  if(alt!=null) 
  { d.appendChild(genspan('Altitude: ' + alt.toFixed(0) + 'm [')) ;
    d.appendChild(genclickable('setalt(1)','Edit',']br')) ;
  }
  else d.appendChild(genclickable('setalt(0)','Set altitude','br')) ;

  time = segments[s0].data[s1].t ;
  if(time!=null&&time.getFullYear()>1980) 
  { d.appendChild(genspan('Date: '+time.toDateString(),'br')) ;
    d.appendChild(genspan('Time: '+time.toTimeString(),'br')) ;
  }

  if(datum.type!=null) 
  { d.appendChild(genspan(datum.type + ': ' + datum.marker.title + ' [')) ;
    d.appendChild(genclickable('labelprompt()','Edit',']br')) ;
  }

  if(alt==null||s1==segments[s0].data.length-1) nalt = null ; 
  else 
  { nalt = segments[s0].data[s1+1].h ; 
    if(nalt!=null) x = dist(pos,segments[s0].data[s1+1].pos) ; 
  }
  if(nalt!=null&&Math.abs(nalt-alt)<x) 
  { grad = 100*Math.asin((nalt-alt)/x) ; 
    gradstr = Math.abs(grad).toFixed(0) ; 
    if(gradstr=='0') d.appendChild(genspan('Flat','br')) ;
    else if(grad>0) d.appendChild(genspan('Climb '+gradstr+'%','br')) ;
    else d.appendChild(genspan('Descend '+gradstr+'%','br')) ;
  }
  if(segments.length>1) s = 'Segment '+s0+' p' ; else s = 'P' ;
  s += 'oint ' + s1 ; 
  d.appendChild(genspan(s,null,'font-size:80%')) ;
  return d ;
}
/* -------------------------------------------------------------------------- */

function titlediv(a,b,action,lim)
{ var bold,d=document.createElement('div') ; 
  if(lim==null||lim==undefined||b.length<=lim) 
  { d.setAttribute('style','white-space:nowrap') ;
    d.appendChild(document.createTextNode(a+': ')) ;
    bold = document.createElement('b') ;
    bold.appendChild(document.createTextNode(b)) ;
    d.appendChild(bold) ;
    d.appendChild(document.createTextNode(' [')) ;
  }
  else d.appendChild(document.createTextNode(a+': '+b+' [')) ;

  d.appendChild(genclickable(action+'()','Edit',']br')) ;
  return d ;
}
/* -------------------------------------------------------------------------- */
/*page*/
/* -------------------------------------------------------------------------- */

function routediv(rp,ov) 
{ var s0,s1,s,asc,des,oalt,alt,nlabels,nowpts,i,unsaved,props,spacing,npix ; 
  var maxsep,sep,outoforder,tlast,otime,time,tdist,ttime,ntimes,s,tdiv,ldiv ;
  var nnull , loadno , pr , dd , d = document.createElement('div') ; 

  for(loadno=nactions-1;loadno>=0&&actions[loadno][0]!='load';loadno--) ; 
  if(loadno>=0) pr = actions[loadno][3] ; else pr = null ; 
  tlast = null ;
  tdist = ttime = outoforder = dd = nnull = 0 ;
  maxsep = nlabels = npix = des = asc = nowpts = ntimes = 0 ;

  // calculate route properties 

  for(s0=0;s0<segments.length;s0++) 
  { nowpts += segments[s0].data.length ;

    for(oalt=null,s1=0;s1<segments[s0].data.length;otime=time,s1++)
    { if((alt=segments[s0].data[s1].h)==null) nnull += 1 ; 
      else
      { if(oalt!=null) 
        { if(alt>oalt) asc += alt-oalt ; else des += oalt - alt ; } 
        oalt = alt ;
      }
      if(segments[s0].data[s1].type!=null) nlabels += 1 ;
      npix += segments[s0].data[s1].photo.length ;

      time = segments[s0].data[s1].t ;
      if(time!=null) { time = time.getTime() ; ntimes += 1 ; }
      if(tlast!=null&&time!=null&&time<tlast) outoforder = 1 ; // out of order
      if(time!=null) tlast = time ;

      if(s1) 
      { sep = dist(segments[s0].data[s1-1].pos,segments[s0].data[s1].pos) ;
        dd += sep ; 
        if(sep>maxsep) maxsep = sep ; 
        if(time!=null&&otime!=null) 
        { tdist += sep ; ttime += (time-otime)/3600 ; }
      }
    }
  }

  // title
  d.appendChild(titlediv('Title',rp.title,'retitle',50)) ;

  // stars
  starsdiv = document.createElement('div') ; 
  starsdiv.setAttribute('style','color:#0000bd') ; 
  d.appendChild(starsline(rp.stars,1)) ;

  /* ------------------------------------------------------------------------ */
  /*page*/
  /* ------------------------------------------------------------------------ */

  // description
  if(rp.desc==null) 
  { tdiv = document.createElement('div') ; 
    tdiv.appendChild(document.createTextNode('[')) ;
    tdiv.appendChild(genclickable('redescribe()','Add description',']br')) ;
  }
  else tdiv = titlediv('Description',rp.desc,'redescribe',50) ;
  d.appendChild(underline(tdiv)) ;

  // last added route
  if(loadno>0)
  { s = '\u00a0\u00a0\u00a0Last added route' ;
    if(pr.title==null) s += ':' ; else s += ' (' + pr.title + '):' ;
    d.appendChild(genspan(s,'br')) ;
    s = '\u00a0\u00a0\u00a0' ;
  }
  else s = '' ;

  // number of track points and optimisation
  s += 'Track points on input: ' + pr.inputlen ; 
  if(pr.optim.already)  
    d.appendChild(genspan(s+' (previously optimised)','br')) ;
  else if(pr.optim.ndel==0) 
  { d.appendChild(genspan(s+' [')) ;
    if(nactions==loadno+1) 
      d.appendChild(genclickable('optimprompt()','Optimise','br')) ;
    else d.appendChild(genspan('Optimise',null,'color:silver','br')) ; 
  }
  else d.appendChild
         (genspan(s+', optimised to '+(pr.inputlen-pr.optim.ndel),'br')) ;
  if(!pr.optim.already&&pr.inputlen-pr.optim.ndel!=nowpts) 
    d.appendChild(genspan('Now ' + nowpts + ' track points','br')) ;

  // are there any missing altitudes?
  if(nnull) 
  { d.appendChild(genspan(nnull+' points have no associated altitudes [')) ;
    d.appendChild(genclickable('getalts(0)','Find altitudes',']br')) ;
  }

  // are points timed and are they in sequence?
  if(outoforder==0) 
  { if(ntimes==0) d.appendChild(genspan('No timings provided','br')) ;
    else 
    { if(ntimes>=nowpts) s = '[' ;
      else s = (nowpts-ntimes) + ' points have no associated timings [' ;
      d.appendChild(genspan(s)) ;
      d.appendChild(genclickable('deltimes()','Discard timings',']br')) ;
    }
  }
  else d.appendChild(genspan('Times are out of sequence (will be '+
                             'discarded on download)','br')) ;

 // labels and photos
  if(nlabels>0) d.appendChild
    (genspan(nlabels+' labelled course point'+(nlabels>1?'s':''),'br')) ; 
  if(npix>0) d.appendChild(genspan(npix+' photo'+(npix>1?'s':''),'br')) ; 

  /* ------------------------------------------------------------------------ */
  /*page*/
  /* ------------------------------------------------------------------------ */

  // unsaved changes
  unsaved = unsavedchanges.length ; 
  if(unsaved>0) d.appendChild
    (genspan(unsaved+' unsaved change'+(unsaved>1?'s':''),'br')) ;

  // number of segments - option to combine
  if(segments.length>1) 
  { d.appendChild(genspan(segments.length + ' segments [')) ;
    d.appendChild(genclickable('combine()','Combine',']br')) ;
    d.appendChild(genspan('Note that segments must be combined before saving',
                          'br','font-style:italic')) ;
  }
  
  // max waypoint separation - option to interpolate
  d.appendChild
    (genspan('Max waypoint separation: '+maxsep.toFixed(0)+'m','br')) ;
  if(maxsep>=100) 
  { d.appendChild(genspan('Note that separations \u003e100m are illegal '+
                          'on Garmin','br','font-style:italic')) ;
    d.appendChild(genspan('\u00a0\u00a0\u00a0[')) ;
    d.appendChild(genclickable('extrapts()','Interpolate extra points',']br')) ;
  }

  // distance / ascent / descent / average speed
  tdiv = document.createElement('div') ; 
  tdiv.setAttribute('style',
    'margin-top:2px;border-top:solid 1px silver;padding-top:2px') ; 

  function ldivadd(b)
  { ldiv.appendChild(document.createTextNode(b)) ;
    ldiv.appendChild(document.createElement('br')) ;
  } ;

  ldiv = document.createElement('div') ; 
  ldiv.setAttribute('style','float:left;padding-right:8px') ; 
  ldivadd('Total distance:') ;
  ldivadd('Total ascent:') ;
  ldivadd('Total descent:') ;
  if(outoforder==0&&tdist>0&&ttime>0) ldivadd('Average speed:') ;
  tdiv.appendChild(ldiv) ; 

  ldiv = document.createElement('div') ; 
  ldiv.setAttribute('style','float:left;text-align:right') ; 
  ldivadd((dd/1000).toFixed(3)) ;
  ldivadd(asc.toFixed(0)) ;
  ldivadd(des.toFixed(0)) ;
  if(outoforder==0&&tdist>0&&ttime>0) ldivadd((tdist/ttime).toFixed(1)) ;
  tdiv.appendChild(ldiv) ; 

  ldiv = document.createElement('div') ; 
  ldiv.setAttribute('style','float:left;padding-left:2px') ; 
  ldivadd('km') ;
  ldivadd('m') ;
  ldivadd('m') ;
  if(outoforder==0&&tdist>0&&ttime>0) ldivadd('km/hr') ;
  tdiv.appendChild(ldiv) ; 

  d.appendChild(tdiv) ; 
  return d ; 
}
/* -------------------------------------------------------------------------- */
/*page*/
/* -------------------------------------------------------------------------- */

function starsline(nstars,editable)
{ var i , c , s , d ;
  if(editable) 
  { if(starsdiv==null) return ; else d = starsdiv ; 
    while(d.childNodes.length>0) 
      d.removeChild(d.childNodes[d.childNodes.length-1]) ;
  }
  else d = document.createElement('div') ; 
 
  for(i=1;i<=5;i++) 
  { s = document.createElement('span') ;
    if(nstars==null||i>nstars) c = '\u2606' ; else c = '\u2605' ;
    s.appendChild(document.createTextNode(c)) ;
    if(editable&&i!=nstars) 
    { s.setAttribute('style','cursor:pointer') ; 
      function createfunc(i) { return function() { restars(nstars,i) ; } } ;
      s.onclick = createfunc(i) ; 
    }
    d.appendChild(s) ; 
  }
  if(editable&&nstars!=null) 
  { d.appendChild(genspan(' [')) ; 
    d.appendChild(genclickable('restars('+nstars+',null)','Clear')) ; 
    d.appendChild(genspan(']')) ; 
  }
  return d ; 
}
/* --------------------------------- photo info ----------------------------- */

function phdiv(i) 
{ var d=document.createElement('div') ,ind,hind,r,k,shape ;
  var list=imginfo.list , sizes=imginfo.sizes ;

  d.appendChild(genspan('Name: '+list[i].name,'br')) ;
  d.appendChild(genspan('Title: '+list[i].title,'br')) ;
  for(hind=null,ind=0;ind<i;ind++) if(list[ind].name==null) hind = ind ;
  if(hind!=null) 
    d.appendChild(genspan('Under \u201c'+list[hind].title+'\u201d','br')) ;

  // how many sizes?
  for(r=ind=0;ind<sizes.length;ind++) if(sizes[ind].type==undefined) r += 1 ;
  d.appendChild(genspan('Available in '+r+' size'+(r>1?'s: ':': '))) ;

  // print the sizes
  for(k=ind=0;ind<sizes.length;ind++) if(sizes[ind].type==undefined)
  { if(k==0) s = '' ; else { if(k==r-1) s = ' and ' ; else s = ', ' ; }
    shape = imgsize(list[i],sizes,ind) ;
    d.appendChild(genspan(s+shape[0]+'x'+shape[1])) ;
    k += 1 ; 
  }
  d.appendChild(document.createElement('br')) 

  shape = list[i].thumbshape ;
  d.appendChild(genspan('Thumb: '+shape[0]+'x'+shape[1],'br')) ;
  s += '<br>Thumb: ' + shape[0] + 'x' + shape[1] ;
  for(ind=0;ind<sizes.length;ind++) if(sizes[ind].type=='raw')
  { shape = imgsize(list[i],sizes,ind) ;
    d.appendChild(genspan('Raw: '+shape[0]+'x'+shape[1],'br')) ;
  }

  if(imginfo.pixpage!=null)
  { d.appendChild(genlink(imginfo.pixpage,'Full photo set',1)) ;
    d.appendChild(genspan(' (opens in new tab/window)','br')) ;
  }

  if(list[i].retid!=null)
  { for(hind=null,ind=0;ind<=i;ind++) 
      if(list[ind].retpage!=undefined&&list[ind].retpage!=null) 
        hind = list[ind].retpage + '.html#' + list[i].retid ; 
    if(hind!=null) 
    { d.appendChild(genlink(hind,'Route notes',1)) ;
      d.appendChild(genspan(' (opens in new tab/window)','br')) ;
    }
  }
  return d ; 
}
/* -------------------------------------------------------------------------- */
/*page*/
/* -------------------------------------------------------------------------- */
/*          FUNCTIONS FOR COMPUTING & DISPLAYING THE ALTITUDE PROFILE         */
/* -------------------------------------------------------------------------- */

function profiletype(x,y,sind)
{ var i,ymin,ymax  ;
  this.x = x ; 
  this.y = y ; 
  this.sind = sind ; 
  this.sum = x[x.length-1] ;
  for(ymin=ymax=null,i=0;i<y.length;i++) if(y[i]!=null) 
  { if(ymax==null||y[i]>ymax) ymax = y[i] ;
    if(ymin==null||y[i]<ymin) ymin = y[i] ;
  }
  if(ymin>0) { if(ymax>3*ymin) ymin = 0 ; else ymin *= 1 - (ymax/ymin-1)/2 ; }
  this.ymin = ymin ;
  this.ymax = ymax ; 
  this.yspan = Math.max(1,ymax-ymin) ; 
  this.prodiv = this.curdiv = this.curcan = this.curhandle = null ; 
}
// member functions
profiletype.prototype.getx = function(x) { return 10 + 600 * x / this.sum ; } ;
profiletype.prototype.locx = function(i) { return this.getx(this.x[i]) ; } ;
profiletype.prototype.gety = function(y) 
{ return 10 + 180*(this.ymax-y)/this.yspan ; } ;

profiletype.prototype.getxy = function(i)
{ return [this.getx(this.x[i]),this.y[i]==null?null:this.gety(this.y[i])] ; } ;

profiletype.prototype.getsel = function(x)
{ var lo=0,hi=this.x.length-1,m,s0 ;
  if(x<0) x = 0 ; else if(x>600) x = 600 ;
  x += 10 ; 
  while(hi>lo+1)  // binary search
  { m = Math.floor((lo+hi)/2) ; if(this.locx(m)>x) hi = m ; else lo = m ; }
  if(Math.abs(this.locx(lo)-x)<Math.abs(this.locx(hi)-x)) m = lo ; else m = hi ;
  for(s0=0;s0<this.sind.length-1&&this.sind[s0+1]<=m;s0++) ;
  return [ s0 , m-this.sind[s0] ] ;
} ;
/* -------------------------------------------------------------------------- */

function procoords(segments)
{ var n,x,y,s0,s1,sum,len,pos,oldpos,sind,i ;
  for(n=s0=0;s0<segments.length;s0++) n += segments[s0].data.length ;
  x = new Array(n) ;
  y = new Array(n) ;
  sind = new Array(segments.length+1) ;
  x[0] = 0 ; 

  for(ymax=ymin=null,sum=i=s0=0;s0<segments.length;s0++) 
    for(sind[s0]=i,len=segments[s0].data.length,s1=0;s1<len;s1++,i++)
  { y[i] = segments[s0].data[s1].h ; 
    pos = segments[s0].data[s1].pos ;
    if(i) sum = x[i] = sum + dist(pos,oldpos) ; 
    oldpos = pos ; 
  }
  sind[segments.length] = n ; 
  if(n==0) return null ; else return new profiletype(x,y,sind) ; 
}
/* -------------------------------------------------------------------------- */
/*page*/
/* -------------------------------------------------------------------------- */

function drawpro(pro)
{ var div=document.createElement('div'),c=document.createElement('canvas') ;
  var ctx,i,n,s0,s1,len,xinit,ox,step,xy ;

  div.setAttribute
    ('style','position:absolute;height:190px;right:0;top:0;width:620px') ; 
  c.setAttribute('width',620) ; 
  c.setAttribute('height',200) ; 
  div.appendChild(c) ;
  ctx = c.getContext("2d") ; 
  ctx.font = "10px Helvetica" ;
  ctx.lineWidth = 0 ; 
  ctx.globalAlpha = 0.6 ; 

  ctx.fillStyle = 'lightgray' ;
  ctx.rect(0,0,620,200) ;
  ctx.fill() ; 

  // draw a profile of each segment
  for(i=n=s0=0;s0<pro.sind.length-1;s0++) 
  { len = pro.sind[s0+1] - pro.sind[s0] ;
    if(s0&1) ctx.fillStyle = "#ff9999" ; else ctx.fillStyle = "#ff0000" ; 
    for(xinit=ox=null,s1=0;s1<len;s1++,n++,ox=xy[0])
    { xy = pro.getxy(n) ; 
      if(xy[1]!=null) 
      { if(xinit==null) 
        { ctx.beginPath() ; ctx.moveTo(xy[0],xy[1]) ; xinit = xy[0] ; }
        else ctx.lineTo(xy[0],xy[1]) ; 
      }
    }
    ctx.lineTo(xy[0],190) ; 
    ctx.lineTo(xinit,190) ; 
    ctx.closePath() ; 
    ctx.fill() ; 
  }

  // lines
  if(pro.yspan>2500) step = 1000 ; 
  else if(pro.yspan>1250) step = 500 ;
  else step = 100 ; 

  for(i=step*Math.floor(pro.ymin/step+1);i<pro.ymax;i+=step) 
  { y = 0.5 + pro.gety(i) ;
    ctx.beginPath() ; 
    ctx.lineWidth = 1 ; 
    ctx.strokeStyle = '#555' ; 
    ctx.moveTo(10,y) ;
    ctx.lineTo(610,y) ; 
    ctx.stroke() ; 
    ctx.strokeText(i,590,y-2) ;
  }
  pro.prodiv = div ; 

  // cursor
  pro.curdiv = document.createElement('div') ;
  pro.curdiv.setAttribute
    ('style','position:absolute;height:190px;right:0;top:0;width:620px') ; 
  pro.curcan = null ; 
  pro.curhandle = pro.curdiv.addEventListener("click",function(e)
  { var pos = e.clientX - (window.innerWidth-610) ; 
    if((pos-594)*(pos-594)+(e.clientY-16)*(e.clientY-16)<200)
    { unprofile() ; return ; } 
    drawsel(0,pro.getsel(pos)) ; 
  } ) ;  
}
/* -------------------------------------------------------------------------- */
/*page*/
/* -------------------------------------------------------------------------- */

function drawxcur(pro,sel)
{ if(pro==null||pro.prodiv==null) return null ; 
  var pos = pro.getx(pro.x[sel[1]+pro.sind[sel[0]]]) , i ; 
  var canvas=document.createElement('canvas') , ctx=canvas.getContext("2d") ; 
  canvas.setAttribute('width',620) ; 
  canvas.setAttribute('height',200) ; 
  ctx.beginPath() ; 
  ctx.lineWidth = 1 ; 
  ctx.moveTo(pos,10) ;
  ctx.lineTo(pos,190) ; 
  ctx.stroke() ; 

  // the circle of the 'x' 
  ctx.beginPath() ; 
  ctx.strokeStyle = '#555' ; 
  ctx.fillStyle = 'white' ; 
  ctx.lineWidth = 3 ; 
  ctx.arc(604,16,14.1,0,2*Math.PI,false) ;
  ctx.stroke() ; 
  ctx.fill() ; 

  for(i=6;i<=26;i+=20) // the two bars of the 'x'
  { ctx.beginPath() ; 
    ctx.moveTo(594,i) ; 
    ctx.lineTo(614,32-i) ; 
    ctx.stroke() ; 
  }
  if(pro.curcan!=null) pro.curdiv.removeChild(pro.curcan) ; 
  pro.curdiv.appendChild(pro.curcan=canvas) ;
}
/* -------------------------------------------------------------------------- */
/*page*/
/* -------------------------------- actionname ------------------------------ */

function actionname(x)
{ var i,s ; 
  if(x[0]=='bin') return 'delete segment' ; 
  if(x[0]=='snip') return 'split segment' ; 
  if(x[0]=='editlabel') 
  { if(x[4]=='') return 'delete label' ; 
    else if(x[3]=='') return 'label waypoint' ;
    else return 'edit label' ; 
  }
  if(x[0]=='edittitle') return 'edit title' ; 
  if(x[0]=='editdescription') return 'edit description' ; 
  if(x[0]=='wpdel') return 'delete waypoint' ; 
  if(x[0]=='move') 
  { if(x[5]) return 'insert waypoint' ; else return 'drag waypoint' ; }
  if(x[0]=='recal') return 'recalibrate altitudes' ; 
  if(x[0]=='setalt') return 'set waypoint altitude' ; 
  if(x[0]=='resign') return 'change label symbol' ; 
  if(x[0]=='combine') return 'combine '+x[1]+' segments' ; 
  if(x[0]=='revseg') return 'reverse segment' ; 
  if(x[0]=='optimise') return 'optimisation' ; 
  if(x[0]=='deltimes') return 'delete times' ; 
  if(x[0]=='editphoto') 
  { if(x[5]==null) return 'delete photo' ; 
    else if(x[4]==null) return 'add photo' ;
    else return 'change photo' ; 
  }
  if(x[0]=='extra') return 'interpolate extra points' ; 
  if(x[0]=='stars'&&x[2]==null)
  { s = 'clear ' ; for(i=0;i<x[1];i++) s += '\u2605' ; return s ; } 
  if(x[0]=='stars')
  { s = 'set ' ; for(i=0;i<x[2];i++) s += '\u2605' ; return s ; } 
  alert('Unrecognised action: '+x[0]) ;
}
/* -------------------------------------------------------------------------- */
/*page*/
/* --------------------------------- getalts -------------------------------- */

var elevator=null,reqlist=[] ; 

function getalts(thresh)
{ var s0,s1,start,end,n,npts,flag,i,lox,ind,point ; 
  if(thresh==0) { infowindow.close() ; thresh = 1 ; } 
  if(reqlist.length>0) return ;
  if(elevator==null) elevator = new google.maps.ElevationService ;

  for(reqlist=[],flag=npts=s0=0;s0<segments.length;s0++)
    for(s1=0;s1<segments[s0].data.length;s1++) if(segments[s0].data[s1].h==null)
  { if(s1>0) start = s1-0 ; else start = s1 ; 
    for(;s1<segments[s0].data.length&&segments[s0].data[s1].h==null;s1++) ;
    if(s1==segments[s0].data.length) end = s1 ; else end = s1+1 ; 
    n = end - start ; 
    if(npts+n<=500) { npts += n ; reqlist.push([s0,start,end]) ; }
    else if(reqlist.length>0) { flag = 1 ; break ; }
    else
    { for(point=new Array(501),i=0;i<=500;i++)
        point[i] = segments[s0].data[start+Math.floor((i*(n-1))/500)] ;
      reqlist.push([s0,0,501]) ;
      flag = 2 ; 
      break ; 
    }
  }
  if(flag==0&&npts<thresh) { reqlist = [] ; return ; }

  if(flag!=2) for(point=new Array(npts),i=ind=0;ind<reqlist.length;ind++) 
  { s0 = reqlist[ind][0] ;
    start = reqlist[ind][1] ;
    end = reqlist[ind][2] ;
    for(s1=start;s1<end;s1++,i++) point[i] = segments[s0].data[s1] ;
  }
  for(lox=new Array(point.length),i=0;i<point.length;i++) 
    lox[i] = point[i].pos ;

  elevator.getElevationForLocations( {locations:lox} , function (results,status)
  { // assume that the results come in sequence, ie. correspond to xpending[0]
    var d0,dn,k ;
    if(status===google.maps.ElevationStatus.OVER_QUERY_LIMIT)
      alert('No calibration data available: over Google query limit') ;  
    else if(status===google.maps.ElevationStatus.INVALID_REQUEST)
      alert('Invalid calibration request') ;  
    else if(status===google.maps.ElevationStatus.REQUEST_DENIED)
      alert('Calibration request denied') ;  
    else if(status===google.maps.ElevationStatus.UNKNOWN_ERROR)
      alert('Unknown error reported for calibration request:'+status) ;  
    else if(status!==google.maps.ElevationStatus.OK)
      alert('Calibration error') ;  
    if(status!==google.maps.ElevationStatus.OK) throw '' ;

    if(results.length!=lox.length) flag = 1 ; 
    else for(flag=i=0;i<lox.length&&flag==0;i++) 
      if(dist(lox[i],results[i].location)>5) flag= 1 ; 
    if(flag) 
    { alert("elevation response does not correspond to request") ; return ; }

    for(k=ind=0;ind<reqlist.length;ind++,k+=n) 
    { n = reqlist[ind][2] - reqlist[ind][1] ; 
      if(point[k].h!=null&&point[k+n-1].h!=null)
      { d0 = point[k].h - results[k].elevation ;
        dn = point[k+n-1].h - results[k+n-1].elevation ;
        for(i=1;i<n-1;i++) 
          point[k+i].h = results[k+i].elevation + (i*dn+((n-1)-i)*d0) / (n-1) ;
      }
      else
      { if(point[k].h!=null) d0 = point[k].h - results[k].elevation ; 
        else if(point[k+n-1].h!=null) 
          d0 = point[k+n-1].h - results[k+n-1].elevation ; 
        else d0 = 0 ; 
        for(i=0;i<n;i++) point[k+i].h = results[k+i].elevation + d0  ;
      }
    }
    reqlist = [] ; 
    getalts(thresh) ; 
  } ) ;
}

Archived from routemaster.html

// www.masterlyinactivity.com/software/routemaster.html
//
/* The MIT License

   Copyright (c) 2016, by Colin Champion <colin.champion@masterlyinactivity.com>

   Permission is hereby granted, free of charge, to any person obtaining
   a copy of this software and associated documentation files (the
   "Software"), to deal in the Software without restriction, including
   without limitation the rights to use, copy, modify, merge, publish,
   distribute, sublicense, and/or sell copies of the Software, and to
   permit persons to whom the Software is furnished to do so, subject to
   the following conditions:

   The above copyright notice and this permission notice shall be
   included in all copies or substantial portions of the Software.

   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
   MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
   NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
   BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
   ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
   CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
   SOFTWARE.
*/
var icons = 
{ // coursepoint icons
  flagsign:
  { path: "M 0.5 20.5  L 0.5 0.5  12.5 6  0.5 11.5  ",
    fillColor: '#FCDFFF',
    fillOpacity: 0.7,
    strokeColor: 'purple',
    strokeWeight: 1.5,
    anchor: new google.maps.Point(0.5,20.5),
  } ,
  turnleft:
  { path: "M 18.5 20.5  L 16.5 11.5  A 2 2 0 0 0 14.5 9.5  "+
          "L 11.5 10  11.5 13.5  "+
          "6.5 7.5  11.5 1.5  11.5 5  16.5 5.5  A 3.5 3.5 0 0 1 20 9   z",
    fillColor: '#FCDFFF',
    fillOpacity: 0.7,
    strokeColor: 'purple',
    strokeWeight: 1.5,
    anchor: new google.maps.Point(18.5,20.5),
  } ,
  straighton:
  { path: "M 7.5 20.5  L 4.5 6.5  0.5 6.5  7.5 0.5  14.5 6.5  10 6.5  z",
    fillColor: '#FCDFFF',
    fillOpacity: 0.7,
    strokeColor: 'purple',
    strokeWeight: 1.5,
    anchor: new google.maps.Point(7.5,20.5),
  } ,
  turnright:
  { path: "M 3.5 20.5  L 5.5 11.5  A 2 2 0 0 1 7.5 9.5  L 10.5 10  10.5 13.5  "+
          "15.5 7.5  10.5 1.5  10.5 5  5.5 5.5  A 3.5 3.5 0 0 0 2 9   z",
    fillColor: '#FCDFFF',
    fillOpacity: 0.7,
    strokeColor: 'purple',
    strokeWeight: 1.5,
    anchor: new google.maps.Point(3.5,20.5),
  } ,
  shriek:
  { path: "M 8.5 21.5 A 2.5 2.5 0 0 1 8.5 16.5  A 2.5 2.5 0 1 1 8.5 21.5  "+
          "M 8.5 14.5   4.5 5.5  A 4.5 4.5 0 1 1 12.5 5.5 L 8.5 14.5" ,
    fillColor: '#FCDFFF',
    fillOpacity: 0.7,
    strokeColor: 'purple',
    strokeWeight: 1.5,
    anchor: new google.maps.Point(8.5,21.5),
  } ,
  fork:
  { path: "M 0.5 0.5  L 0.5 5.5  2.5 5.5  2.5 0.5  2.5 5.5  4.5 5.5  4.5 0.5" +
          "  4.5 5.5  6.5 5.5   6.5 0.5   6.5 7.5  " +
          "A 2.5 2.5 0 0 1 4.25 9.95   L 5 19.5  "+
          "A 1.5 1.5 0 0 1  2 19.5   L 2.75 9.95 " + 
          "A 2.5 2.5 0 0 1 0.5 7.5   z",
    fillColor: '#FCDFFF',
    fillOpacity: 0.7,
    strokeColor: 'purple',
    strokeWeight: 1.5,
    anchor: new google.maps.Point(3.5,22),
  } ,
  // icon for arrow representing current waypoint
  arrow:
  { path: "M 6 9  0 15  6 0  12 15 z",
    fillColor: 'black',
    fillOpacity: 1,
    strokeColor: 'black',
    strokeWeight: 0,
    anchor: new google.maps.Point(6,6),
    rotation: 0,
    clickable: false 
  } ,
  // icon for concentric circles representing draggable waypoint
  concircle:
  { path: "M 6 0  A 6 6 0 1 0 6 12  A 6 6 0 1 0 6 0 M 6 3  " +
          "A 3 3 0 1 0 6  9   A 3 3 0 1 0 6  3",
    fillColor: 'black',
    fillOpacity: 0,
    strokeColor: 'black',
    strokeWeight: 1,
    strokeOpacity: 1,
    anchor: new google.maps.Point(6,6),
    clickable: false 
  } ,
  // camera icon
  camera:
  { path: "M 0.5 4   A 1.5 1.5 0 0 1 2 2.5   L  5.5 2.5   7 0.5  11 0.5   " + 
          "12.5 2.5   14 2.5   A 1.5 1.5 0 0 1  16 3   L 20 7   16 11 " +
          "A 1.5 1.5 0 0 1 15 11.5   L 2 11.5   A 1.5 1.5 0 0 1 0.5 10  z " + 
          "M 9 4  A 3 3 0 0 1 9 10   A 3 3 0 0 1 9 4 " ,  
    fillColor: '#FCDFFF',
    fillOpacity: 0.7,
    strokeColor: 'purple',
    strokeWeight: 1.5,
    anchor: new google.maps.Point(21,7),
    clickable: false 
  } 
} ;
function xmlfloat(x) { return parseFloat(x.childNodes[0].textContent) ; }

function isvaliddate(d) 
{ if(Object.prototype.toString.call(d)!=="[object Date]") return false ;
  else return !isNaN(d.getTime()) ;
}
function isvalidnum(x) { return !isNaN(parseFloat(x)) && isFinite(x) ; }

/* ------------------------------- data structure --------------------------- */

// I found the following logic quite hard to get right. A (non-null) label
// satisfies the following constraints:
// o. the marker is non-null
// o. the map may be null, and if it is null the title may also be null and the
//    icon may be arbitrary
// o. if the type is null, the map is null
// o. the map is null if and only if the clickhandler is inactive
// the same constraints apply (mutatis mutandis) to the photo, so it follows 
// that the label may have a null map and the photo non-null (and vice versa)
//    we therefore conclude that a label must be in one of 3 states:
// o. type null, map null, handlers inactive, but marker non-null
// o. type non-null, map null, handlers inactive, marker non-null
// o. type non-null, map non-null, handlers active, marker non-null
// the state in which type is non-null and map is null is applied to all 
// labels in a segment being deleted (we preserve the information in the 
// action list but don't want the label to be displayed)

function datatype(pos,h,t)
{ this.pos = pos ; 
  this.h = h ; 
  this.marker = this.photomarker = this.type = this.t = null ;
  if(t!=undefined&&t!=null&&isvaliddate(t)&&t.getTime()>365*24*3600000) 
    this.t = t ; 
  this.photo = [] ;
  this.caption = '' ;  
  this.clickhandler = this.righthandler = this.photohandler = null ; 
}
// member functions
datatype.prototype.geticon = function()
{ if(this.type=='Left')  return icons.turnleft ; 
  else if(this.type=='Straight') return icons.straighton ; 
  else if(this.type=='Right') return icons.turnright ; 
  else if(this.type=='Danger') return icons.shriek ; 
  else if(this.type=='Food') return icons.fork ; 
  else return icons.flagsign ; 
} ;
datatype.prototype.setlabelmap = function(m) 
{ if(m==null||this.type==null) m = null ; else m = map ; 
  if(m==null&&this.marker==null) return ;
  this.marker.setMap(m) ; 
  if(m==null&&this.clickhandler!=null)
  { google.maps.event.removeListener(this.clickhandler) ;
    google.maps.event.removeListener(this.righthandler) ;
    this.clickhandler = this.righthandler = null ; 
  }
  if(m!=null&&this.clickhandler==null)
  { this.clickhandler = this.marker.addListener('click',selpoint) ;
    this.righthandler = this.marker.addListener('rightclick',labelcycle) ;
  }
} ;
datatype.prototype.setphotomap = function(m) 
{ if(m==null||this.photo.length==0) m = null ; else m = map ; 
  if(m==null&&this.photomarker==null) return ;
  this.photomarker.setMap(m) ;
  if(m==null&&this.photohandler!=null) 
  { google.maps.event.removeListener(this.photohandler) ;
    this.photohandler = null ; 
  }
  if(m!=null&&this.photohandler==null) 
    this.photohandler = this.photomarker.addListener('click',selpoint) ;
} ;
datatype.prototype.setlabel = function(t,c) 
{ this.type = t ; 
  this.caption = c ; 
  if(t==null) { if(this.marker!=null) this.setlabelmap(null) ; return ; } 
  if(this.marker==null) this.marker = new google.maps.Marker
      ({ position:this.pos,map:map,icon:this.geticon(),title:c,zIndex:1 }) ;
  else { this.marker.setIcon(this.geticon()) ; this.marker.setTitle(c) ; }
  this.setlabelmap(map) ; 
} ;
datatype.prototype.setphoto = function(ind,p) 
{ var i ;
  if(p==null)
  { for(i=ind;i<this.photo.length-1;i++) this.photo[i] = this.photo[i+1] ; 
    this.photo.length -= 1 ; 
    if(this.photo.length==0&&this.photomarker!=null) this.setphotomap(null) ; 
    return ; 
  }
  else { this.photo[ind] = p ; if(ind==0) this.photomarker.setTitle(p) ; }
} ;
datatype.prototype.addphoto = function(p) 
{ this.photo.push(p) ; 
  if(this.photomarker==null) this.photomarker = new google.maps.Marker
      ({ position:this.pos,map:map,icon:icons.camera,title:p,zIndex:1 }) ;
  this.setphotomap(map) ; 
} ;
datatype.prototype.setpos = function(p) 
{ this.pos = p ; 
  if(this.type!=null) this.marker.setPosition(p) ; 
  if(this.photo.length>0) this.photomarker.setPosition(p) ; 
} ;
datatype.prototype.setmap = function(m) 
{ this.setlabelmap(m) ; this.setphotomap(m) ; } ;

datatype.prototype.settype = function(t) 
{ this.type = t ; this.marker.setIcon(this.geticon()) ; } ;

datatype.prototype.labelcycle = function()
{ var oldtype = this.type , type ; 
  if(oldtype=='Generic') type = 'Left' ; 
  else if(oldtype=='Left') type = 'Straight' ; 
  else if(oldtype=='Straight') type = 'Right' ; 
  else if(oldtype=='Right') type = 'Danger' ; 
  else if(oldtype=='Danger') type = 'Food' ; 
  else type = 'Generic' ; 
  this.settype(type) ;
  return [ oldtype , type ] ; 
}
function addlabel(data,pos,type,caption) 
{ var j,ind,mindist ; 
  for(j=0;j<data.length;j++) if(j==0||dist(pos,data[j].pos)<mindist) 
  { mindist = dist(pos,data[j].pos) ; ind = j ; } 
  data[ind].setlabel(type,caption) ;
}
function propstype()
{ this.desc = this.title = this.list = this.inputlen = this.source = null ;
  this.stats = this.tracklink = this.overview = this.stars = null ; 
  this.photo = [] ;
  this.optim = { already: 0, ndel: 0, origlen: 0, parms: null }
}
/* -------------------------------------------------------------------------- */

function readtcx(xmldoc)
{ var nodeno,type,lat,lon,i,j,node,alt,pos,segment,props,title,list,txt ;
  var ind,caption,data,photo,time,valid,anc,xmlnodes,nsegment,propno,names ;
  var track,trackpoint,trackno,course,coursepoint,courseno,courselen,overview ;
  var validalt,fieldnames ; 

  // loop over trackpoints 
  track = xmldoc.getElementsByTagName('Trackpoint') ;
  for(courselen=[],data=[],anc=null,trackno=0;trackno<track.length;trackno++)
  { trackpoint = track[trackno] ;
    if(trackpoint.parentNode.parentNode.nodeName=='Course')
      if(trackpoint.parentNode.parentNode!=anc) 
    { anc = trackpoint.parentNode.parentNode ; 
      courselen.push([trackno,anc]) ; 
    }
    lat = lon = alt = time = null ; 
    photo = [] ;
    for(validalt=valid=1,nodeno=0;nodeno<trackpoint.childNodes.length;nodeno++)
    { node = trackpoint.childNodes[nodeno] ;

      if(node.nodeName=='AltitudeMeters') alt = xmlfloat(node) ; 
      else if(node.nodeName=='Time') // '1970-01-01T03:040:08Z'
        time = new Date(node.childNodes[0].textContent) ; 
      else if(node.nodeName=='Position') for(j=0;j<node.childNodes.length;j++)
      { if(node.childNodes[j].nodeName=='LatitudeDegrees') 
          lat = xmlfloat(node.childNodes[j]) ; 
        else if(node.childNodes[j].nodeName=='LongitudeDegrees') 
          lon = xmlfloat(node.childNodes[j]) ;
      }
      else if(node.nodeName=='Extensions') for(j=0;j<node.childNodes.length;j++)
      { if(node.childNodes[j].nodeName=='Photo') 
          photo = node.childNodes[j].childNodes[0].textContent.split(' ') ;
        else if(node.childNodes[j].nodeName=='ValidTime') valid = 0 ;
        else if(node.childNodes[j].nodeName=='ValidAlt') validalt = 0 ;
      }
    }
    if(lat==null||lon==null) continue ; 
    if(!isvalidnum(alt)) validalt = 0 ; 
    pos = new google.maps.LatLng(lat,lon) ;
    data.push(new datatype(pos,validalt?alt:null,valid?time:null)) ; 
    for(ind=0;ind<photo.length;ind++) data[data.length-1].addphoto(photo[ind]) ;
  }
  if(track.length==0) { alert('no trackpoints') ; throw '' ; }
  if(courselen.length==0) courselen.push([0,null]) ; 
  courselen.push([track.length,null]) ; 

  // loop over coursepoints
  course = xmldoc.getElementsByTagName('CoursePoint') ;
  for(courseno=0;courseno<course.length;courseno++)
  { coursepoint = course[courseno] ;
    caption = type = lat = lon = null ;
    for(nodeno=0;nodeno<coursepoint.childNodes.length;nodeno++)
    { node = coursepoint.childNodes[nodeno] ;
      if(node.nodeName=='Name') caption = node.childNodes[0].textContent ; 
      else if(node.nodeName=='PointType') 
        type = node.childNodes[0].textContent ; 
      else if(node.nodeName=='Position') for(j=0;j<node.childNodes.length;j++)
      { if(node.childNodes[j].nodeName=='LatitudeDegrees') 
          lat = xmlfloat(node.childNodes[j]) ;
        else if(node.childNodes[j].nodeName=='LongitudeDegrees') 
          lon = xmlfloat(node.childNodes[j]) ;
      }
    }
    if(lat==null||lon==null||caption==null||type==null) 
    { alert('Badly formatted course point' ) ; throw '' ; }
    addlabel(data,new google.maps.LatLng(lat,lon),type,caption) ; 
  }

  for(segment=[],i=0;i<courselen.length-1;i++)
    segment.push(data.slice(courselen[i][0],courselen[i+1][0])) ;
  nsegment = segment.length ;

  // props fields
  props = new Array(nsegment) ; 
  for(i=0;i<nsegment;i++) 
  { props[i] = new propstype() ;
    props[i].inputlen = data.length ; 
    if(props[i].optim.origlen==0) props[i].optim.origlen = data.length ;
  }

  // optimised?
  xmlnodes = xmldoc.getElementsByTagName('Optimised') ;
  if(xmlnodes.length)
  { props[0].optim.already = 1 ; 
    props[0].optim.origlen = parseInt(xmlnodes[0].getAttribute('from')) ; 
    props[0].optim.ndel = 
      props[0].optim.origlen - parseInt(xmlnodes[0].getAttribute('to')) ; 
    props[0].optim.parms = 
      { tol: parseFloat(xmlnodes[0].getAttribute('tol')) ,
        maxsep: parseFloat(xmlnodes[0].getAttribute('maxsep')) ,
        wppenalty: parseFloat(xmlnodes[0].getAttribute('wppenalty')) ,
        vweight: parseFloat(xmlnodes[0].getAttribute('vweight')) 
      } ; 
    for(i=1;i<nsegment;i++) props[i].optim = props[0].optim ; 
  }

  // overview fields (different values for each segment) all in one big loop
  names = [ 'Name' ,     'LongTitle' , 'Description' , 'Stats' , 'PhotoList' ,
            'Overview' , 'Index' ,     'TrackLink' , 'Stars' ,       'Photo' ] ;
  fieldnames = [ 'title' ,    'desc' ,     'desc' ,      'stats' , 'list' ,
                 'overview' , 'overview' , 'tracklink' , 'stars' , 'photo' ] ;
  for(title=null,propno=0;propno<names.length;propno++)
  { xmlnodes = xmldoc.getElementsByTagName(names[propno]) ;
    for(i=0;i<xmlnodes.length;i++)
    { node = xmlnodes[i] ;
      if(propno==0) anc = node.parentNode ;
      else anc = node.parentNode.parentNode ;
      if(propno==4) txt = node.getAttribute('src') ;
      else if(propno==5||propno==6||propno==7) txt = node.getAttribute('href') ;
      else txt = node.childNodes[0].textContent ;
      for(j=0;j<nsegment&&anc!=courselen[j][1];j++) ;
      if(j==nsegment)
      { if(propno==0&&title==null)
          if( node.parentNode.nodeName=='Courses' 
           || node.parentNode.nodeName=='Lap' ) title = txt ;  
        continue ; 
      }
      if(names[propno]=='Photo') props[j].photo = txt.match(/\S+/g) ;
      else props[j][fieldnames[propno]] = txt ;
    }
  }

  return { title: title , props: props , segments: segment } ;
}
/* -------------------------------------------------------------------------- */

function readgpx(xmldoc)
{ var xmlcoords,nodeno,type,lat,lon,i,node,alt,pos,caption,data,time ; 
  var props = new propstype() ; 

  // get the route name
  xmlcoords = xmldoc.getElementsByTagName('name') ;
  for(i=0;props.title==null&&i<xmlcoords.length;i++)
    if(xmlcoords[i].parentNode.nodeName!='wpt') 
      props.title = xmlcoords[i].childNodes[0].textContent ;

  // get the route description
  xmlcoords = xmldoc.getElementsByTagName('desc') ;
  if(xmlcoords.length>0) 
    props.desc = xmlcoords[0].childNodes[0].textContent ;

  // loop over the track points to get the coords
  xmlcoords = xmldoc.getElementsByTagName('trkpt') ;
  if(xmlcoords.length==0) xmlcoords = xmldoc.getElementsByTagName('rtept') ;

  for(data=[],i=0;i<xmlcoords.length;i++)
  { lat = parseFloat(xmlcoords[i].getAttribute('lat')) ; 
    lon = parseFloat(xmlcoords[i].getAttribute('lon')) ; 
    pos = new google.maps.LatLng(lat,lon) ;

    for(time=alt=null,nodeno=0;nodeno<xmlcoords[i].childNodes.length;nodeno++)
    { node = xmlcoords[i].childNodes[nodeno] ;
      if(node.nodeName=='ele') alt = parseFloat(node.textContent) ; 
      else if(node.nodeName=='time')
        time = new Date(node.childNodes[0].textContent) ; 
    }
    if(!isvalidnum(alt)) alt = null ; 
    data.push(new datatype(pos,alt,time)); 
  }

  // loop over the course points to get the labels
  xmlcoords = xmldoc.getElementsByTagName('wpt') ;
  for(i=0;i<xmlcoords.length;i++)
  { caption = type = lat = lon = null ;
    lat = parseFloat(xmlcoords[i].getAttribute('lat')) ; 
    lon = parseFloat(xmlcoords[i].getAttribute('lon')) ; 
    for(nodeno=0;nodeno<xmlcoords[i].childNodes.length;nodeno++)
    { node = xmlcoords[i].childNodes[nodeno] ;
      if(node.nodeName=='name') caption = node.childNodes[0].textContent ; 
      else if(node.nodeName=='type') type = node.childNodes[0].textContent ; 
    }
    if(lat==null||lon==null||caption==null) 
    { alert('Badly formatted course point' ) ; throw '' ; }
    if(type==null) type = 'Generic' ; 
    addlabel(data,new google.maps.LatLng(lat,lon),type,caption) ; 
  }

  props.inputlen = data.length ; 
  return { props: [props] , segments: [data] } ;
}
/* -------------------------------------------------------------------------- */
/*page*/
/* -------------------------------------------------------------------------- */

function dist(x,y)
{ return google.maps.geometry.spherical.computeDistanceBetween(x,y) ; }

function angle(x,y)
{ return google.maps.geometry.spherical.computeHeading(x,y)*Math.PI/180 ; }

/* -------------------------------------------------------------------------- */

function optimise(idata,parms)
{ var stk,nnstk,stk2,clen=idata.length,i,j,m,step=new Array(clen-1) ; 
  var opos,oalt,npos,nalt,ndatum,mpos,malt,arccentre,arctol,pathpos ; 
  var legal,theta,omega,x,y,hyp,tdist,maxtheta,mintheta,d,dh,od,odh,odash ; 
  var bearings=new Array(clen),nstk=new Array(clen),pi=Math.PI,tol=parms.tol ;

  stk = [ { data:[idata[0]] , err:0 , pathpos:1 } ] ;
  for(i=0;i<clen-1;i++) step[i] = dist(idata[i].pos,idata[i+1].pos) ; 

  // this is a forwards dynamic program. in stk we have a list of hypotheses
  // each of which advances a different number of points through the data, 
  // sorted increasing on how far they've advanced. at each step we take the 
  // first item from the stack and try extending to each legal successor point.
  //    note that a hypothesis whose pathpos is k is one whose last point is
  // idata[k-1].
  //    note too that I could (& should) preallocate stk and stk2 for efficiency
  while(stk[0].pathpos<clen)
  { pathpos = stk[0].pathpos ;
    opos = idata[pathpos-1].pos ;
    oalt = idata[pathpos-1].h ; 
    // try extending to pathpos+i
    for(arctol=null,nnstk=i=0;i<clen-pathpos;i++)
    { ndatum = idata[pathpos+i] ; 
      npos = ndatum.pos ; 
      nalt = ndatum.h ; 
      if(i==0) hyp = step[pathpos-1] ;
      else if((hyp=dist(opos,npos))>parms.maxsep) break ; 
      omega = angle(opos,npos) ; 
      // find the min and max legal bearing
      if(hyp>tol) 
      { theta = Math.asin(tol/hyp) ; 
        if(arctol==null) { arccentre = omega ; arctol = theta ; } 
        else
        { for(odash=omega-arccentre;odash>pi;odash-=2*pi) ; 
          while(odash<-pi) odash += 2*pi ;
          maxtheta = Math.min(arctol,odash+theta) ; 
          mintheta = Math.max(-arctol,odash-theta) ; 
          if(maxtheta<mintheta) break ; 
          arccentre += (maxtheta+mintheta) /2 ; 
          arctol     = (maxtheta-mintheta) /2 ;
        }
      } 
      /* -------------------------------------------------------------------- */
      /*page*/
      /* -------------------------------------------------------------------- */

      bearings[i] = { hyp:hyp , omega:omega } ; 
      // see whether this breaches the max error on any intermediate point
      for(legal=1,od=odh=tdist=m=0;m<i;m++,od=d,odh=dh)
      { mpos = idata[pathpos+m].pos ;
        malt = idata[pathpos+m].h ; 
        x = bearings[m].hyp ; 
        theta = bearings[m].omega ; 
        d = x * Math.sin(theta-omega) ; 
        dh = 0 ;
        if(d*d<tol*tol&&oalt!=null&&nalt!=null&&malt!=null)  
        { y = hyp - x*Math.cos(theta-omega) ;
          y = Math.sqrt(d*d+y*y) ; 
          dh = parms.vweight * ( malt - (oalt*y+nalt*x)/(x+y) ) ; 
        }
        if(d*d+dh*dh>tol*tol) { legal = 0 ; break ; } 
        tdist += step[pathpos-1+m] * ( d*d+d*od+od*od + dh*dh+odh*dh+odh*odh ) ;
      }
      // if we emerge with 'legal' non-zero then we may advance to pathpos+i 
      // and tdist is the sum of squared errors
      if(legal) nstk[nnstk++] = 
      ( { data:     stk[0].data.concat([ndatum]) , 
          err:      stk[0].err + pi*tdist/3 + parms.wppenalty , 
          pathpos:  stk[0].pathpos+i+1 
        } ) ; 
      if(ndatum.type!=null||ndatum.photo.length>0) break ; 
    }  // end loop over i 

    // now we have in nstk the possible extensions of stk[0] in increasing
    // order of end point, so we merge with stk[0..stk.length-1]
    for(stk2=[],i=1,j=0;i<stk.length||j<nnstk;)
      if(i==stk.length) stk2.push(nstk[j++]) ; 
      else if(j==nnstk||stk[i].pathpos<nstk[j].pathpos) stk2.push(stk[i++]) ; 
      else if(stk[i].pathpos>nstk[j].pathpos) stk2.push(nstk[j++]) ; 
      else if(stk[i].err<nstk[j].err) { stk2.push(stk[i++]) ; j += 1 ; } 
      else { stk2.push(nstk[j++]) ; i += 1 ; } 
    stk = stk2 ; 
  }
  return stk[0].data ; 
}
/* -------------------------------------------------------------------------- */
/*page*/
/* ------------------------------- writetcx  -------------------------------- */

function writetcx(props,idata) 
{ var i,j,k,xmldoc,course,lap,datum,filename,track,time,routelen,nnull,sum ; 
  var trackpoint,coursepoint,str,flag,clen=idata.length,tlast,di,dk,pointdist ;
  var origlen,ndel,ano,photo,thisuri,maxsep,sep,tdist,ttime,time,otime,x,y ; 
  var distance = new Array(clen) , msecs = new Array(clen) ;
  var valid = new Array(clen) , alt = new Array(clen) ;

  for(tlast=null,nnull=maxsep=tdist=ttime=i=flag=0;i<clen;otime=time,i++) 
  { if((alt[i]=idata[i].h)==null) nnull += 1 ; 
    time = idata[i].t ;
    if(time!=null) { msecs[i] = time = time.getTime() ; valid[i] = 1 ; } 
    if(tlast!=null&&time!=null&&time<tlast) flag = 1 ; // out of order
    if(time!=null) tlast = time ;
    if(i) 
    { sep = dist(idata[i-1].pos,idata[i].pos) ;
      distance[i] = distance[i-1] + sep ; 
      if(sep>maxsep) maxsep = sep ; 
      if(time!=null&&otime!=null) { tdist += sep ; ttime += time - otime ; }
    }
    else distance[i] = 0 ; 
  }
  routelen = distance[clen-1] ;

  if(maxsep>100&&!confirm('Some gaps between waypoints are >100m.\n'+
    'This will cause problems if used for navigation in a Garmin.\n'+
    'You can hit [OK] and I will proceed anyway, or\n'+
    'you can hit [Cancel] and interpolate extra points\n'+
    '(recommended \u2013 go to Route Info under the cogwheel).')) return null ; 

  // decide what to do if some points have no altitudes
  if(nnull==clen) { alert('no points have altitudes') ; return null ; }
  if(nnull>0&&!confirm(nnull+' waypoints have no associated altitudes.\n' +
  'You can hit [OK] and I will interpolate altitudes (not guaranteed),\n'+
  'or you can hit [Cancel] and try again later when the altitudes may be '+
  'available.')) return null ; 

  if(nnull) for(pointdist=new Array(clen),i=0;i<clen;i=j)
  { for(;i<clen&&idata[i].h!=null;i++) ;          // advance to null
    if(i==clen) break ; 
    for(j=i+1;j<clen&&idata[j]==null;j++) ;       // advance to non-null
    if(i==0) { for(y=idata[j].h;i<j;i++) alt[i] = y ; continue ; } 
    if(j==clen) { for(x=idata[i-1].h;i<j;i++) alt[i] = x ; continue ; } 
    for(sum=k=0;k<=j-i;k++) 
      sum = pointdist[k] = sum + dist(idata[i+k-1].pos,idata[i+k].pos) ; 
    for(x=idata[i-1].h,y=idata[j].h,k=0;k<j-i;k++) 
      alt[i+k] = ( x*(sum-pointdist[k]) + y*pointdist[k] ) / sum ; 
  }

  // fill in missing times
  if(tdist==0||flag!=0) for(i=0;i<clen;i++) msecs[i] = distance[i] * 333 ;
  else for(i=0;i<clen;i=k)
  { for(;i<clen&&idata[i].t!=null;i++) ;      // advance to null
    if(i==clen) break ;
    for(k=i+1;k<clen&&idata[k].t==null;k++) ; // advance to non-null
    for(j=i;j<k;j++) valid[i] = 0 ;
    if(i==0) for(time=msecs[k],j=i;j<k;j++)
      msecs[j] = time - (distance[k]-distance[j])*ttime/tdist ;
    else if(k==clen) for(time=msecs[i-1],j=i;j<clen;j++)
      msecs[j] = time + (distance[j]-distance[i-1])*ttime/tdist ;
    else for(j=i,di=distance[i-1],dk=distance[k];j<k;j++) 
      msecs[j] = ( msecs[i-1]*(dk-distance[j]) + msecs[k]*(distance[j]-di) ) 
                        / (dk-di) ;
  }

  str = '<?xml version="1.0" encoding="UTF-8" standalone="no" ?>\n' +
        '<TrainingCenterDatabase xmlns="http://www.garmin.com/xmlschemas/' +
        'TrainingCenterDatabase/v2"\n' +
        '          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"\n' +
        '          xsi:schemaLocation="http://www.garmin.com/' +
        'xmlschemas/TrainingCenterDatabase/v2 ' + 
        'http://www.garmin.com/xmlschemas/TrainingCenterDatabasev2.xsd">\n' +
        '<!-- http://www.masterlyinactivity.com/software/routemaster.html -->' ;
  str += '\n  <Folders><Courses><CourseFolder Name="Courses">\n' ;
  str += '        <CourseNameRef><Id>'+props.title+'</Id></CourseNameRef>\n' ;
  str += '  </CourseFolder></Courses></Folders>\n<Courses><Course>\n' ; 
  str += '  <Name>'+props.title+'</Name>\n  <Lap>\n' + adddist(routelen) ; 
  time = (msecs[clen-1]-msecs[0]) / 1000 ;
  str += '    <TotalTimeSeconds>' + time.toFixed(0) + '</TotalTimeSeconds>\n' ;
  str += addpos('Begin',idata[0].pos) ; 
  str += addpos('End',idata[clen-1].pos) ; 
  str += '    <Intensity>Active</Intensity>\n' + '  </Lap>\n  <Track>\n' ; 

  // loop over trackpoints
  for(i=0;i<idata.length;i++) 
  { str += '  <Trackpoint>\n' + addpos('',idata[i].pos) ; 
    str += adddist(distance[i]) + addalt(idata[i].h==null?alt[i]:idata[i].h) ;
    str += '    <Time>' + new Date(msecs[i]).toISOString() + '</Time>\n' ;
    if(idata[i].photo.length>0)
    { str += '    <Extensions><Photo>' ;
      for(k=0;k<idata[i].photo.length;k++)
      { if(k) str += ' ' ; str += idata[i].photo[k] ; }
      str += '</Photo></Extensions>\n' ; 
    }
    if(valid[i]==0||idata[i].h==null) 
    { str += '    <Extensions>' ; 
      if(valid[i]==0) str += '<ValidTime>False</ValidTime>' ;
      if((valid[i]==0&&idata[i].h==null)) str += '\n                ' ;
      if(idata[i].h==null) str += '<ValidAlt>False</ValidAlt>' ;
      str += '</Extensions>\n' ; 
    }
    str += '    <SensorState>Absent</SensorState>\n  </Trackpoint>\n' ; 
  }
  str += '  </Track>\n\n' ;

  if( props.optim.ndel>0 || props.list!=null || props.stars!=null
   || props.desc!=null   || props.overview != null) 
  { str += '  <Extensions>\n' ;
    if(props.optim.ndel) 
    { str += '    <Optimised from="' + props.optim.origlen +
                            '" to="' + (props.optim.origlen-props.optim.ndel) ;
      if(props.optim.parms!=null) 
        str += '" tol="' + props.optim.parms.tol.toFixed(0) +
                '" maxsep="' + props.optim.parms.maxsep.toFixed(0) +
                '" wppenalty="' + props.optim.parms.wppenalty.toFixed(0) +
                '" vweight="' + props.optim.parms.vweight.toFixed(1) ;
      str += '"/>\n'
    }
    if(props.list!=null) str += '    <PhotoList src="'+props.list+'"/>\n' ; 
    if(props.stars!=null) str += '    <Stars>'+props.stars+'</Stars>\n' ; 
    if(props.desc!=null) 
      str += '    <Description>'+props.desc+'</Description>\n' ; 
    if(props.overview!=null) 
      str += '    <Index href=">' + props.overview + '"/>\n' ; 
    str += '  </Extensions>\n\n' ;
  }
  
  // finally loop over coursepoints
  for(i=0;i<idata.length;i++) if(idata[i].type!=null)
  { datum = idata[i] ;
    str += '  <CoursePoint><Name>'+datum.marker.title+'</Name>\n' ; 
    str += '    <PointType>'+datum.type+'</PointType>\n' ; 
    str += addpos('',idata[i].pos) + addalt(idata[i].h) ;
    time = new Date(msecs[i]) ; 
    str += '    <Time>' + time.toISOString() + '</Time>\n  </CoursePoint>\n' ;
  }
  return str + '</Course></Courses></TrainingCenterDatabase>\n' ; 
}
/* -------------------------------------------------------------------------- */

function addpos(tag,pos)
{ var str = '    <'+tag+'Position>\n      <LatitudeDegrees>' ; 
  str += pos.lat().toFixed(5)+'</LatitudeDegrees>\n      <LongitudeDegrees>' ;
  str += pos.lng().toFixed(5)+'</LongitudeDegrees>\n    </'+tag+'Position>\n' ;
  return str ;
}
function addalt(x)
{ return '    <AltitudeMeters>' + x.toFixed(0) + '</AltitudeMeters>\n' ; }
function adddist(x)
{ return '    <DistanceMeters>' + x.toFixed(0) + '</DistanceMeters>\n' ; }

/* ----------------------------- writeoverview  ----------------------------- */

function writeoverview(segments,title,list) 
{ var i,j,h,oh,maxalt,minalt,routelen,up,down,ndata,photo,segno,idata,title ;
  var uri ; 
  var str = '<?xml version="1.0" encoding="UTF-8" standalone="no" ?>\n' +
        '<!-- http://www.masterlyinactivity.com/software/routemaster.html -->' +
        '\n<!-- **** routemaster index file **** not for navigation ****' +
        ' -->\n<TrainingCenterDatabase><Courses>\n' ;

  if(title!='Untitled Route'&&title!=null) str += '<Name>'+title+'</Name>\n' ;
  str += '\n' ; 

  for(segno=0;segno<segments.length;segno++)
  { idata = segments[segno].data ;
    for(oh=maxalt=minalt=null,routelen=up=down=i=0;i<idata.length;oh=h,i++)
    { if(i) routelen += dist(idata[i-1].pos,idata[i].pos) ;
      if((h=idata[i].h)==null) continue ; 
      if(maxalt==null||h>maxalt) maxalt = h ; 
      if(minalt==null||h<minalt) minalt = h ; 
      if(h!=null&&oh!=null) { if(h>oh) up += h-oh ; else down += oh-h ; } 
    }

    for(photo=[],ndata=new Array(idata.length),i=0;i<idata.length;i++)
    { ndata[i] = new datatype(idata[i].pos,0) ;
      for(j=0;j<idata[i].photo.length;j++) photo.push(idata[i].photo[j]) ; 
    }
    idata = optimise(ndata,{tol:500,maxsep:100000,wppenalty:10000,vweight:0}) ;

    str += '<Course>\n' ;
    if(segments[segno].props.title!=null) 
      str += '  <Name>' + segments[segno].props.title + '</Name>\n' ;
    str += '  <Track>\n' ; 

    // loop over trackpoints
    for(i=0;i<idata.length;i++) 
      str += '    <Trackpoint><Position>\n      <LatitudeDegrees>' +
             idata[i].pos.lat().toFixed(4)+'</LatitudeDegrees>\n      ' +
             '<LongitudeDegrees>' + idata[i].pos.lng().toFixed(4) +
             '</LongitudeDegrees>\n    </Position></Trackpoint>\n' ; 
    str += '  </Track>\n\n  <Extensions>\n' ;

    title = segments[segno].props.stars ;
    if(title!=null) str += '    <Stars>' + title + '</Stars>\n' ; 

    title = segments[segno].props.desc ;
    if(title!=null) str += '    <Description>' + title + '</Description>\n' ; 

    uri = document.URL ;  
    if((title=segments[segno].props.source)!=null) 
    { if(title[1]=='uri') uri = reluri(uri,'?'+title[0]) ; 
      else uri = reluri(uri,'?$FILE$/'+title[0]) ; 
    }
    str += '    <Stats>Distance ' + (routelen/1000).toFixed(1) + 'km;' +
           ' altitude ' + minalt.toFixed(0) + '-' + maxalt.toFixed(0) + 
           'm; \u2191' + up.toFixed(0) + 'm \u2193' + down.toFixed(0) + 
           'm</Stats>\n    <TrackLink href="' + uri + '"/>\n' ; 

    if(list!=null) str += '    <PhotoList src="' + list + '"/>\n' ; 

    if(photo.length)
    { str += '    <Photo>' ;
      for(i=0;i<photo.length;i++) 
      { if(i>0&&i%6==0) str += '\n           ' ; else if(i) str += ' ' ; 
        str += photo[i] ; 
      }
      if(photo.length%6==0) str += '\n           ' ; 
      str += '</Photo>\n' ;
    }
    str += '  </Extensions>\n</Course>\n\n' ;
  }

  return str + '</Courses></TrainingCenterDatabase>\n' ; 
}
/* -------------------------------------------------------------------------- */

function gencolours(n)
{ var colours=new Array(n) ;
  var ind,density,k,a,na,i,j,m,r,g,b ;

  for(ind=0,density=1;ind<n;density*=2)
  { if(density==1) k = 3 ; else k = Math.floor(0.5+0.75*density*(1+density/2)) ;
    a = new Array(k) ;
    if(density==1) { a = [ [0,0] , [0,1] , [1,0] ] ; na = 3 ; }
    else for(na=i=0;i<=density;i++) 
    { if((i&1)==0) for(j=1;i+j<=density;j+=2) a[na++] = [ i , j ] ;
      else for(j=density-i;j>=0;j--) a[na++] = [ i , j ] ;
    }
    if(na!=k) alert('logic error') ; 
    for(k=0;(1<<k)<na;k++) ;
    for(i=0;i<(1<<k)&&ind<n;i++)
    { for(m=j=0;j<k;j++) m |= ((i>>j)&1) << (k-1-j) ;
      if(m>=na) continue ;
      r = ( density - a[m][0] - a[m][1] ) * (255/density) ;
      g = a[m][1] * (180/density) ;
      b = a[m][0] * (300/density) ;
      r = ("00"+Math.floor(0.5+r).toString(16)).substr(-2) ;
      g = ("00"+Math.floor(0.5+g).toString(16)).substr(-2) ;
      b = ("00"+Math.floor(0.5+b>255?255:b).toString(16)).substr(-2) ;
      colours[ind++] = '#' + r + g + b ; 
    }      
  }
  return colours ;
}
/* -------------------------------------------------------------------------- */

Archived from pix.html

// www.masterlyinactivity.com/software/pix.html // document.write( '<style>a:link{color:#66aaaa}' + 'a:visited{color:#cc3388}a:active{color:#404040}</style>' ) ; if((typeof pixlib)=="undefined") document.write ('<scri'+'pt src="http://www.masterlyinactivity.com/pixlib.js"></scri'+'pt>'); var thispage,full=0,nload=0,here,lind,rind,blink,pagetitle,retpage ; var nitem=[],thumbshape=[],maxthumb,imagedir=null ; var llink,rlink,ulink,dlink,prevind,fromnotes,fullname ; /* -------------------------------------------------------------------------- */ // pixresize responds to a window resize as follows: // o. update 'full' if necessary // o. for a table view, if the number of columns can change, change it; // o. for an image view, if the new size permits a larger or smaller image, // enlarge or reduce; // o. or if the window is expanding or contracting but hasn't changed size // enough to change the desired image, anticipatively preload a larger or // smaller one. function pixresize() { if((full=queryfullscreen())==0&&here.full!=0&&fullname!=here.name) location.href = thispage + '?' + here.name + (fromnotes?'+n':'') ; if(here.name!=null) resize() else if(full!=here.full||here.ncol!=(k=getncol())) tabulate() ; } /* ------------------- construct a link to an image page -------------------- */ function piclink(ind) { if(full) return 'javascript:display("' + list[ind].name + '")' ; else return thispage + '?' + list[ind].name + (fromnotes?'+n':'') ; } /* ----- getncol finds the number of table columns which fit the screen ----- */ function getncol() { var maxcol,ncol,k,ind ; // 17 pix for scrollbar, 19 pix for margin, border... maxcol = Math.floor((window.innerWidth-17)/(maxthumb[0]+19)) ; if(maxcol<1) maxcol = 1 ; for(ncol=ind=0;ind<nitem.length;ind++) { k = Math.ceil(nitem[ind]/Math.ceil(nitem[ind]/maxcol)) ; if(k>ncol) ncol = k ; } return ncol ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------- create links line of the table page ----------------- */ function linkp() { var p,a,span,astyle,i,flag=0 ; astyle = 'font-family:arial;font-weight:normal;text-decoration:none;' ; p = document.createElement("p") ; p.setAttribute('style',"text-align:center;font-size:100%;margin:6px;"+ "font-family:arial;color:silver") ; if(retpage!=null) { a = document.createElement('a') ; a.setAttribute('href',list[retpage].retpage+'.html') ; a.setAttribute('style',astyle) ; a.appendChild(document.createTextNode('notes')) ; p.appendChild(a) ; flag = 1 ; } if(links!=null) for(i=0;i<links.length;flag=1,i++) { if(flag) p.appendChild(document.createTextNode(' : ')) ; a = document.createElement('a') ; a.setAttribute('href',links[i].href) ; a.setAttribute('style',astyle) ; a.appendChild(document.createTextNode(links[i].name)) ; p.appendChild(a) ; } if(full==0&&querycanfullscreen()) { if(flag) p.appendChild(document.createTextNode(' : ')) ; a = document.createElement('a') ; a.setAttribute('style',astyle) ; a.setAttribute('href','javascript:{}') ; a.setAttribute('onclick','enterfullscreen()') ; a.appendChild(document.createTextNode('full screen')) ; p.appendChild(a) ; span = document.createElement('span') ; span.setAttribute('style',"color:gray") ; span.appendChild(document.createTextNode(' [f key] ')) ; p.appendChild(span) ; } return p ; } /* -------------------------------------------------------------------------- */ function display(name) { var titlestr,body,title,i,k,fetchitem,s,ind=findimage(list,name) ; body = document.getElementsByTagName("body")[0] ; if(name!=here.name) { title = document.getElementsByTagName("title")[0] ; while(title.childNodes.length>1) title.removeChild(title.firstChild) ; titlestr = document.createTextNode(list[ind].title) if(title.childNodes.length==0) title.appendChild(titlestr) ; else title.replaceChild(titlestr,title.childNodes[0]) ; } here = { name:name , ind:ind , ncol:0 , full:full } ; // navigation links: < for(lind=ind-1; lind>=0&&(list[lind].name==undefined||list[lind].display=='none'); lind--) ; if(lind<0) llink = null ; else llink = piclink(lind) ; // navigation links: > for(rind=ind+1; rind<list.length && (list[rind].name==undefined||list[rind].display=='none'); rind++) ; if(rind==list.length) rlink = null ; else rlink = piclink(rind) ; // navigation links: return if(fromnotes==0) { if(full) blink = 'javascript:tabulate()' ; else blink = thispage ; s = "table" ; } else { blink = retlink(list,ind) ; if(blink==null) alert('no return page for '+list[ind].name) ; s = "notes" ; } // decide which image if any to prefetch if(prevind!=null&&prevind>ind&&lind>=0) k = lind ; else if((prevind==null||prevind<=ind)&&rind<list.length) k = rind ; else k = null ; if(k==null) fetchitem = null ; else fetchitem = list[k] ; gendisplay(body,list[ind],sizes,llink,blink,rlink,s,fetchitem) ; } /* ------------------------ navigate using the arrow keys ------------------- */ function navigate(e) { if(e.keyCode==37&&lind>=0) // left arrow key { e.preventDefault() ; if(full==0) location.href= llink ; else { prevind = lind+1 ; display(list[lind].name) ; } } else if(e.keyCode==39&&rind<list.length) // right arrow key { e.preventDefault() ; if(full==0) location.href= rlink ; else { prevind = rind-1 ; display(list[rind].name) ; } } else if(e.keyCode==13&&here.ncol==0) // return { e.preventDefault() ; if(full&&fromnotes==0) tabulate() ; else location.href = blink ; } else if(e.keyCode==40&&here.ncol==0) { e.preventDefault() ; reduce() ; } else if(e.keyCode==38&&here.ncol==0) { e.preventDefault() ; enlarge() ; } else if(e.keyCode==70&&full==0) // 'f' (full screen) { e.preventDefault() ; fullname = here.name ; enterfullscreen() ; full = 1 ; } } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------------------------------------------------- */ function tabulate() { var ind,ncol,p,body,table,tr,td,a,img,trflag,k,s,opts,thind=thumbind(sizes) ; var prefetch,nimg,padflag,k,num ; gendisplay() ; rind = list.length ; lind = -1 ; body = document.getElementsByTagName("body")[0] ; while(body.firstChild) body.removeChild(body.firstChild) ; ncol = getncol() ; here = { name:null , ind:null , ncol:ncol , full:full } ; // prefetch will be performed when nimg thumbs have been loaded for(nimg=ind=0;ind<list.length;ind++) if(list[ind].name!=undefined&&list[ind].display!='none') nimg += 1 ; p = document.createElement("p") ; p.setAttribute('style',"text-align:center;font-size:140%;margin:2px 0 6px;"+ "font-family:arial;color:silver;") ; p.appendChild(document.createTextNode(pagetitle)) ; body.appendChild(p) ; body.appendChild(linkp()) ; table = document.createElement('table') ; table.setAttribute('cellspacing','0') ; table.setAttribute('cellpadding','0') ; table.setAttribute('align','center') ; for(s='',ind=0;s==''&&ind<list.length;ind++) if(list[ind].name==undefined) s = "border-bottom:1px solid #444;padding-bottom:4px;" ; table.setAttribute('style',s+'margin:0px auto') ; for(prefetch=null,nload=trflag=colno=ind=0;ind<list.length;ind++) if(list[ind].display!='none') { if(0==colno%ncol||list[ind].name==undefined) { if(trflag>0) table.appendChild(tr) ; tr = document.createElement('tr') ; trflag = 0 ; } td = document.createElement('td') ; if(list[ind].name==undefined) { td.setAttribute("style","border-top:1px solid #444") ; td.setAttribute('align','left') ; td.setAttribute('colspan',ncol) ; p = document.createElement("p") ; p.setAttribute("style","font-size:110%;padding-top:6px;"+ "font-family:arial;color:silver") ; if(list[ind].gps==undefined) p.appendChild(document.createTextNode(list[ind].title)) ; else { p.appendChild(document.createTextNode(list[ind].title+' : ')) ; a = document.createElement('a') ; a.setAttribute('href',list[ind].gps) ; a.setAttribute('style','font-size:90%;text-decoration:none') ; a.appendChild(document.createTextNode('[GPS track]')) ; p.appendChild(a) ; } td.appendChild(p) ; tr.appendChild(td) ; table.appendChild(tr) ; tr = document.createElement('tr') ; colno = trflag = 0 ; continue ; } lind = ind ; llink = piclink(lind) ; if(rind==list.length) { rind = ind ; rlink = piclink(rind) ; } // extra padding at the bottom before a title row if(0==colno%ncol) { padflag = 4 ; for(num=0,k=ind;k<list.length&&num<ncol&&list[k].name!=undefined;k++) if(list[k].display!='none') num += 1 ; if(k<list.length&&num<=ncol) { padflag = 8 ; if(ncol>5) padflag += 2*(ncol-5) ; } } td.setAttribute('align','center') ; if(list[ind].display=='|'&&colno%ncol>0) td.setAttribute('style','border-left:1px solid #444') ; a = document.createElement('a') ; a.setAttribute('href',piclink(ind)) ; a.setAttribute("class","box") ; a.setAttribute("title",list[ind].title) ; img = genimage(list[ind],sizes,thind,function() { nload += 1 ; if(nload==nimg&&prefetch!=null) genimage(list[prefetch],sizes) ; } ) ; img.setAttribute('border',1) ; // vertical bar: margin is t-r-b-l or t-lr-b if(colno%ncol>0&&list[ind].display!='|') img.setAttribute('style',"padding:4px;margin:4px 4px "+padflag+"px 5px") ; else img.setAttribute('style',"padding:4px;margin:4px 4px "+padflag+"px") ; a.appendChild(img) ; td.appendChild(a) ; tr.appendChild(td) ; trflag = 1 ; if(prefetch==null) prefetch = ind ; colno += 1 ; } table.appendChild(tr) ; body.appendChild(table) ; body.appendChild(linkp()) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------------------------------------------------- */ function pix(h) { var ind,query,opts="",k,r,prev,fullpage=location.href,body ; thispage = fullpage ; pagetitle = document.getElementsByTagName("title")[0].textContent ; body = document.getElementsByTagName("body")[0] ; body.setAttribute('style','background:black;margin:0') ; if((typeof links)=='undefined') links = null ; if(h!=undefined&&h!=null&&links==null) links = [ { name: 'home' , href: h } ] ; prevind = null ; here = { name:null , ind:null , ncol:0 , full:0 } ; if((ind=thispage.lastIndexOf('/'))>=0) thispage = thispage.substring(ind+1) ; // set up the array nitem containing the number of items in each block for(nitem=[],ind=-1;ind<list.length;ind=k) { for(k=ind+1;k<list.length&&list[k].name!=undefined;k++) ; if(k>ind+1) nitem.push(k-(ind+1)) ; } maxthumb = setthumbshape(list,sizes,thumbshape,imagedir) ; if((ind=thispage.indexOf('?'))>=0) { query = thispage.substring(ind+1) ; // 'garden+n' thispage = thispage.substring(0,ind) ; fullpage = fullpage.substring(0,fullpage.length-1-query.length) ; if((ind=query.indexOf('+'))>=0) { opts = query.substring(ind+1) ; query = query.substring(0,ind) ; } for(ind=0;ind<list.length&&(list[ind].name!=query);ind++) ; } else { query = null ; ind = -1 ; } for(k=0; k<list.length&&(list[k].name==undefined||list[k].retpage==undefined); k++) ; if(k<list.length) retpage = k ; else retpage = null ; if(ind>=0&&ind<list.length) // return to table if there is no retpage { // all the following code is finding whether we're stepping backwards if((k=document.referrer.lastIndexOf('/'))>=0) { prev = document.referrer.substring(k+1) ; k = prev.indexOf('?') ; } if(k>=0) { prev = prev.substring(k+1) ; k = prev.indexOf('+') ; if(k>=0) prev = prev.substring(0,k) ; for(k=0;k<list.length&&list[k].name!=prev;k++) ; if(k<list.length) prevind = k ; } else if(fullpage==document.referrer) { for(k=ind+1;k<list.length&&list[ind].name==undefined;k++) ; if(k==list.length) prevind = list.length ; } } window.onresize = pixresize ; document.onkeydown = navigate ; document.addEventListener('touchstart',startswipe,false) ; document.addEventListener('touchmove',midswipe,false) ; document.addEventListener('touchend',endswipe,false) ; if(ind<0||ind>=list.length) { fromnotes = 0 ; tabulate() ; } else { fromnotes = (opts.charAt(0)=='n'&&retpage!=null) ; display(query) ; } } /* -------------------------------------------------------------------------- */
/*page*/ /* ---------------------------------- swipes -------------------------------- */ function simulate(btn) { navigate({keyCode:btn,preventDefault:function(){}}) ; } var xloc=null,yloc=null,xstart=null,ystart=null,fingersep=null,startsep=null ; var swipetime=null ; function startswipe(e) { var x0=e.touches[0].clientX,y0=e.touches[0].clientY,x1,y1 ; if(e.touches.length==1) { xstart = x0 ; ystart = y0 ; swipetime = new Date().getTime() ; } else if(e.touches.length==2&&here.ncol==0) { e.preventDefault() ; // don't let iOS take control of pinches x1 = e.touches[1].clientX ; y1 = e.touches[1].clientY ; startsep = Math.sqrt((x1-x0)*(x1-x0)+(y1-y0)*(y1-y0)) ; } } function midswipe(e) { var x0=e.touches[0].clientX,y0=e.touches[0].clientY,x1,y1 ; if(e.touches.length==1) { xloc = x0 ; yloc = y0 ; fingersep = startsep = null ; } else if(e.touches.length==2&&here.ncol==0) { e.preventDefault() ; x1 = e.touches[1].clientX ; y1 = e.touches[1].clientY ; fingersep = Math.sqrt((x1-x0)*(x1-x0)+(y1-y0)*(y1-y0)) ; xloc = yloc = xstart = ystart = swipetime = null ; } } function endswipe(e) { var v ; if(fingersep!=null&&startsep!=null&&here.ncol==0) { e.preventDefault() ; if(fingersep>startsep+50) simulate(38) ; else if(startsep>fingersep+50) simulate(40) ; } else if(xloc!=null&&yloc!=null&&xstart!=null&&ystart!=null&&swipetime!=null) { swipetime = new Date().getTime() - swipetime ; v = Math.sqrt((xloc-xstart)*(xloc-xstart)+(yloc-ystart)*(yloc-ystart)) ; if(v>0.65*swipetime) { e.preventDefault() ; if(Math.abs(xloc-xstart)>100&&Math.abs(yloc-ystart)<100) { if(xloc>xstart) simulate(37) ; else simulate(39) ; } else if(yloc>ystart+100&&Math.abs(xloc-xstart)<100) simulate(13) ; } } xloc = yloc = xstart = ystart = fingersep = startsep = swipetime = null ; }

Archived from pix.html

// www.masterlyinactivity.com/software/pix.html // var pixlib = 1 ; /* -------------------------------------------------------------------------- */ function enterfullscreen() { if(document.documentElement.requestFullscreen) document.documentElement.requestFullscreen() ; else if(document.documentElement.mozRequestFullScreen) document.documentElement.mozRequestFullScreen() ; else if(document.documentElement.webkitRequestFullscreen) document.documentElement.webkitRequestFullscreen() ; else if(document.documentElement.msRequestFullscreen) document.documentElement.msRequestFullscreen() ; } function queryfullscreen() { if(document.fullScreen||document.mozFullScreen||document.webkitIsFullScreen) return 1 ; else return 0 ; } function querycanfullscreen() { if ( document.documentElement.requestFullscreen || document.documentElement.mozRequestFullScreen || document.documentElement.webkitRequestFullscreen || document.documentElement.msRequestFullscreen ) return 1 ; else return 0 ; } /* -------------------------------------------------------------------------- */ function thumbind(sizes) { var ind ; for(ind=0;ind<sizes.length&&sizes[ind].type!='thumb';ind++) ; if(ind==sizes.length) { alert('no sizes entry of type "thumb"') ; throw '' ; } return ind ; } /* -------------------------------------------------------------------------- */ function retlink(list,ind) { var i,k,blink ; for(i=ind;i>=0&&list[i].retpage==undefined;i--) ; if(i<0) return null ; ; blink = list[i].retpage+'.html' ; for(k=ind;k>=i&&list[k].retid==undefined;k--) ; if(k>=i) blink += '#' + list[k].retid ; return blink ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ---- fill in missing thumbs and raws, return maximum thumb dimensions ---- */ function setthumbshape(list,sizes,thumbshape,imagedir,hithumb) { var ind,r0,r1,maxthumb,k,umax,doraw ; if(typeof thumbshape=='undefined'||thumbshape==undefined) thumbshape = null ; if(typeof imagedir=='undefined'||imagedir==undefined) imagedir = null ; if(typeof hithumb=='undefined'||hithumb==undefined) hithumb = null ; ind = thumbind(sizes) ; if(thumbshape==null||thumbshape.length!=2) { if(sizes[ind].scale>0) r0 = sizes[ind].scale / sizes[0].scale ; else { alert('thumbs have size '+sizes[ind].scale) ; throw '' ; } } for(doraw=r1=ind=0;ind<sizes.length&&sizes[ind].type!='raw';ind++) ; if(ind<sizes.length) { r1 = sizes[ind].scale / sizes[0].scale ; doraw = 1 ; } for(maxthumb=[0,0],ind=0;ind<list.length;ind++) if(list[ind].name!=undefined) { if(list[ind].thumbshape==undefined) { if(thumbshape!=null&&thumbshape.length==2) list[ind].thumbshape = thumbshape ; else for(list[ind].thumbshape=[0,0],k=0;k<2;k++) list[ind].thumbshape[k] = Math.floor(0.5+list[ind].shape[k]*r0) ; } else if(list[ind].hithumb==undefined) list[ind].hithumb = hithumb ; for(k=0;k<2;k++) if(list[ind].thumbshape[k]>maxthumb[k]) maxthumb[k] = list[ind].thumbshape[k] ; if(doraw&&list[ind].rawshape==undefined) for(list[ind].rawshape=[0,0],k=0;k<2;k++) list[ind].rawshape[k] = Math.floor(0.5+list[ind].shape[k]*r1) ; if(imagedir==null) list[ind].filename = list[ind].name ; else list[ind].filename = imagedir + '/' + list[ind].name ; } // fill in the fontsize field in sizes for(R=k=ind=0;ind<sizes.length;ind++) if(sizes[ind].type==undefined) { R += Math.log(sizes[ind].scale) ; k += 1 ; } R /= k ; for(ind=0;ind<sizes.length;ind++) if(sizes[ind].type==undefined&&sizes[ind].fontsize==undefined) sizes[ind].fontsize = 16 * Math.exp((Math.log(sizes[ind].scale)-R)/3) ; for(umax=null,ind=0;ind<sizes.length;ind++) if(sizes[ind].type==undefined) if(umax==null||sizes[ind].fontsize>umax) umax = sizes[ind].fontsize ; for(ind=0;ind<sizes.length;ind++) if(sizes[ind].type=='raw'&&sizes[ind].fontsize==undefined) sizes[ind].fontsize = umax ; return maxthumb ; } /* ----------------------------- image functions ---------------------------- */ function imgshape(item,sizes,sizeno) { if(sizes[sizeno].type=='thumb') return item.thumbshape ; else if(sizes[sizeno].type=='raw') return item.rawshape ; return [ Math.floor(0.5+item.shape[0]*sizes[sizeno].scale/sizes[0].scale) , Math.floor(0.5+item.shape[1]*sizes[sizeno].scale/sizes[0].scale) ] ; } function imgsize(item,sizes,sizeno) { return imgshape(item,sizes,sizeno) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ----- sparepix finds the margins left if item is displayed at usesize ---- */ function sparepix(item,sizes,sizeno) { var shape=imgsize(item,sizes,sizeno),w=shape[0],h=shape[1],r ; h += Math.floor(0.5+1.25*sizes[sizeno].fontsize) + 2 ; if(item.caption!=undefined) h += Math.floor(0.5+sizes[sizeno].fontsize) + 2 ; r = [ window.innerHeight-50-h , window.innerHeight-h ] ; if(window.innerWidth-w<r[0]) r[0] = window.innerWidth-w ; if(window.innerWidth-w-50<r[1]) r[1] = window.innerWidth-w-50 ; return r ; } /* ---------------------- construct the jpg path name ----------------------- */ function jpg(item,sizes,sizeno) { return item.filename + sizes[sizeno].suffix + '.jpg' ; } /* ------------------- generate the srcset for an image --------------------- */ function srcset(item,sizes,sizeno) { var i,s="",hith,scale=sizes[sizeno].scale ; if(sizes[sizeno].type=='raw') return '' ; if(sizes[sizeno].type=='thumb') { hith = sizes[sizeno].hithumb ; if(hith!=undefined&&hith!=null ) return item.filename + hith.suffix + '.jpg ' + hith.scale + 'x, ' ; if( item.thumbshape[0]*item.shape[1] != item.thumbshape[1]*item.shape[0] ) return '' ; scale *= item.thumbshape[0] / item.shape[0] ; } for(i=0;i<sizes.length;i++) if(sizes[i].type==undefined) if(sizes[i].scale>scale) s += jpg(item,sizes,i) + ' ' + (sizes[i].scale/scale).toFixed(1) + "x, " ; if(s!='') s = s.substring(0,s.length-2) ; return s ; } /* -------------------------------------------------------------------------- */ function preload(item,sizes,sizeno,loadaction) { return genimage(item,sizes,sizeno,loadaction) ; } /* ------- getsize finds the largest image size which fits the screen ------- */ function getsize(item,sizes,loadstatus,thresh) { var i,ibest,ismall,spare ; for(ismall=ibest=null,i=0;i<sizes.length;i++) if(sizes[i].type==undefined&&(loadstatus==undefined||loadstatus[i]>=thresh)) { if(ismall==null||sizes[i].scale<sizes[ismall].scale) ismall = i ; spare = sparepix(item,sizes,i) ; if(spare[0]>=0||spare[1]>=0) if(ibest==null||sizes[i].scale>sizes[ibest].scale) ibest = i ; } if(ibest==null) return ismall ; else return ibest ; } /* -------------------------------------------------------------------------- */
/*page*/ /* --------------------------------- genimage ------------------------------- */ function genimage(item,sizes,sizeno,loadfunc) { var img=document.createElement('img') , shape , s ; if(sizeno==undefined||sizeno==null||sizeno<0) sizeno = getsize(item,sizes) ; shape = imgshape(item,sizes,sizeno) ; img.setAttribute('width',shape[0]) ; img.setAttribute('height',shape[1]) ; if((s=srcset(item,sizes,sizeno))!='') img.setAttribute("srcset",s) ; if(loadfunc!=undefined&&loadfunc!=null) img.onload = loadfunc ; img.setAttribute("src",jpg(item,sizes,sizeno)) ; // console.log(img.src+(s==""?"":' ['+s+']')) ; return img ; } /* ------- setimgpos sets the image position according to window size ------- */ function setimgpos(parms,shape) { var imgw=shape[0],imgh=shape[1],x,y,s,overflowx,overflowy ; var pad=parms.headh + parms.caph ; // set space to the amount of room we've got for the image div var space = [ window.innerWidth , window.innerHeight-pad ] ; if(parms.portrait) space[0] -= 50 ; else space[1] -= 50 ; // set imgw and imgh to the desired dimensions of the image div if(imgh>space[1]) imgw += 20 ; // generous scrollbar if(imgw>space[0]) imgh += 20 ; // set (x,y) to the coords of the main div x = (window.innerWidth-imgw)/2 ; y = (window.innerHeight-imgh)/2 - parms.headh; if(parms.portrait) { if(x<50) x = 50 ; if(y<0) y = 0 ; } else { if(x<0) x = 0 ; if(y<50) y = 50 ; } x = Math.floor(x) ; y = Math.floor(y) ; // now constrain imgw, imgh by the window size if(imgw>window.innerWidth-x) imgw = window.innerWidth - x ; if(imgh>window.innerHeight-y-pad) imgh = window.innerHeight-y-pad ; s = 'position:absolute;left:'+x+'px;width:'+(window.innerWidth-x)+'px;top:' ; s += y+'px;height:'+(pad+imgh)+'px;overflow:hidden' ; parms.maindiv.setAttribute('style',s) ; s = 'font-family:arial;color:silver;position:absolute;' ; s += 'left:0;width:'+(window.innerWidth-x)+'px;top:0;' ; s += 'height:'+parms.headh+'px;overflow:hidden;font-size:'+parms.headf+'px' ; parms.headdiv.setAttribute('style',s) ; if(parms.capdiv!=null) { s = 'font-family:arial;color:silver;position:absolute;' ; s += 'left:0;width:'+(window.innerWidth-x)+'px;bottom:0;' ; s += 'height:'+parms.caph+'px;overflow:hidden;font-size:'+parms.capf+'px' ; parms.capdiv.setAttribute('style',s) ; } if(imgw<shape[0]) overflowx = 'scroll' ; else overflowx = 'hidden' ; if(imgh<shape[1]) overflowy = 'scroll' ; else overflowy = 'hidden' ; s = 'position:absolute;left:0;width:'+imgw+'px;top:'+parms.headh+'px;' ; s += 'height:'+imgh+'px;overflow-x:'+overflowx+';overflow-y:'+overflowy ; parms.imgdiv.setAttribute('style',s) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------- find the list ind with a given name ----------------- */ function findimage(list,name) { var ind ; for(ind=0;ind<list.length;ind++) if(list[ind].name==name) return ind ; return null ; } /* ---- adjust element attributes in accordance with a change of img size --- */ function setimg(parms,item,sizes,sizeno,fetchitem,preloadstatus) { var font,spare,shape,img,s,i,fetcher ; if(sizeno==null) return ; enredcell(parms.enlink,sizes,sizeno,2) ; enredcell(parms.redlink,sizes,sizeno,-1) ; spare = sparepix(item,sizes,sizeno) ; shape = imgsize(item,sizes,sizeno) ; font = sizes[sizeno].fontsize ; parms.headh = 2 + Math.floor(0.5+1.25*font) ; parms.headf = Math.floor(0.5+font) ; if(item.caption==undefined) parms.caph = parms.capf = 0 ; else { parms.caph = 2+Math.floor(0.5+font) ; parms.capf = Math.floor(0.5+0.8*font) ; } parms.portrait = spare[0]<spare[1] ; // create the image for the new size fetcher = null ; if(fetchitem!=undefined&&fetchitem!=null) if(preloadstatus[i=getsize(fetchitem,sizes)]==0) { preloadstatus[i] = 1 ; fetcher = function() { genimage(fetchitem,sizes,i) ; } ; } img = genimage(item,sizes,sizeno,fetcher) ; if(parms.img==null) parms.imgdiv.appendChild(img) ; else parms.imgdiv.replaceChild(img,parms.img) ; parms.img = img ; setimgpos(parms,shape) ; return sizeno ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ------------ rescale finds the next larger/next smaller image ------------ */ function rescale(sizes,sizeno,dir) { var i,ind ; if(dir<0) { for(ind=null,i=0;i<sizes.length;i++) if(sizes[i].type==undefined) if(sizes[i].scale<sizes[sizeno].scale||sizes[sizeno].type=='raw') if(ind==null||sizes[i].scale>sizes[ind].scale) ind = i ; return ind ; } if(sizes[sizeno].type=='raw') return null ; for(ind=null,i=0;i<sizes.length;i++) if(sizes[i].type==undefined||(sizes[i].type=='raw'&&dir==2)) if( sizes[i].scale>sizes[sizeno].scale || sizes[i].type=='raw' ) { if(ind==null) ind = i ; else if(sizes[ind].type=='raw') ind = i ; else if(sizes[i].scale<sizes[ind].scale&&sizes[i].type!='raw') ind = i ; } return ind ; } /* --------------- reposition image in response to a window resize ---------- */ function resizesub(iparms,dparms) { var k2,k0,dir,flag=0,shape,fetch,item=iparms.item,sizes=iparms.sizes ; var sizeno=iparms.sizeno ; k2 = getsize(item,sizes,iparms.loadstatus,2) ; k0 = getsize(item,sizes) ; // there's a better size already loaded if(iparms.holdsize==0&&k2!=sizeno) { setimg(dparms,item,sizes,k2,iparms.fetchitem,iparms.preloadstatus) ; iparms.sizeno = k2 ; return ; } // there's a better size not yet loaded else if(iparms.holdsize==0&&k0!=sizeno&&iparms.loadstatus[k0]==0) flag = 1 ; else if(iparms.holdsize!=0&&k0==sizeno) iparms.holdsize = 0 ; else if(iparms.holdsize==0&&iparms.window!=null) { if( window.innerWidth>=iparms.window[0] && window.innerHeight>=iparms.window[1] ) dir = 1 ; else if( window.innerWidth<=iparms.window[0] && window.innerHeight<=iparms.window[1] ) dir = -1 ; else dir = null ; if(dir!=null&&(k0=rescale(sizes,sizeno,dir))!=null) if(iparms.loadstatus[k0]==0) flag = 1 ; } if(flag) { iparms.window = null ; iparms.loadstatus[k0] = 1 ; genimage(item,sizes,k0,genloadhandler(iparms.loadstatus,k0)) ; } // redisplay if portrait<->landscape makes a fit possible, else redraw shape = imgsize(item,sizes,sizeno) ; spare = sparepix(item,sizes,sizeno) ; if(dparms.portrait!=(spare[0]<spare[1])&&(spare[0]<0)!=(spare[1]<0)) dparms.portrait = 1-dparms.portrait ; setimgpos(dparms,imgsize(item,sizes,sizeno)) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ---------------- create a div containing an image title ------------------ */ function maketextdiv(title) { var div=document.createElement('div') ; div.appendChild(document.createTextNode(title)) ; div.setAttribute('style','font-family:arial;color:silver') ; return div ; } /* --------------------- create/reset enlarge/reduce icon ------------------- */ function enredcell(td,sizes,sizeno,dir) { var a,astyle ; astyle = 'font-family:arial;font-weight:normal;text-decoration:none;' ; while(td.firstChild) td.removeChild(td.firstChild) ; if(rescale(sizes,sizeno,dir)!=null) { a = document.createElement('a') ; a.setAttribute('href',dir>0?'javascript:enlarge()':'javascript:reduce()') ; a.setAttribute("title",dir>0?"enlarge [\u2191 key]":"reduce [\u2193 key]") ; a.setAttribute("style",astyle) ; a.appendChild(document.createTextNode(dir>0?'\u2295':'\u2296')) ; td.appendChild(a) ; } } /* --------------------- create a cell for navigation icon ------------------ */ function navcell(link,dir,string) { var a,td=document.createElement('td'),s='center',astyle ; if(dir=='l') s = 'left' ; else if (dir=='r') s = 'right' ; astyle = 'font-family:arial;font-weight:normal;text-decoration:none;' ; s = "text-align:" + s + (dir==0?'':';font-size:20px;') ; s += 'text-align:center;vertical-align:middle;font-size:16px;' + 'line-height:16px;width:16px;height:16px' ; td.setAttribute("style",s) ; if(dir!=0) td.setAttribute("rowspan",3) ; if(link!=null) { a = document.createElement('a') ; a.setAttribute('href',link) ; if(dir>0) s = "next [\u2192 key]" ; else if(dir==0) s = "back to " + string + " [\u21b5 key]" ; else s = "prev [\u2190 key]" ; a.setAttribute("title",s) ; a.setAttribute("style",astyle) ; if(dir>0) s = '>' ; else if(dir==0) s = '\u21b5' ; else s = '<' ; a.appendChild(document.createTextNode(s)) ; td.appendChild(a) ; } return td ; } /* -------------------------------------------------------------------------- */ var maxthumb,thumbshape,imagedir,hithumb,thind=null,sfx=null ; function thumb(name) { var ind,s ; if(maxthumb==undefined||maxthumb==null) maxthumb = setthumbshape(list,sizes,thumbshape,imagedir,hithumb) ; if(thind==null) thind = thumbind(sizes) ; if(sfx==null) sfx = sizes[thind].suffix ; if(null==(ind=findimage(list,name))) alert('Missing image: '+name) ; s = srcset(list[ind],sizes,thind) ; document.write('<a href="'+pixpage+'?'+name+'+n"><img class=pix src="'+ jpg(list[ind],sizes,thind)+'" width='+list[ind].thumbshape[0]+ ' height='+list[ind].thumbshape[1]+' title="'+list[ind].title+ ((s==''||s==null)?'':('" srcset="'+s)) + '"></a>') ; } /* -------------------------------------------------------------------------- */ var drawparms=null,itemparms=null ; function setsize(k) { if(itemparms==null||(k=rescale(itemparms.sizes,itemparms.sizeno,k))==null) return ; itemparms.sizeno = setimg(drawparms,itemparms.item,itemparms.sizes,k) ; itemparms.holdsize = 1 ; } function genloadhandler(loadstatus,k) { return function() { if(itemparms!=null) { loadstatus[k] = 2 ; resize() ; } } ; } function resize() { if(itemparms!=null) resizesub(itemparms,drawparms) ; } function enlarge() { setsize(2) ; } function reduce() { setsize(-1) ; } /* -------------------------------------------------------------------------- */ function gendisplay(element,item,sizes,llink,blink,rlink,fromstring,fetchitem) { var i,sizeno,captioned,table,tr,td,a,utd,dtd ; if(element==undefined||element==null) { itemparms = drawparms = null ; return ; } sizeno = getsize(item,sizes) ; if(item.caption==undefined) captioned = 0 ; else captioned = 1 ; itemparms = { item: item, sizes: sizes, sizeno: sizeno, holdsize: 0, loadstatus: new Array(sizes.length), fetchitem: fetchitem, preloadstatus: new Array(sizes.length), window: [ window.innerWidth , window.innerHeight ] } ; for(i=0;i<sizes.length;i++) itemparms.loadstatus[i] = itemparms.preloadstatus[i] = 0 ; itemparms.loadstatus[sizeno] = 2 ; // make the navigation table table = document.createElement('table') ; table.setAttribute('cellpadding',0) ; table.setAttribute('cellspacing',0) ; // the '<' link tr = document.createElement('tr') ; tr.appendChild(navcell(llink,-1)) ; // the enlarge link utd = document.createElement('td') ; utd.setAttribute("style",'text-align:center;font-size:16px;' + 'line-height:16px;width:16px;height:16px') ; tr.appendChild(utd) ; table.appendChild(tr) ; // the '>' link tr.appendChild(navcell(rlink,1)) ; table.appendChild(tr) ; // the return link tr = document.createElement('tr') ; tr.appendChild(navcell(blink,0,fromstring)) ; table.appendChild(tr) ; // the reduce link tr = document.createElement('tr') ; dtd = document.createElement('td') ; dtd.setAttribute("style",'text-align:center;font-size:16px;' + 'line-height:16px;width:16px;height:16px') ; tr.appendChild(dtd) ; table.appendChild(tr) ; drawparms = { headh: null, caph: null, headf: null, capf: null, enlink: utd, redlink: dtd, img: null, portrait: null, maindiv: document.createElement('div'), headdiv: maketextdiv(item.title), imgdiv: document.createElement('div'), capdiv: captioned==0?null:maketextdiv(item.caption) } ; setimg(drawparms,item,sizes,sizeno,fetchitem,itemparms.preloadstatus) ; drawparms.imgdiv.appendChild(drawparms.img) ; drawparms.maindiv.appendChild(drawparms.headdiv) ; drawparms.maindiv.appendChild(drawparms.imgdiv) ; if(captioned) drawparms.maindiv.appendChild(drawparms.capdiv) ; while(element.firstChild) element.removeChild(element.firstChild) ; element.appendChild(table) ; element.appendChild(drawparms.maindiv) ; } /* -------------------------------------------------------------------------- */

Archived from routemaster.html

var icons = 
{ // coursepoint icons
  flagsign:
  { path: "M 0.5 20.5  L 0.5 0.5  12.5 6  0.5 11.5  ",
    fillColor: '#FCDFFF',
    fillOpacity: 0.7,
    strokeColor: 'purple',
    strokeWeight: 1.5,
    anchor: new google.maps.Point(0.5,20.5),
  } ,
  turnleft:
  { path: "M 18.5 20.5  L 16.5 11.5  A 2 2 0 0 0 14.5 9.5  "+
          "L 11.5 10  11.5 13.5  "+
          "6.5 7.5  11.5 1.5  11.5 5  16.5 5.5  A 3.5 3.5 0 0 1 20 9   z",
    fillColor: '#FCDFFF',
    fillOpacity: 0.7,
    strokeColor: 'purple',
    strokeWeight: 1.5,
    anchor: new google.maps.Point(18.5,20.5),
  } ,
  straighton:
  { path: "M 7.5 20.5  L 4.5 6.5  0.5 6.5  7.5 0.5  14.5 6.5  10 6.5  z",
    fillColor: '#FCDFFF',
    fillOpacity: 0.7,
    strokeColor: 'purple',
    strokeWeight: 1.5,
    anchor: new google.maps.Point(7.5,20.5),
  } ,
  turnright:
  { path: "M 3.5 20.5  L 5.5 11.5  A 2 2 0 0 1 7.5 9.5  L 10.5 10  10.5 13.5  "+
          "15.5 7.5  10.5 1.5  10.5 5  5.5 5.5  A 3.5 3.5 0 0 0 2 9   z",
    fillColor: '#FCDFFF',
    fillOpacity: 0.7,
    strokeColor: 'purple',
    strokeWeight: 1.5,
    anchor: new google.maps.Point(3.5,20.5),
  } ,
  shriek:
  { path: "M 8.5 21.5 A 2.5 2.5 0 0 1 8.5 16.5  A 2.5 2.5 0 1 1 8.5 21.5  "+
          "M 8.5 14.5   4.5 5.5  A 4.5 4.5 0 1 1 12.5 5.5 L 8.5 14.5" ,
    fillColor: '#FCDFFF',
    fillOpacity: 0.7,
    strokeColor: 'purple',
    strokeWeight: 1.5,
    anchor: new google.maps.Point(8.5,21.5),
  } ,
  fork:
  { path: "M 0.5 0.5  L 0.5 5.5  2.5 5.5  2.5 0.5  2.5 5.5  4.5 5.5  4.5 0.5" +
          "  4.5 5.5  6.5 5.5   6.5 0.5   6.5 7.5  " +
          "A 2.5 2.5 0 0 1 4.25 9.95   L 5 19.5  "+
          "A 1.5 1.5 0 0 1  2 19.5   L 2.75 9.95 " + 
          "A 2.5 2.5 0 0 1 0.5 7.5   z",
    fillColor: '#FCDFFF',
    fillOpacity: 0.7,
    strokeColor: 'purple',
    strokeWeight: 1.5,
    anchor: new google.maps.Point(3.5,22),
  } ,
  // icon for arrow representing current waypoint
  arrow:
  { path: "M 6 9  0 15  6 0  12 15 z",
    fillColor: 'black',
    fillOpacity: 1,
    strokeColor: 'black',
    strokeWeight: 0,
    anchor: new google.maps.Point(6,6),
    rotation: 0,
    clickable: false 
  } ,
  // icon for concentric circles representing draggable waypoint
  concircle:
  { path: "M 6 0  A 6 6 0 1 0 6 12  A 6 6 0 1 0 6 0 M 6 3  " +
          "A 3 3 0 1 0 6  9   A 3 3 0 1 0 6  3",
    fillColor: 'black',
    fillOpacity: 0,
    strokeColor: 'black',
    strokeWeight: 1,
    strokeOpacity: 1,
    anchor: new google.maps.Point(6,6),
    clickable: false 
  } ,
  // camera icon
  camera:
  { path: "M 0.5 4   A 1.5 1.5 0 0 1 2 2.5   L  5.5 2.5   7 0.5  11 0.5   " + 
          "12.5 2.5   14 2.5   A 1.5 1.5 0 0 1  16 3   L 20 7   16 11 " +
          "A 1.5 1.5 0 0 1 15 11.5   L 2 11.5   A 1.5 1.5 0 0 1 0.5 10  z " + 
          "M 9 4  A 3 3 0 0 1 9 10   A 3 3 0 0 1 9 4 " ,  
    fillColor: '#FCDFFF',
    fillOpacity: 0.7,
    strokeColor: 'purple',
    strokeWeight: 1.5,
    anchor: new google.maps.Point(21,7),
    clickable: false 
  } 
} ;
function xmlfloat(x) { return parseFloat(x.childNodes[0].textContent) ; }

function isvaliddate(d) 
{ if(Object.prototype.toString.call(d)!=="[object Date]") return false ;
  else return !isNaN(d.getTime()) ;
}
function isvalidnum(x) { return !isNaN(parseFloat(x)) && isFinite(x) ; }

/* ------------------------------- data structure --------------------------- */

// I found the following logic quite hard to get right. A (non-null) label
// satisfies the following constraints:
// o. the marker is non-null
// o. the map may be null, and if it is null the title may also be null and the
//    icon may be arbitrary
// o. if the type is null, the map is null
// o. the map is null if and only if the clickhandler is inactive
// the same constraints apply (mutatis mutandis) to the photo, so it follows 
// that the label may have a null map and the photo non-null (and vice versa)
//    we therefore conclude that a label must be in one of 3 states:
// o. type null, map null, handlers inactive, but marker non-null
// o. type non-null, map null, handlers inactive, marker non-null
// o. type non-null, map non-null, handlers active, marker non-null
// the state in which type is non-null and map is null is applied to all 
// labels in a segment being deleted (we preserve the information in the 
// action list but don't want the label to be displayed)

function datatype(pos,h,t)
{ this.pos = pos ; 
  this.h = h ; 
  this.marker = this.photomarker = this.type = this.t = null ;
  if(t!=undefined&&t!=null&&isvaliddate(t)&&t.getTime()>365*24*3600000) 
    this.t = t ; 
  this.photo = [] ;
  this.caption = '' ;  
  this.clickhandler = this.righthandler = this.photohandler = null ; 
}
// member functions
datatype.prototype.geticon = function()
{ if(this.type=='Left')  return icons.turnleft ; 
  else if(this.type=='Straight') return icons.straighton ; 
  else if(this.type=='Right') return icons.turnright ; 
  else if(this.type=='Danger') return icons.shriek ; 
  else if(this.type=='Food') return icons.fork ; 
  else return icons.flagsign ; 
} ;
datatype.prototype.setlabelmap = function(m) 
{ if(m==null||this.type==null) m = null ; else m = map ; 
  if(m==null&&this.marker==null) return ;
  this.marker.setMap(m) ; 
  if(m==null&&this.clickhandler!=null)
  { google.maps.event.removeListener(this.clickhandler) ;
    google.maps.event.removeListener(this.righthandler) ;
    this.clickhandler = this.righthandler = null ; 
  }
  if(m!=null&&this.clickhandler==null)
  { this.clickhandler = this.marker.addListener('click',selpoint) ;
    this.righthandler = this.marker.addListener('rightclick',labelcycle) ;
  }
} ;
datatype.prototype.setphotomap = function(m) 
{ if(m==null||this.photo.length==0) m = null ; else m = map ; 
  if(m==null&&this.photomarker==null) return ;
  this.photomarker.setMap(m) ;
  if(m==null&&this.photohandler!=null) 
  { google.maps.event.removeListener(this.photohandler) ;
    this.photohandler = null ; 
  }
  if(m!=null&&this.photohandler==null) 
    this.photohandler = this.photomarker.addListener('click',selpoint) ;
} ;
datatype.prototype.setlabel = function(t,c) 
{ this.type = t ; 
  this.caption = c ; 
  if(t==null) { if(this.marker!=null) this.setlabelmap(null) ; return ; } 
  if(this.marker==null) this.marker = new google.maps.Marker
      ({ position:this.pos,map:map,icon:this.geticon(),title:c,zIndex:1 }) ;
  else { this.marker.setIcon(this.geticon()) ; this.marker.setTitle(c) ; }
  this.setlabelmap(map) ; 
} ;
datatype.prototype.setphoto = function(ind,p) 
{ var i ;
  if(p==null)
  { for(i=ind;i<this.photo.length-1;i++) this.photo[i] = this.photo[i+1] ; 
    this.photo.length -= 1 ; 
    if(this.photo.length==0&&this.photomarker!=null) this.setphotomap(null) ; 
    return ; 
  }
  else { this.photo[ind] = p ; if(ind==0) this.photomarker.setTitle(p) ; }
} ;
datatype.prototype.addphoto = function(p) 
{ this.photo.push(p) ; 
  if(this.photomarker==null) this.photomarker = new google.maps.Marker
      ({ position:this.pos,map:map,icon:icons.camera,title:p,zIndex:1 }) ;
  this.setphotomap(map) ; 
} ;
datatype.prototype.setpos = function(p) 
{ this.pos = p ; 
  if(this.type!=null) this.marker.setPosition(p) ; 
  if(this.photo.length>0) this.photomarker.setPosition(p) ; 
} ;
datatype.prototype.setmap = function(m) 
{ this.setlabelmap(m) ; this.setphotomap(m) ; } ;

datatype.prototype.settype = function(t) 
{ this.type = t ; this.marker.setIcon(this.geticon()) ; } ;

datatype.prototype.labelcycle = function()
{ var oldtype = this.type , type ; 
  if(oldtype=='Generic') type = 'Left' ; 
  else if(oldtype=='Left') type = 'Straight' ; 
  else if(oldtype=='Straight') type = 'Right' ; 
  else if(oldtype=='Right') type = 'Danger' ; 
  else if(oldtype=='Danger') type = 'Food' ; 
  else type = 'Generic' ; 
  this.settype(type) ;
  return [ oldtype , type ] ; 
}
function addlabel(data,pos,type,caption) 
{ var j,ind,mindist ; 
  for(j=0;j<data.length;j++) if(j==0||dist(pos,data[j].pos)<mindist) 
  { mindist = dist(pos,data[j].pos) ; ind = j ; } 
  data[ind].setlabel(type,caption) ;
}
function propstype()
{ this.desc = this.title = this.list = this.inputlen = this.source = null ;
  this.stats = this.tracklink = this.overview = this.stars = null ; 
  this.photo = [] ;
  this.optim = { already: 0, ndel: 0, origlen: 0, parms: null }
}
/* -------------------------------------------------------------------------- */

function readtcx(xmldoc)
{ var nodeno,type,lat,lon,i,j,node,alt,pos,segment,props,title,list,txt ;
  var ind,caption,data,photo,time,valid,anc,xmlnodes,nsegment,propno,names ;
  var track,trackpoint,trackno,course,coursepoint,courseno,courselen,overview ;
  var validalt,fieldnames ; 

  // loop over trackpoints 
  track = xmldoc.getElementsByTagName('Trackpoint') ;
  for(courselen=[],data=[],anc=null,trackno=0;trackno<track.length;trackno++)
  { trackpoint = track[trackno] ;
    if(trackpoint.parentNode.parentNode.nodeName=='Course')
      if(trackpoint.parentNode.parentNode!=anc) 
    { anc = trackpoint.parentNode.parentNode ; 
      courselen.push([trackno,anc]) ; 
    }
    lat = lon = alt = time = null ; 
    photo = [] ;
    for(validalt=valid=1,nodeno=0;nodeno<trackpoint.childNodes.length;nodeno++)
    { node = trackpoint.childNodes[nodeno] ;

      if(node.nodeName=='AltitudeMeters') alt = xmlfloat(node) ; 
      else if(node.nodeName=='Time') // '1970-01-01T03:040:08Z'
        time = new Date(node.childNodes[0].textContent) ; 
      else if(node.nodeName=='Position') for(j=0;j<node.childNodes.length;j++)
      { if(node.childNodes[j].nodeName=='LatitudeDegrees') 
          lat = xmlfloat(node.childNodes[j]) ; 
        else if(node.childNodes[j].nodeName=='LongitudeDegrees') 
          lon = xmlfloat(node.childNodes[j]) ;
      }
      else if(node.nodeName=='Extensions') for(j=0;j<node.childNodes.length;j++)
      { if(node.childNodes[j].nodeName=='Photo') 
          photo = node.childNodes[j].childNodes[0].textContent.split(' ') ;
        else if(node.childNodes[j].nodeName=='ValidTime') valid = 0 ;
        else if(node.childNodes[j].nodeName=='ValidAlt') validalt = 0 ;
      }
    }
    if(lat==null||lon==null) continue ; 
    if(!isvalidnum(alt)) validalt = 0 ; 
    pos = new google.maps.LatLng(lat,lon) ;
    data.push(new datatype(pos,validalt?alt:null,valid?time:null)) ; 
    for(ind=0;ind<photo.length;ind++) data[data.length-1].addphoto(photo[ind]) ;
  }
  if(track.length==0) { alert('no trackpoints') ; throw '' ; }
  if(courselen.length==0) courselen.push([0,null]) ; 
  courselen.push([track.length,null]) ; 

  // loop over coursepoints
  course = xmldoc.getElementsByTagName('CoursePoint') ;
  for(courseno=0;courseno<course.length;courseno++)
  { coursepoint = course[courseno] ;
    caption = type = lat = lon = null ;
    for(nodeno=0;nodeno<coursepoint.childNodes.length;nodeno++)
    { node = coursepoint.childNodes[nodeno] ;
      if(node.nodeName=='Name') caption = node.childNodes[0].textContent ; 
      else if(node.nodeName=='PointType') 
        type = node.childNodes[0].textContent ; 
      else if(node.nodeName=='Position') for(j=0;j<node.childNodes.length;j++)
      { if(node.childNodes[j].nodeName=='LatitudeDegrees') 
          lat = xmlfloat(node.childNodes[j]) ;
        else if(node.childNodes[j].nodeName=='LongitudeDegrees') 
          lon = xmlfloat(node.childNodes[j]) ;
      }
    }
    if(lat==null||lon==null||caption==null||type==null) 
    { alert('Badly formatted course point' ) ; throw '' ; }
    addlabel(data,new google.maps.LatLng(lat,lon),type,caption) ; 
  }

  for(segment=[],i=0;i<courselen.length-1;i++)
    segment.push(data.slice(courselen[i][0],courselen[i+1][0])) ;
  nsegment = segment.length ;

  // props fields
  props = new Array(nsegment) ; 
  for(i=0;i<nsegment;i++) 
  { props[i] = new propstype() ;
    props[i].inputlen = data.length ; 
    if(props[i].optim.origlen==0) props[i].optim.origlen = data.length ;
  }

  // optimised?
  xmlnodes = xmldoc.getElementsByTagName('Optimised') ;
  if(xmlnodes.length)
  { props[0].optim.already = 1 ; 
    props[0].optim.origlen = parseInt(xmlnodes[0].getAttribute('from')) ; 
    props[0].optim.ndel = 
      props[0].optim.origlen - parseInt(xmlnodes[0].getAttribute('to')) ; 
    props[0].optim.parms = 
      { tol: parseFloat(xmlnodes[0].getAttribute('tol')) ,
        maxsep: parseFloat(xmlnodes[0].getAttribute('maxsep')) ,
        wppenalty: parseFloat(xmlnodes[0].getAttribute('wppenalty')) ,
        vweight: parseFloat(xmlnodes[0].getAttribute('vweight')) 
      } ; 
    for(i=1;i<nsegment;i++) props[i].optim = props[0].optim ; 
  }

  // overview fields (different values for each segment) all in one big loop
  names = [ 'Name' ,     'LongTitle' , 'Description' , 'Stats' , 'PhotoList' ,
            'Overview' , 'Index' ,     'TrackLink' , 'Stars' ,       'Photo' ] ;
  fieldnames = [ 'title' ,    'desc' ,     'desc' ,      'stats' , 'list' ,
                 'overview' , 'overview' , 'tracklink' , 'stars' , 'photo' ] ;
  for(title=null,propno=0;propno<names.length;propno++)
  { xmlnodes = xmldoc.getElementsByTagName(names[propno]) ;
    for(i=0;i<xmlnodes.length;i++)
    { node = xmlnodes[i] ;
      if(propno==0) anc = node.parentNode ;
      else anc = node.parentNode.parentNode ;
      if(propno==4) txt = node.getAttribute('src') ;
      else if(propno==5||propno==6||propno==7) txt = node.getAttribute('href') ;
      else txt = node.childNodes[0].textContent ;
      for(j=0;j<nsegment&&anc!=courselen[j][1];j++) ;
      if(j==nsegment)
      { if(propno==0&&title==null)
          if( node.parentNode.nodeName=='Courses' 
           || node.parentNode.nodeName=='Lap' ) title = txt ;  
        continue ; 
      }
      if(names[propno]=='Photo') props[j].photo = txt.match(/\S+/g) ;
      else props[j][fieldnames[propno]] = txt ;
    }
  }

  return { title: title , props: props , segments: segment } ;
}
/* -------------------------------------------------------------------------- */

function readgpx(xmldoc)
{ var xmlcoords,nodeno,type,lat,lon,i,node,alt,pos,caption,data,time ; 
  var props = new propstype() ; 

  // get the route name
  xmlcoords = xmldoc.getElementsByTagName('name') ;
  for(i=0;props.title==null&&i<xmlcoords.length;i++)
    if(xmlcoords[i].parentNode.nodeName!='wpt') 
      props.title = xmlcoords[i].childNodes[0].textContent ;

  // get the route description
  xmlcoords = xmldoc.getElementsByTagName('desc') ;
  if(xmlcoords.length>0) 
    props.desc = xmlcoords[0].childNodes[0].textContent ;

  // loop over the track points to get the coords
  xmlcoords = xmldoc.getElementsByTagName('trkpt') ;
  if(xmlcoords.length==0) xmlcoords = xmldoc.getElementsByTagName('rtept') ;

  for(data=[],i=0;i<xmlcoords.length;i++)
  { lat = parseFloat(xmlcoords[i].getAttribute('lat')) ; 
    lon = parseFloat(xmlcoords[i].getAttribute('lon')) ; 
    pos = new google.maps.LatLng(lat,lon) ;

    for(time=alt=null,nodeno=0;nodeno<xmlcoords[i].childNodes.length;nodeno++)
    { node = xmlcoords[i].childNodes[nodeno] ;
      if(node.nodeName=='ele') alt = parseFloat(node.textContent) ; 
      else if(node.nodeName=='time')
        time = new Date(node.childNodes[0].textContent) ; 
    }
    if(!isvalidnum(alt)) alt = null ; 
    data.push(new datatype(pos,alt,time)); 
  }

  // loop over the course points to get the labels
  xmlcoords = xmldoc.getElementsByTagName('wpt') ;
  for(i=0;i<xmlcoords.length;i++)
  { caption = type = lat = lon = null ;
    lat = parseFloat(xmlcoords[i].getAttribute('lat')) ; 
    lon = parseFloat(xmlcoords[i].getAttribute('lon')) ; 
    for(nodeno=0;nodeno<xmlcoords[i].childNodes.length;nodeno++)
    { node = xmlcoords[i].childNodes[nodeno] ;
      if(node.nodeName=='name') caption = node.childNodes[0].textContent ; 
      else if(node.nodeName=='type') type = node.childNodes[0].textContent ; 
    }
    if(lat==null||lon==null||caption==null) 
    { alert('Badly formatted course point' ) ; throw '' ; }
    if(type==null) type = 'Generic' ; 
    addlabel(data,new google.maps.LatLng(lat,lon),type,caption) ; 
  }

  props.inputlen = data.length ; 
  return { props: [props] , segments: [data] } ;
}
/* -------------------------------------------------------------------------- */
/*page*/
/* -------------------------------------------------------------------------- */

function dist(x,y)
{ return google.maps.geometry.spherical.computeDistanceBetween(x,y) ; }

function angle(x,y)
{ return google.maps.geometry.spherical.computeHeading(x,y)*Math.PI/180 ; }

/* -------------------------------------------------------------------------- */

function optimise(idata,parms)
{ var stk,nnstk,stk2,clen=idata.length,i,j,m,step=new Array(clen-1) ; 
  var opos,oalt,npos,nalt,ndatum,mpos,malt,arccentre,arctol,pathpos ; 
  var legal,theta,omega,x,y,hyp,tdist,maxtheta,mintheta,d,dh,od,odh,odash ; 
  var bearings=new Array(clen),nstk=new Array(clen),pi=Math.PI,tol=parms.tol ;

  stk = [ { data:[idata[0]] , err:0 , pathpos:1 } ] ;
  for(i=0;i<clen-1;i++) step[i] = dist(idata[i].pos,idata[i+1].pos) ; 

  // this is a forwards dynamic program. in stk we have a list of hypotheses
  // each of which advances a different number of points through the data, 
  // sorted increasing on how far they've advanced. at each step we take the 
  // first item from the stack and try extending to each legal successor point.
  //    note that a hypothesis whose pathpos is k is one whose last point is
  // idata[k-1].
  //    note too that I could (& should) preallocate stk and stk2 for efficiency
  while(stk[0].pathpos<clen)
  { pathpos = stk[0].pathpos ;
    opos = idata[pathpos-1].pos ;
    oalt = idata[pathpos-1].h ; 
    // try extending to pathpos+i
    for(arctol=null,nnstk=i=0;i<clen-pathpos;i++)
    { ndatum = idata[pathpos+i] ; 
      npos = ndatum.pos ; 
      nalt = ndatum.h ; 
      if(i==0) hyp = step[pathpos-1] ;
      else if((hyp=dist(opos,npos))>parms.maxsep) break ; 
      omega = angle(opos,npos) ; 
      // find the min and max legal bearing
      if(hyp>tol) 
      { theta = Math.asin(tol/hyp) ; 
        if(arctol==null) { arccentre = omega ; arctol = theta ; } 
        else
        { for(odash=omega-arccentre;odash>pi;odash-=2*pi) ; 
          while(odash<-pi) odash += 2*pi ;
          maxtheta = Math.min(arctol,odash+theta) ; 
          mintheta = Math.max(-arctol,odash-theta) ; 
          if(maxtheta<mintheta) break ; 
          arccentre += (maxtheta+mintheta) /2 ; 
          arctol     = (maxtheta-mintheta) /2 ;
        }
      } 
      /* -------------------------------------------------------------------- */
      /*page*/
      /* -------------------------------------------------------------------- */

      bearings[i] = { hyp:hyp , omega:omega } ; 
      // see whether this breaches the max error on any intermediate point
      for(legal=1,od=odh=tdist=m=0;m<i;m++,od=d,odh=dh)
      { mpos = idata[pathpos+m].pos ;
        malt = idata[pathpos+m].h ; 
        x = bearings[m].hyp ; 
        theta = bearings[m].omega ; 
        d = x * Math.sin(theta-omega) ; 
        dh = 0 ;
        if(d*d<tol*tol&&oalt!=null&&nalt!=null&&malt!=null)  
        { y = hyp - x*Math.cos(theta-omega) ;
          y = Math.sqrt(d*d+y*y) ; 
          dh = parms.vweight * ( malt - (oalt*y+nalt*x)/(x+y) ) ; 
        }
        if(d*d+dh*dh>tol*tol) { legal = 0 ; break ; } 
        tdist += step[pathpos-1+m] * ( d*d+d*od+od*od + dh*dh+odh*dh+odh*odh ) ;
      }
      // if we emerge with 'legal' non-zero then we may advance to pathpos+i 
      // and tdist is the sum of squared errors
      if(legal) nstk[nnstk++] = 
      ( { data:     stk[0].data.concat([ndatum]) , 
          err:      stk[0].err + pi*tdist/3 + parms.wppenalty , 
          pathpos:  stk[0].pathpos+i+1 
        } ) ; 
      if(ndatum.type!=null||ndatum.photo.length>0) break ; 
    }  // end loop over i 

    // now we have in nstk the possible extensions of stk[0] in increasing
    // order of end point, so we merge with stk[0..stk.length-1]
    for(stk2=[],i=1,j=0;i<stk.length||j<nnstk;)
      if(i==stk.length) stk2.push(nstk[j++]) ; 
      else if(j==nnstk||stk[i].pathpos<nstk[j].pathpos) stk2.push(stk[i++]) ; 
      else if(stk[i].pathpos>nstk[j].pathpos) stk2.push(nstk[j++]) ; 
      else if(stk[i].err<nstk[j].err) { stk2.push(stk[i++]) ; j += 1 ; } 
      else { stk2.push(nstk[j++]) ; i += 1 ; } 
    stk = stk2 ; 
  }
  return stk[0].data ; 
}
/* -------------------------------------------------------------------------- */
/*page*/
/* ------------------------------- writetcx  -------------------------------- */

function writetcx(props,idata) 
{ var i,j,k,xmldoc,course,lap,datum,filename,track,time,routelen,nnull,sum ; 
  var trackpoint,coursepoint,str,flag,clen=idata.length,tlast,di,dk,pointdist ;
  var origlen,ndel,ano,photo,thisuri,maxsep,sep,tdist,ttime,time,otime,x,y ; 
  var distance = new Array(clen) , msecs = new Array(clen) ;
  var valid = new Array(clen) , alt = new Array(clen) ;

  for(tlast=null,nnull=maxsep=tdist=ttime=i=flag=0;i<clen;otime=time,i++) 
  { if((alt[i]=idata[i].h)==null) nnull += 1 ; 
    time = idata[i].t ;
    if(time!=null) { msecs[i] = time = time.getTime() ; valid[i] = 1 ; } 
    if(tlast!=null&&time!=null&&time<tlast) flag = 1 ; // out of order
    if(time!=null) tlast = time ;
    if(i) 
    { sep = dist(idata[i-1].pos,idata[i].pos) ;
      distance[i] = distance[i-1] + sep ; 
      if(sep>maxsep) maxsep = sep ; 
      if(time!=null&&otime!=null) { tdist += sep ; ttime += time - otime ; }
    }
    else distance[i] = 0 ; 
  }
  routelen = distance[clen-1] ;

  if(maxsep>100&&!confirm('Some gaps between waypoints are >100m.\n'+
    'This will cause problems if used for navigation in a Garmin.\n'+
    'You can hit [OK] and I will proceed anyway, or\n'+
    'you can hit [Cancel] and interpolate extra points\n'+
    '(recommended \u2013 go to Route Info under the cogwheel).')) return null ; 

  // decide what to do if some points have no altitudes
  if(nnull==clen) { alert('no points have altitudes') ; return null ; }
  if(nnull>0&&!confirm(nnull+' waypoints have no associated altitudes.\n' +
  'You can hit [OK] and I will interpolate altitudes (not guaranteed),\n'+
  'or you can hit [Cancel] and try again later when the altitudes may be '+
  'available.')) return null ; 

  if(nnull) for(pointdist=new Array(clen),i=0;i<clen;i=j)
  { for(;i<clen&&idata[i].h!=null;i++) ;          // advance to null
    if(i==clen) break ; 
    for(j=i+1;j<clen&&idata[j]==null;j++) ;       // advance to non-null
    if(i==0) { for(y=idata[j].h;i<j;i++) alt[i] = y ; continue ; } 
    if(j==clen) { for(x=idata[i-1].h;i<j;i++) alt[i] = x ; continue ; } 
    for(sum=k=0;k<=j-i;k++) 
      sum = pointdist[k] = sum + dist(idata[i+k-1].pos,idata[i+k].pos) ; 
    for(x=idata[i-1].h,y=idata[j].h,k=0;k<j-i;k++) 
      alt[i+k] = ( x*(sum-pointdist[k]) + y*pointdist[k] ) / sum ; 
  }

  // fill in missing times
  if(tdist==0||flag!=0) for(i=0;i<clen;i++) msecs[i] = distance[i] * 333 ;
  else for(i=0;i<clen;i=k)
  { for(;i<clen&&idata[i].t!=null;i++) ;      // advance to null
    if(i==clen) break ;
    for(k=i+1;k<clen&&idata[k].t==null;k++) ; // advance to non-null
    for(j=i;j<k;j++) valid[i] = 0 ;
    if(i==0) for(time=msecs[k],j=i;j<k;j++)
      msecs[j] = time - (distance[k]-distance[j])*ttime/tdist ;
    else if(k==clen) for(time=msecs[i-1],j=i;j<clen;j++)
      msecs[j] = time + (distance[j]-distance[i-1])*ttime/tdist ;
    else for(j=i,di=distance[i-1],dk=distance[k];j<k;j++) 
      msecs[j] = ( msecs[i-1]*(dk-distance[j]) + msecs[k]*(distance[j]-di) ) 
                        / (dk-di) ;
  }

  str = '<?xml version="1.0" encoding="UTF-8" standalone="no" ?>\n' +
        '<TrainingCenterDatabase xmlns="http://www.garmin.com/xmlschemas/' +
        'TrainingCenterDatabase/v2"\n' +
        '          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"\n' +
        '          xsi:schemaLocation="http://www.garmin.com/' +
        'xmlschemas/TrainingCenterDatabase/v2 ' + 
        'http://www.garmin.com/xmlschemas/TrainingCenterDatabasev2.xsd">\n' +
        '<!-- http://www.masterlyinactivity.com/software/routemaster.html -->' ;
  str += '\n  <Folders><Courses><CourseFolder Name="Courses">\n' ;
  str += '        <CourseNameRef><Id>'+props.title+'</Id></CourseNameRef>\n' ;
  str += '  </CourseFolder></Courses></Folders>\n<Courses><Course>\n' ; 
  str += '  <Name>'+props.title+'</Name>\n  <Lap>\n' + adddist(routelen) ; 
  time = (msecs[clen-1]-msecs[0]) / 1000 ;
  str += '    <TotalTimeSeconds>' + time.toFixed(0) + '</TotalTimeSeconds>\n' ;
  str += addpos('Begin',idata[0].pos) ; 
  str += addpos('End',idata[clen-1].pos) ; 
  str += '    <Intensity>Active</Intensity>\n' + '  </Lap>\n  <Track>\n' ; 

  // loop over trackpoints
  for(i=0;i<idata.length;i++) 
  { str += '  <Trackpoint>\n' + addpos('',idata[i].pos) ; 
    str += adddist(distance[i]) + addalt(idata[i].h==null?alt[i]:idata[i].h) ;
    str += '    <Time>' + new Date(msecs[i]).toISOString() + '</Time>\n' ;
    if(idata[i].photo.length>0)
    { str += '    <Extensions><Photo>' ;
      for(k=0;k<idata[i].photo.length;k++)
      { if(k) str += ' ' ; str += idata[i].photo[k] ; }
      str += '</Photo></Extensions>\n' ; 
    }
    if(valid[i]==0||idata[i].h==null) 
    { str += '    <Extensions>' ; 
      if(valid[i]==0) str += '<ValidTime>False</ValidTime>' ;
      if((valid[i]==0&&idata[i].h==null)) str += '\n                ' ;
      if(idata[i].h==null) str += '<ValidAlt>False</ValidAlt>' ;
      str += '</Extensions>\n' ; 
    }
    str += '    <SensorState>Absent</SensorState>\n  </Trackpoint>\n' ; 
  }
  str += '  </Track>\n\n' ;

  if( props.optim.ndel>0 || props.list!=null || props.stars!=null
   || props.desc!=null   || props.overview != null) 
  { str += '  <Extensions>\n' ;
    if(props.optim.ndel) 
    { str += '    <Optimised from="' + props.optim.origlen +
                            '" to="' + (props.optim.origlen-props.optim.ndel) ;
      if(props.optim.parms!=null) 
        str += '" tol="' + props.optim.parms.tol.toFixed(0) +
                '" maxsep="' + props.optim.parms.maxsep.toFixed(0) +
                '" wppenalty="' + props.optim.parms.wppenalty.toFixed(0) +
                '" vweight="' + props.optim.parms.vweight.toFixed(1) ;
      str += '"/>\n'
    }
    if(props.list!=null) str += '    <PhotoList src="'+props.list+'"/>\n' ; 
    if(props.stars!=null) str += '    <Stars>'+props.stars+'</Stars>\n' ; 
    if(props.desc!=null) 
      str += '    <Description>'+props.desc+'</Description>\n' ; 
    if(props.overview!=null) 
      str += '    <Index href=">' + props.overview + '"/>\n' ; 
    str += '  </Extensions>\n\n' ;
  }
  
  // finally loop over coursepoints
  for(i=0;i<idata.length;i++) if(idata[i].type!=null)
  { datum = idata[i] ;
    str += '  <CoursePoint><Name>'+datum.marker.title+'</Name>\n' ; 
    str += '    <PointType>'+datum.type+'</PointType>\n' ; 
    str += addpos('',idata[i].pos) + addalt(idata[i].h) ;
    time = new Date(msecs[i]) ; 
    str += '    <Time>' + time.toISOString() + '</Time>\n  </CoursePoint>\n' ;
  }
  return str + '</Course></Courses></TrainingCenterDatabase>\n' ; 
}
/* -------------------------------------------------------------------------- */

function addpos(tag,pos)
{ var str = '    <'+tag+'Position>\n      <LatitudeDegrees>' ; 
  str += pos.lat().toFixed(5)+'</LatitudeDegrees>\n      <LongitudeDegrees>' ;
  str += pos.lng().toFixed(5)+'</LongitudeDegrees>\n    </'+tag+'Position>\n' ;
  return str ;
}
function addalt(x)
{ return '    <AltitudeMeters>' + x.toFixed(0) + '</AltitudeMeters>\n' ; }
function adddist(x)
{ return '    <DistanceMeters>' + x.toFixed(0) + '</DistanceMeters>\n' ; }

/* ----------------------------- writeoverview  ----------------------------- */

function writeoverview(segments,title,list) 
{ var i,j,h,oh,maxalt,minalt,routelen,up,down,ndata,photo,segno,idata,title ;
  var uri ; 
  var str = '<?xml version="1.0" encoding="UTF-8" standalone="no" ?>\n' +
        '<!-- http://www.masterlyinactivity.com/software/routemaster.html -->' +
        '\n<!-- **** routemaster index file **** not for navigation ****' +
        ' -->\n<TrainingCenterDatabase><Courses>\n' ;

  if(title!='Untitled Route'&&title!=null) str += '<Name>'+title+'</Name>\n' ;
  str += '\n' ; 

  for(segno=0;segno<segments.length;segno++)
  { idata = segments[segno].data ;
    for(oh=maxalt=minalt=null,routelen=up=down=i=0;i<idata.length;oh=h,i++)
    { if(i) routelen += dist(idata[i-1].pos,idata[i].pos) ;
      if((h=idata[i].h)==null) continue ; 
      if(maxalt==null||h>maxalt) maxalt = h ; 
      if(minalt==null||h<minalt) minalt = h ; 
      if(h!=null&&oh!=null) { if(h>oh) up += h-oh ; else down += oh-h ; } 
    }

    for(photo=[],ndata=new Array(idata.length),i=0;i<idata.length;i++)
    { ndata[i] = new datatype(idata[i].pos,0) ;
      for(j=0;j<idata[i].photo.length;j++) photo.push(idata[i].photo[j]) ; 
    }
    idata = optimise(ndata,{tol:500,maxsep:100000,wppenalty:10000,vweight:0}) ;

    str += '<Course>\n' ;
    if(segments[segno].props.title!=null) 
      str += '  <Name>' + segments[segno].props.title + '</Name>\n' ;
    str += '  <Track>\n' ; 

    // loop over trackpoints
    for(i=0;i<idata.length;i++) 
      str += '    <Trackpoint><Position>\n      <LatitudeDegrees>' +
             idata[i].pos.lat().toFixed(4)+'</LatitudeDegrees>\n      ' +
             '<LongitudeDegrees>' + idata[i].pos.lng().toFixed(4) +
             '</LongitudeDegrees>\n    </Position></Trackpoint>\n' ; 
    str += '  </Track>\n\n  <Extensions>\n' ;

    title = segments[segno].props.stars ;
    if(title!=null) str += '    <Stars>' + title + '</Stars>\n' ; 

    title = segments[segno].props.desc ;
    if(title!=null) str += '    <Description>' + title + '</Description>\n' ; 

    uri = document.URL ;  
    if((title=segments[segno].props.source)!=null) 
    { if(title[1]=='uri') uri = reluri(uri,'?'+title[0]) ; 
      else uri = reluri(uri,'?$FILE$/'+title[0]) ; 
    }
    str += '    <Stats>Distance ' + (routelen/1000).toFixed(1) + 'km;' +
           ' altitude ' + minalt.toFixed(0) + '-' + maxalt.toFixed(0) + 
           'm; \u2191' + up.toFixed(0) + 'm \u2193' + down.toFixed(0) + 
           'm</Stats>\n    <TrackLink href="' + uri + '"/>\n' ; 

    if(list!=null) str += '    <PhotoList src="' + list + '"/>\n' ; 

    if(photo.length)
    { str += '    <Photo>' ;
      for(i=0;i<photo.length;i++) 
      { if(i>0&&i%6==0) str += '\n           ' ; else if(i) str += ' ' ; 
        str += photo[i] ; 
      }
      if(photo.length%6==0) str += '\n           ' ; 
      str += '</Photo>\n' ;
    }
    str += '  </Extensions>\n</Course>\n\n' ;
  }

  return str + '</Courses></TrainingCenterDatabase>\n' ; 
}
/* -------------------------------------------------------------------------- */

function gencolours(n)
{ var colours=new Array(n) ;
  var ind,density,k,a,na,i,j,m,r,g,b ;

  for(ind=0,density=1;ind<n;density*=2)
  { if(density==1) k = 3 ; else k = Math.floor(0.5+0.75*density*(1+density/2)) ;
    a = new Array(k) ;
    if(density==1) { a = [ [0,0] , [0,1] , [1,0] ] ; na = 3 ; }
    else for(na=i=0;i<=density;i++) 
    { if((i&1)==0) for(j=1;i+j<=density;j+=2) a[na++] = [ i , j ] ;
      else for(j=density-i;j>=0;j--) a[na++] = [ i , j ] ;
    }
    if(na!=k) alert('logic error') ; 
    for(k=0;(1<<k)<na;k++) ;
    for(i=0;i<(1<<k)&&ind<n;i++)
    { for(m=j=0;j<k;j++) m |= ((i>>j)&1) << (k-1-j) ;
      if(m>=na) continue ;
      r = ( density - a[m][0] - a[m][1] ) * (255/density) ;
      g = a[m][1] * (180/density) ;
      b = a[m][0] * (300/density) ;
      r = ("00"+Math.floor(0.5+r).toString(16)).substr(-2) ;
      g = ("00"+Math.floor(0.5+g).toString(16)).substr(-2) ;
      b = ("00"+Math.floor(0.5+b>255?255:b).toString(16)).substr(-2) ;
      colours[ind++] = '#' + r + g + b ; 
    }      
  }
  return colours ;
}
/* -------------------------------------------------------------------------- */

Archived from routemaster.html

var segments=[],selected,actions,nactions,sel,dragging,loadno,overviewing=0 ; var pending,xpending,mouseopt=0,elevator,resuri,xmlfile=null ; var routetitle,body,mapdiv,pro ; var scroller=null,longtitle=null,overview=null,imgdiv,imghandle,imginfo,imgind ; var cursorbtn,scissorsbtn,binbtn,undobtn,redobtn,penbtn,setbtn=null,dlbtn ; var defparms = {tol:10,maxsep:95,wppenalty:1000,vweight:1} ; var neutral='<span style="font-family:helvetica">' ; var active='<span style="cursor:pointer;color:#0000bd" onclick=' ; var inactive='<span style="color:silver">' ; var textbox='<div style="margin-bottom:2px;border-bottom:solid 1px silver;' + 'padding-bottom:2px;font-family:helvetica">' ; var finalbox='<div style="font-family:helvetica">' ; var parser = new DOMParser() ; var map = null , clickhandle = null ; var unsavedchanges = [] ; var infowindow = { handle: null , type: null , open: function(s,pos,type) { this.handle = new google.maps.InfoWindow({content:s,position:pos}) ; this.handle.open(map) ; google.maps.event.addListener(this.handle,'closeclick',function() { if(infowindow.type=='highlight') { redraw(selected[0]) ; if(scroller!=null) { clearInterval(scroller) ; scroller = null ; } } else if(infowindow.type=='phinfo') walkto(selected[0],selected[1]) ; infowindow.handle = infowindow.type = null ; } ) ; this.type = type ; } , close: function() { if(this.handle==null) return null ; var response = this.type ; this.handle.close() ; if(response=='highlight') { redraw(selected[0]) ; if(scroller!=null) { clearInterval(scroller) ; scroller = null ; } } else if(response=='phinfo') walkto(selected[0],selected[1]) ; this.handle = this.type = null ; return response ; } } ; /* --------------- construct a segment from an xml document ----------------- */ function genseg(a,b) { this.data = a ; this.props = b ; this.route = this.routehandler = this.dots = this.dothandler = null ; this.colour = "red" ; } /* -------------------------------------------------------------------------- */ /* CONSTRUCTORS */ /* -------------------------------------------------------------------------- */ function dotpath(a,b) { this.path = [a,b] ; this.cursor = 'default' ; this.geodesic = true ; this.strokeOpacity = 0 ; this.icons = [ { icon: { path: 'M 0 0 L 1 0',strokeOpacity:1,scale:1 } , offset: '1px' , repeat: '4px' } ] ; this.zIndex = 0 ; } /* -------------------------------------------------------------------------- */ function linepath(s0,start,end,colour,width) { var i,len=(start<0?segments[s0].data.length:end-start) ; if(width==undefined) width = 2 ; this.path = new Array(len) ; if(start<0) for(i=0;i<len;i++) this.path[i] = segments[s0].data[i].pos ; else for(i=0;i<len;i++) this.path[i] = segments[s0].data[start+i].pos ; this.clickable = 'false' ; this.cursor = 'default' ; this.geodesic = true ; this.strokeColor = colour ; this.strokeOpacity = 1.0 ; this.strokeWeight = width ; if(width==2) this.zIndex = 0 ; else this.zIndex = 1 ; } /* -------------------------------------------------------------------------- */ function listinfo() { this.list = [] ; this.sizes = [] ; this.uri = null ; this.thumbind = this.scale = this.status = this.type = this.pixpage = null ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------------------------------------------------- */ /* UTILITY FUNCTIONS */ /* -------------------------------------------------------------------------- */ function interp(x,y,lamda) { return google.maps.geometry.spherical.interpolate(x,y,lamda) ; } function bearing(x,y) { return google.maps.geometry.spherical.computeHeading(x,y) ; } /* --------------------------- button handlers ----------------------------- */ function greyout(btn) { if(overviewing||btn.active==0) return 0 ; btn.btn.setAttribute('src',btn.greyimg) ; btn.ui.removeEventListener('click',btn.handler) ; if(btn==penbtn) btn.ui.removeEventListener('contextmenu',photoprompt) ; btn.ui.style.cursor = 'default' ; btn.active = 0 ; return 1 ; } function blackout(btn) { if(overviewing||btn.active) return ; btn.btn.setAttribute('src',btn.blackimg) ; btn.ui.addEventListener('click',btn.handler) ; if(btn==penbtn) btn.ui.addEventListener('contextmenu',photoprompt) ; btn.ui.style.cursor = 'pointer' ; btn.active = 1 ; } /* ------------------------ enter/exit full screen -------------------------- */ // most of the code is available from pixlib function enterFullscreen() { infowindow.close() ; enterfullscreen() ; } function exitFullscreen() { infowindow.close() ; if(document.exitFullscreen) document.exitFullscreen() ; else if(document.mozCancelFullScreen) document.mozCancelFullScreen() ; else if(document.webkitExitFullscreen) document.webkitExitFullscreen() ; } /* -------------------------------------------------------------------------- */ function findimg(id) { var i ; for(i=0;i<imginfo.list.length;i++) if(imginfo.list[i].name!=undefined&&imginfo.list[i].name==id) return i ; return -1 ; } /* ------------------- message warning of unsaved changes ------------------- */ function unsavedmsg(ok) { var msg , len = unsavedchanges.length , i ; if(len==0) return null ; msg = 'You have ' + len + ' unsaved change' + (len==1?"":'s') ; if(len<=3) for(i=0;i<len;i++) msg += (i?',':' (') + unsavedchanges[i] + (i==len-1?')':'') ; msg += '\nIf you hit ' + (ok?'[OK]':'[Leave page]') ; return msg + (len==1?' this change':' these changes') + ' will be lost.' ; } /* -------------------------------------------------------------------------- */
/*page*/ /* --------------- selpoint: choose the clicked waypoint ------------------- */ function selpoint(event) { var i,j,closest,d,mindist,s0,s1 ; if(dragging) return ; var flag = (infowindow.close()=='wpinfo') && (event.shiftKey==0) ; if(overviewing==0&&event.shiftKey) { s0 = selected[0] ; s1 = segments[s0].data.length ; insert(s0,s1,1) ; segments[s0].data[s1].setpos(event.latLng) ; lookupalt(s0,s1) ; redrawconnect(s0,s1) ; done(['move',s0,s1,event.latLng,event.latLng,1]) ; } else for(s1=s0=-1,i=0;i<segments.length;i++) for(j=0;j<segments[i].data.length;j++) { d = dist(segments[i].data[j].pos,event.latLng) ; if(s0<0||d<mindist) { s0 = i ; s1 = j ; mindist = d ; } } walkto(s0,s1,flag) ; } /* -------------------------- track highlighter ---------------------------- */ function highlight() { var s0=selected[0],scroll,thind=null ; infowindow.close() ; undraw(s0) ; draw(s0,4) ; if(imginfo.uri!=null) thind = thumbind(imginfo.sizes) ; scroll = highdiv(segments[s0].props,imginfo.list,imginfo.sizes, thind,segments[s0].props.photo) ; scroller = scroll.scroller ; infowindow.open(scroll.div,northernmost(segments[s0].data),'highlight') ; } /* ------------------------------- getbtnpos -------------------------------- */ function getbtnpos(btnno) { var bounds=map.getBounds(),sw,ne,lat,lon,lam ; sw = bounds.getSouthWest() ; ne = bounds.getNorthEast() ; lam = 52.0 / window.innerHeight ; lat = lam*ne.lat() + (1-lam)*sw.lat() ; lam = 0.5 + (btnno*32-112.0)/window.innerWidth ; lon = lam*ne.lng() + (1-lam)*sw.lng() ; return new google.maps.LatLng(lat,lon) ; } /* ----- unambig: does the selected waypoint determine a unique segment? ---- */ function unambig() // does the selected waypoint determine a unique segment? { var s0=selected[0],s1=selected[1] ; if(segments.length==1) return 1 ; if( ( s0==segments.length-1 || s1!=segments[s0].data.length-1 || ! segments[s0].data[s1].pos.equals(segments[s0+1].data[0].pos) ) && ( s0==0 || s1!=0 || ! segments[s0].data[s1].pos.equals (segments[s0-1].data[segments[s0-1].data.length-1].pos) ) ) return 1 ; else return 0 ; } /* -------------------------------------------------------------------------- */
/*page*/ /* --------------------------------- walkto --------------------------------- */ // draw a selection point (and possibly an info box) at [s0,s1], bringing up // a wpinfo window if flag = 0 or a selinfo window if flag = 1 function walkto(s0,s1,flag) { var s='',i,ind,excuse,imgname=null,phind=-1,list=imginfo.list,sset ; var datum = segments[s0].data[s1] , pos = datum.pos ; if(flag==undefined) flag = 0 ; selected = [ s0,s1 ] ; if(overviewing) return highlight() ; map.panToBounds(new google.maps.LatLngBounds(pos,pos)) ; drawsel(0,[s0,s1]) ; if(flag||(datum.type==null&&datum.photo.length==0)) { if(flag==1) wpinfo() ; else if(flag==2) seginfo() ; else if(flag==3) highlight() ; return ; } if(datum.type!=null) { if(datum.photo.length>0) s = textbox ; else s = finalbox ; if(datum.type!='Generic') s += datum.type + ': ' ; s += datum.marker.title + ' [' + active + '"labelprompt()">Edit</span>]' ; s += '</div>' ; } for(ind=0;ind<datum.photo.length;ind++) { s += textbox ; if(imginfo.status=='ready'&&(phind=findimg(datum.photo[ind]))>=0) { sset = srcset(list[phind],imginfo.sizes,imginfo.thumbind) ; s += '<img src="' + jpg(list[phind],imginfo.sizes,imginfo.thumbind) + '" width=' + list[phind].thumbshape[0] + ' height=' + list[phind].thumbshape[1] + (sset==''?'':(' srcset="'+sset+'"')) + '><br>' + '<b>' + list[phind].name + '</b>: ' ; } else { if(imginfo.status=='null') excuse = 'no list provided' ; else if(imginfo.status=='ready') { imgname = imginfo.uri ; i = imgname.lastIndexOf('/') ; if(i>=0) imgname = imgname.substring(i+1) ; excuse = 'not present in ' + imgname ; } else if(imginfo.status=='waiting') excuse = imgname + ' is not available' ; else excuse = 'imginfo.status = ' + imginfo.status ; s += 'Photo: ' + datum.photo[ind] + ' (' + excuse + ') ' ; } s += '['+active+ '"photoedit('+ind+')">Edit</span>'+']' ; if(phind>=0) s += ' : ['+active+ '"phinfo('+phind+')">Info</span>'+']' + ' : ['+active+ '"display('+ind+')">Enlarge</span>'+']' ; s += '</div>' ; } if(datum.photo.length>0) s += finalbox + '[' + active + '"photoprompt' + '(null)">Add photo</span>' + ']</div>' ; infowindow.open(s,pos,'walking') ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------- keystroke handler ---------------------------- */ function keystroke(e) { var s0=selected[0],s1=selected[1],slast,flag ; if(e.keyCode==40&&overviewing==0) { map.panTo(segments[s0].data[s1].pos) ; return ; } flag = infowindow.close() ; if(flag=='highlight') flag = 3 ; else if(flag=='seginfo') flag = 2 ; else if(flag=='wpinfo') flag = 1 ; else flag = 0 ; if(e.keyCode==32) { selclick() ; return ; } // space if(overviewing) { if(e.keyCode==70) enterfullscreen() ; if(flag!=3||(e.keyCode!=39&&e.keyCode!=37&&e.keyCode!=8&&e.keyCode!=46)) return ; } if(e.keyCode==13) // return { if(dragging) undraggit() ; else if(s0>=0) draggit(0) ; return ; } if(dragging) return ; if(e.keyCode==8||e.keyCode==46) // delete/backspace { e.preventDefault() ; if((flag==3&&segments.length>1)||binbtn.active) discard() ; return ; } if(e.keyCode==9) { e.preventDefault() ; inswp(1) ; return ; } // tab if(e.keyCode==39) // forwards { e.preventDefault() ; if(flag>=2) { s1 = 0 ; s0 += 1 ; if(s0==segments.length) s0 = 0 ; } else if(s1<segments[s0].data.length-1) s1 += 1 ; else { s0 += 1 ; if(s0==segments.length) s0 = 0 ; s1 = 0 ; } } else if(e.keyCode==37) // backwards { e.preventDefault() ; if(flag>=2) { s1 = 0 ; s0 -= 1 ; if(s0<0) s0 = segments.length-1 ; } else if(s1>0) s1 -= 1 ; else { s0 -= 1 ; if(s0<0) s0 = segments.length-1 ; s1 = segments[s0].data.length-1 ; } } else return ; walkto(s0,s1,flag) ; } /* --------------------- undraw & redraw segments -------------------------- */ function undraw(i) { segments[i].route.setMap(null) ; if(segments[i].clickhandler!=null) { google.maps.event.removeListener(segments[i].clickhandler) ; segments[i].clickhandler = null ; } } function redraw(i) { undraw(i) ; draw(i) ; } function recolour(i) { if(overviewing) return ; else if(i&1) segments[i].route.setOptions({strokeColor:"#ff9999"}) ; else segments[i].route.setOptions({strokeColor:"#ff0000"}) ; } function obliterate(s0) // undraw route and all labels { var i ; for(i=0;i<segments[s0].data.length;i++) segments[s0].data[i].setmap(null) ; undraw(s0) ; disconnect(s0-1) ; disconnect(s0) ; } /* ----------------------------- draw segments ------------------------------ */ function draw(i,width) { var colour,poly ; if(overviewing) colour = segments[i].colour ; else if(i&1) colour = "#ff9999" ; else colour = "#ff0000" ; if(width==undefined) poly = new linepath(i,-1,0,colour) ; else poly = new linepath(i,-1,0,colour,width) ; segments[i].route = new google.maps.Polyline(poly) ; segments[i].route.setMap(map) ; if(segments[i].clickhandler==null) segments[i].clickhandler = google.maps.event.addListener(segments[i].route,"click",selpoint) ; } /* ----------------------- connect and disconnect segments ------------------ */ function disconnect(i) { if(overviewing||i<0||i>=segments.length-1||segments[i].dots==null) return ; segments[i].dots.setMap(null) ; if(segments[i].dothandler!=null) { google.maps.event.removeListener(segments[i].dothandler) ; segments[i].dothandler = null ; } } function reconnect(i) { disconnect(i) ; connect(i) ; } function connect(i) { if(overviewing||i<0||i>=segments.length-1) return ; var opos = segments[i].data[segments[i].data.length-1].pos ; var npos = segments[i+1].data[0].pos ; if(opos.equals(npos)) return ; segments[i].dots = new google.maps.Polyline(new dotpath(opos,npos)) ; segments[i].dots.setMap(map) ; segments[i].dothandler = google.maps.event.addListener(segments[i].dots,"click",selpoint) ; } function redrawconnect(s0,s1) { redraw(s0) ; if(s1==0) reconnect(s0-1) ; if(s1=segments[s0].data.length-1) reconnect(s0) ; } /* ---------------------- draw the selection point -------------------------- */ // note: there's no point in allowing clicking on a marker because the // event position is always the marker position rather than the click position function drawsel(opt,selection) { if(selection!=undefined) selected = selection ; var ind,clen,s0=selected[0],s1=selected[1],pos=segments[s0].data[s1].pos ; if(opt) reprofile() ; clen = segments[s0].data.length ; if(clen==1) arrow.rotation = 90 ; else { if(s1==clen-1) ind = s1-1 ; else ind = s1 ; icons.arrow.rotation = bearing(segments[s0].data[ind].pos,segments[s0].data[ind+1].pos) ; } if(sel.marker==null) sel.marker = new google.maps.Marker ({ position:pos, map:map, cursor:'default', icon:icons.arrow , zIndex:2 }) ; else // avoid unnecessary redraws { if(icons.arrow.rotation!=sel.orientation) sel.marker.setIcon(icons.arrow) ; if(!pos.equals(sel.marker.getPosition())) sel.marker.setPosition(pos) ; } sel.orientation = icons.arrow.rotation ; drawxcur(pro,selected) ; blackout(penbtn) ; if(segments.length>1&&unambig()) blackout(binbtn) ; else greyout(binbtn) ; if(s1!=0&&s1!=clen-1) blackout(scissorsbtn) ; else greyout(scissorsbtn) ; } /* ------------- selclick: respond to click of cursor button --------------- */ function selclick() { mouseopt = 1-mouseopt ; infowindow.close() ; if(mouseopt) { map.setOptions({draggable:false, draggableCursor:'default'}) ; if(overviewing==0) cursorbtn.btn.setAttribute('src','hand.png') ; clickhandle = google.maps.event.addListener(map,"click",selpoint) ; } else { map.setOptions({draggable:true, draggableCursor:''}) ; if(overviewing==0) cursorbtn.btn.setAttribute('src','arrow.png') ; google.maps.event.removeListener(clickhandle) ; } } /* -------------------------------------------------------------------------- */ function genhead(uri,key) { if(uri==undefined||uri==null) resuri = 'http://www.masterlyinactivity.com/routemaster/resources/' ; else { resuri = uri + '/' ; document.write('<script src="http://maps.google.com/maps/api/js?' + ((key==null||key==undefined)?'':('key='+key+'&')) + 'libraries=geometry"></scr' + 'ipt>') ; } document.write ('<script src="' + resuri + 'dms.js"></scr' + 'ipt>' + '<script src="' + resuri + 'vector3d.js"></scr' + 'ipt>' + '<script src="' + resuri + 'latlon-ellipsoidal.js"></scr' + 'ipt>' + '<script src="' + resuri + 'utm.js"></scr' + 'ipt>' + '<script src="' + resuri + 'osgridref.js"></scr' + 'ipt>' + '<script src="' + resuri + 'FileSaver.js"></scr' + 'ipt>' + '<script src="' + resuri + 'Blob.js"></scr' + 'ipt>' + '<script src="' + resuri + 'tcxlib.js"></scr' + 'ipt>' + '<script src="' + resuri + 'routemasterlib.js"></scr' + 'ipt>' + '<script src="http://www.masterlyinactivity.com/pixlib.js"></scr' + 'ipt>' + '<style type="text/css">html, body {width: 100%; height: 100%}' + 'body {margin:0px}a:link{color:#66aaaa}' + 'a:visited{color:#cc3388}a:active{color:#404040}</style>' + '</style><title>Routemaster</title>' + '<link rel="shortcut icon" href=' + resuri + 'bus.gif>' ) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------------------------------------------------- */ /* FUNCTIONS TO GENERATE THE INITIAL MAP */ /* -------------------------------------------------------------------------- */ function genpage() { var thispage=document.URL,xhttp,quotind,plusind,listxhttp,div ; imginfo = new listinfo() ; imgdiv = null ; elevator = new google.maps.ElevationService ; window.onload = function() { window.addEventListener("beforeunload",function(e) { var msg = unsavedmsg(0) ; if(msg==null) return undefined ; (e || window.event).returnValue = msg ; //Gecko + IE return msg ; //Gecko + Webkit, Safari, Chrome etc. (msg is ignored by ffx) } ) ; } ; body = document.getElementsByTagName("body")[0] ; while(body.childNodes.length>0) body.removeChild(body.childNodes[body.childNodes.length-1]) ; mapdiv = document.createElement('div') ; mapdiv.setAttribute('id','map') ; mapdiv.setAttribute('style','width:100%;height:100%;position:absolute') ; body.appendChild(mapdiv) ; if((quotind=thispage.indexOf('?'))>=0) { thispage = thispage.substring(quotind+1) ; if((plusind=thispage.indexOf('+'))>0) { getlist(thispage.substring(plusind+1),'uri') ; thispage = thispage.substring(0,plusind) ; } else if(thispage.substring(thispage.length-3)=='.js') { getlist(thispage,'uri') ; mapdiv.appendChild(filedialogue(0)) ; return ; } xhttp = new XMLHttpRequest() ; xhttp.onreadystatechange = function() { if(xhttp.readyState==4) { if(xhttp.status==200) { var x = parser.parseFromString(xhttp.responseText,"application/xml") ; render(x,thispage,0,'uri') ; } else alert("Unable to read "+thispage+": error code "+xhttp.status) ; } } xhttp.open("GET",thispage,true) ; xhttp.send() ; } else { mapdiv.appendChild(filedialogue(0)) ; div = blurbdiv(resuri) ; div.setAttribute('style','font-family:helvetica;margin:4px;'+ 'border-top:solid 1px silver;padding-top:2px') ; mapdiv.appendChild(div) ; div = helpdiv(resuri,1) ; div.setAttribute('style','font-family:helvetica;margin:4px;font-size:90%') ; mapdiv.appendChild(div) ; } } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------- getlist --------------------------------- */ function getlist(uri,imgtype) { var xhttp = new XMLHttpRequest(),list=null,sizes=null,pixpage=null,i,r ; var imagedir=null,thumbshape = [] ; imginfo.status = 'waiting' ; imginfo.type = imgtype ; xhttp.onreadystatechange = function() { if(xhttp.readyState==4) { if(xhttp.status!=200) { alert("Unable to read "+uri+": error code "+xhttp.status) ; return ; } eval(xhttp.responseText) ; imginfo.uri = uri ; imginfo.list = list ; imginfo.sizes = sizes ; imginfo.pixpage = reluri(uri,pixpage) ; imginfo.thumbind = thumbind(sizes) ; setthumbshape(list,sizes,thumbshape,reluri(uri,imagedir)) ; for(i=0;i<list.length;i++) if(list[i].retpage!=undefined) list[i].retpage = reluri(uri,list[i].retpage) ; imginfo.status = 'ready' ; } } xhttp.open("GET",uri,true) ; xhttp.send() ; } /* ----------------------------- file dialogue ------------------------------ */ function filedialogue(overwrite) { var input = document.createElement('input') ; var para = document.createElement('p') ; para.appendChild(document.createTextNode ('\u00a0\u00a0\u00a0Select TCX/GPX file: ')) ; input.setAttribute('type','file') ; input.setAttribute('accept','.tcx,.gpx') ; input.addEventListener('change',function(e) { reader = new FileReader() ; reader.onload = function(e) { var xmldoc = parser.parseFromString(reader.result,"application/xml") ; render(xmldoc,input.files[0].name,overwrite,'file') ; } reader.readAsText(input.files[0]) ; } ) ; para.appendChild(input) ; return para ; } /* -------------------------------------------------------------------------- */
/*page*/ /* --------------------- set up the map and buttons ------------------------- */ function render(xmldoc,filename,overwrite,origin) { var i,opts,centre,lat,lon,minlon,maxlon,minlat,maxlat,newseg,s0 ; var colours,segno,istcx ; infowindow.close() ; document.onkeydown = keystroke ; xmlfile = filename ; // read data i = filename.length ; if(filename.substring(i-4,i).toLowerCase()=='.tcx') istcx = 1 ; else if(filename.substring(i-4,i).toLowerCase()=='.gpx') istcx = 0 ; else { alert(filename+' is neither .tcx nor .gcx') ; throw '' ; } if(istcx) newseg = readtcx(xmldoc) ; else newseg = readgpx(xmldoc) ; if(newseg.segments.length==0||newseg.segments[0].length==0) { alert('no data returned') ; return ; } if(newseg.segments.length>1&&setbtn!=null) { alert('trying to add a multitrack overview... not permitted') ; return ; } if(newseg.segments.length==1) for(i=0;i<newseg.segments[0].length;i++) if(newseg.segments[0][i].h==null) { alert(newseg.segments[0][i].pos+' has no altitude... unable to proceed') ; return ; } // check and process photo list if(imginfo.type!='uri') for(i=0;i<newseg.segments.length;i++) if(newseg.props[i].list!=null) { if(imginfo.uri!=null&&newseg.props[i].list!=imginfo.uri) { alert('inconsistent photo lists: ' + imginfo.uri + ' and ' + newseg.props[i].list) ; return ; } imginfo = new listinfo() ; getlist(newseg.props[i].list,'tcx') ; } if(overwrite) { for(i=0;i<segments.length;i++) obliterate(i) ; unprofile() ; if(sel.marker!=null) sel.marker.setMap(null) ; segments = [] ; if(imginfo.type=='tcx') imginfo = new listinfo() ; } s0 = segments.length ; if(s0==0) { sel = { marker:null, orientation: null } ; pending = [] ; xpending = [] ; actions = [] ; unsavedchanges = [] ; nactions = dragging = 0 ; loadno = -1 ; pro = routetitle = null ; } if(newseg.segments.length>1) { overviewing = 1 ; colours = gencolours(newseg.segments.length) ; } if(istcx&&routetitle==null&&newseg.title!=null) settitle(newseg.title) ; // process the new segments for(i=0;i<newseg.segments.length;i++) { newseg.props[i].source = [ filename , origin ] ; if(longtitle==null) longtitle = newseg.props[i].longtitle ; if(overview==null) overview = newseg.props[i].overview ; if(routetitle==null||routetitle=='Untitled Route') if(newseg.props[i].title!=null) settitle(newseg.props[i].title) ; segments.push(new genseg(newseg.segments[i],newseg.props[i])) ; if(overviewing) segments[segments.length-1].colour = colours[i] ; actions[nactions++] = [ 'load',s0+i,newseg.segments[i].slice(),loadno,newseg.props[i] ] ; loadno = nactions-1 ; } if(routetitle==null) settitle('Untitled Route') ; if(!newseg.props[0].optim.already&&!overviewing) optimaction(segments.length-1,defparms,0) ; // find max and min lat and long - have to look at all segs, not just // newly loaded to avoid google's repeatedly adding a margin for(maxlat=null,segno=0;segno<segments.length;segno++) for(i=0;i<segments[segno].data.length;i++) { lat = segments[segno].data[i].pos.lat() ; lon = segments[segno].data[i].pos.lng() ; if(maxlat==null||lon<minlon) minlon = lon ; if(maxlat==null||lon>maxlon) maxlon = lon ; if(maxlat==null||lat<minlat) minlat = lat ; if(maxlat==null||lat>maxlat) maxlat = lat ; } if(s0==0) centre = new google.maps.LatLng((minlat+maxlat)/2,(minlon+maxlon)/2) ; if(map==null) // all this only done on first call { opts = { zoom: 22, center: centre, scaleControl: true, rotateControl: false, streetViewControl: false, mapTypeId: google.maps.MapTypeId.TERRAIN, disableDoubleClickZoom: true, styles: [ { "featureType": "poi", "stylers": [{ "visibility": "off" }] } ], mapTypeControl:true, mapTypeControlOptions: { style:google.maps.MapTypeControlStyle.HORIZONTAL_BAR }, mapTypeIds: [ google.maps.MapTypeId.ROADMAP, google.maps.MapTypeId.TERRAIN, google.maps.MapTypeId.SATELLITE ] } ; map = new google.maps.Map(mapdiv,opts) ; // set up buttons if(overviewing==0) { setbtn = genbutton('settings') ; cursorbtn = genbutton('cursor') ; cursorbtn.ui.addEventListener('click',selclick) ; scissorsbtn = genbutton('scissors') ; binbtn = genbutton('bin') ; penbtn = genbutton('pen') ; undobtn = genbutton('undo') ; redobtn = genbutton('redo') ; dlbtn = genbutton('dl') ; } selclick() ; } map.fitBounds(new google.maps.LatLngBounds (new google.maps.LatLng(minlat,minlon), new google.maps.LatLng(maxlat,maxlon))) ; for(segno=s0;segno<segments.length;segno++) for(i=0;i<segments[segno].data.length;i++) segments[segno].data[i].setmap(map) ; if(nactions>1) donesomething() ; // specifically, done loading & optimisation else actions.length = nactions ; // load with no optimisation hence no undo if(s0==0) { selected = [0,0] ; if(overviewing==0) drawsel(1) ; } else greyout(dlbtn) ; for(segno=s0;segno<segments.length;segno++) { draw(segno) ; connect(segno-1) ; } connect(segments.length-1) ; reprofile() ; } /* ------------------------------- settitle --------------------------------- */ function settitle(newtitle) { routetitle = newtitle ; var h = document.getElementsByTagName('title')[0] ; while(h.firstChild) h.removeChild(h.firstChild) ; h.appendChild(document.createTextNode(routetitle)) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ------------------------------- retitle ---------------------------------- */ function retitle() { infowindow.close() ; var response = window.prompt("Modify title: (max 15 chars)",routetitle) ; if(response==null) return ; else response = response.substring(0,15) ; if(response==routetitle) return ; settitle(response) ; actions[nactions++] = [ 'edittitle' , routetitle , response ] ; donesomething() ; } /* ----------------------------- longretitle -------------------------------- */ function longretitle() { var response ; infowindow.close() ; if(longtitle==null) response = window.prompt("Add long title:","") ; else response = window.prompt("Modify long title:",longtitle) ; if(response==null||response==longtitle) return ; longtitle = response ; actions[nactions++] = [ 'editlongtitle' , longtitle , response ] ; donesomething() ; } /* ------------------------------- genbutton -------------------------------- */ function genbutton(name) { var u,v,w,b,g,k,h,div=document.createElement('div'),act ; u = document.createElement('div') ; u.style.backgroundColor = '#ffffff' ; u.style.border = '2px solid #ffffff' ; u.style.borderRadius = '3px' ; u.style.boxShadow = '0 2px 6px rgba(0,0,0,.3)' ; if(name=='dl'||name=='settings'||name=='cursor') u.style.cursor = 'pointer' ; else u.style.cursor = 'default' ; u.style.marginBottom = '12px' ; if(name!='dl') u.style.marginRight = '4px' ; u.style.textAlign = 'center' ; div.appendChild(u) ; if(name=='scissors') { h = snip ; div.index = 3 ; } else if(name=='bin') { h = discard ; div.index = 4 ; } else if(name=='pen') { h = labelprompt ; div.index = 5 ; } else if(name=='undo') { h = undo ; div.index = 6 ; } else if(name=='redo') { h = redo ; div.index = 7 ; } else if(name=='dl') { h = function() { dl(0) ; } ; div.index = 8 ; } else if(name=='settings') { h = popup ; div.index = 1 ; } else if(name=='cursor') { h = null ; div.index = 2 ; } g = greybtn(resuri,name) ; k = blackbtn(resuri,name) ; if(name=='dl'||name=='settings'||name=='cursor') b = buttonimg(k) ; else b = buttonimg(g) ; u.appendChild(b) ; map.controls[google.maps.ControlPosition.BOTTOM_CENTER].push(div) ; if(name=='dl'||name=='settings') u.addEventListener('click',h) ; if(name=='dl'||name=='settings') act = 1 ; else act = 0 ; return { ui:u , btn:b , active:act , greyimg:g , blackimg:k , handler:h } ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------------------------------------------------- */ /* OPTIMISATION */ /* -------------------------------------------------------------------------- */ function optimaction(segno,parms,force) { var s = segments[segno], result = optimise(s.data,parms) ; var ndel = s.data.length - result.length ; if((force==0&&ndel<s.data.length/2)||ndel==0) return 0 ; actions[loadno][4].optim.ndel = ndel ; actions[nactions++] = [ 'optimise' , segno , parms ] ; segments[segno] = new genseg(result,segments[segno].props) ; actions[loadno][4].optim.parms = { tol: parms.tol , maxsep: parms.maxsep , wppenalty: parms.wppenalty , vweight: parms.vweight } ; return 1 ; } /* -------------------------------------------------------------------------- */ function optimprompt() { var msg = 'Enter 4 optimisation parms (tol, maxsep, wppenalty and vweight)' ; var parmstr = defparms.tol + ' ' + defparms.maxsep.toFixed(0) + ' ' + defparms.wppenalty.toFixed(0) + ' ' + defparms.vweight.toFixed(1) ; var parms,i ; infowindow.close() ; for(i=0;;i++) { newparms = prompt(msg,parmstr) ; if(newparms==null) return ; if(newparms=='') { parms = defparms ; break ; } newparms = newparms.split(' ') ; if(newparms.length==0) { parms = defparms ; break ; } parms = { tol: parseFloat(newparms[0]) , maxsep: parseFloat(newparms[1]) , wppenalty: parseFloat(newparms[2]) , vweight: parseFloat(newparms[3]) } ; if( isNaN(parms.tol)==0 && isNaN(parms.maxsep)==0 && isNaN(parms.wppenalty)==0 && isNaN(parms.vweight)==0 ) break ; if(i==0) msg = '*** Illegal parms ***\n' + msg ; } if(optimaction(segments.length-1,parms,1)) { donesomething() ; draw(segments.length-1) ; } routeinfo() ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------------------------------------------------- */ /* THE MAIN POPUP MENU AND THE FUNCTIONS IT GOVERNS */ /* -------------------------------------------------------------------------- */ function popup() { var opts,pos,s0=selected[0],s1=selected[1] ; infowindow.close() ; if(dragging) { opts = finalbox + "Hit [return] when you've finished dragging.</div>" ; infowindow.open(opts,getbtnpos(0),'settings') ; return ; } // route options opts = textbox + active + '"routeinfo()">Route info</span><br>' ; if(pro==null||pro.prodiv==null) opts += active + '"drawprofile()">Show altitude profile</span><br>' ; else opts += active + '"unprofile()">Hide altitude profile</span><br>' ; opts += active + '"addload(1)">Load new route</span><br>' ; if(overview!=null) opts += '<a style="cursor:pointer;color:#0000bd;text-decoration:none" '+ 'href="' + overview + '" target="_blank">' + 'View routes ' + 'overview</a>'+neutral+' (opens in new tab/window)</span><br> ' ; opts += active + '"dl(1)">Download track as overview</span></div>' ; // segment options opts += textbox + active + '"seginfo()">Segment info</span><br>' ; if(unambig()) opts += active + '"revseg()">' ; else opts += inactive ; opts += 'Reverse segment</span><br>' ; opts += active + '"manualcal()">Calibrate segment altitudes</span><br>' ; opts += active + '"addload(0)">Load route as a new segment</span></div>' ; // waypoint options opts += textbox + active + '"wpinfo()">Waypoint info</span><br>' ; if(segments[selected[0]].data.length>1) opts += active + '"wpdel()">' ; else opts += inactive ; opts += 'Delete waypoint</span><br>' ; opts += active + '"draggit(0)">Make waypoint draggable</span><br>' ; opts += active + '"inswp(1)">Insert draggable waypoint ahead</span><br>' ; opts += active + '"inswp(-1)">Insert draggable waypoint behind</span>' ; opts += '</div>'+finalbox+active ; // tool options if(querycanfullscreen()) { if(queryfullscreen()==0) opts += '"enterFullscreen()">Enter full screen</span><br>' + active ; else opts += '"exitFullscreen()">Leave full screen</span><br>' + active ; } opts += '"help()">Help</span></div>' ; infowindow.open(opts,getbtnpos(0),'settings') ; } /* ------------------------------- calwork --------------------------------- */ function calwork(s0,y) { var i,s1 ; for(s1=0;s1<segments[s0].data.length;s1++) if(segments[s0].data[s1].h!=null) segments[s0].data[s1].h += y ; reprofile() ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ------------------------------ manualcal --------------------------------- */ function manualcal() { infowindow.close() ; var x,y,s0=selected[0] ; x = prompt('Enter offset in metres to add to altidudes:') ; if(x==null) return ; y = parseFloat(x) ; if(isNaN(y)) { alert(x+' is not a number') ; return ; } calwork(s0,y) ; done(['recal',s0,y]) ; } /* --------------------------------- help ----------------------------------- */ function help() { infowindow.close() ; infowindow.open(helpdiv(resuri),getbtnpos(0),'help') ; } /* --------------------------------- wpdel ---------------------------------- */ function wpdelwork(s0,s1) { var i,response=segments[s0].data[s1],clen=segments[s0].data.length ; response.setmap(null) ; for(i=s1;i<clen-1;i++) segments[s0].data[i] = segments[s0].data[i+1] ; segments[s0].data.length = clen-1 ; selected = [s0,s1] ; if(s1==segments[s0].data.length) selected[1] -= 1 ; redrawconnect(s0,s1) ; drawsel(1) ; return response ; } function wpdel() { var s0=selected[0],s1=selected[1],i ; infowindow.close() ; done(['wpdel',s0,s1,wpdelwork(s0,s1)]) ; } /* --------------------------------- revseg --------------------------------- */ function revsegwork(s0) { var i,s=segments[s0],j,x,len=s.data.length ; disconnect(s0-1) ; disconnect(s0) ; for(i=0;i<len/2;i++) { j = (len-1) - i ; x = s.data[i] ; s.data[i] = s.data[j] ; s.data[j] = x ; } for(i=0;i<s.data.length;i++) if(s.data[i].type=='Right') s.data[i].settype('Left') ; else if(s.data[i].type=='Left') s.data[i].settype('Right') ; if(s0==selected[0]) selected[1] = (len-1) - selected[1] ; connect(s0-1) ; connect(s0) ; drawsel(1) ; } /* -------------------------------------------------------------------------- */ function revseg() { infowindow.close() ; revsegwork(selected[0]) ; done(['revseg',selected[0]]) ; } /* -------------------------------------------------------------------------- */ function addload(overwrite) { var msg ; infowindow.close() ; if(overwrite) { msg = unsavedmsg(1) ; if(msg!=null) if(!confirm(msg)) return ; } infowindow.open(filedialogue(overwrite),getbtnpos(0),'addload') ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------------------------------------------------- */ /* DRAGGING A (POSSIBLY NEWLY INSERTED) WAYPOINT IS QUITE A LOT OF WORK */ /* -------------------------------------------------------------------------- */ function insert(s0,s1,n) { var i ; for(i=segments[s0].data.length+n-1;i>s1;i--) segments[s0].data[i] = segments[s0].data[i-n] ; for(i=0;i<n;i++) segments[s0].data[s1+i] = new datatype(null,null) ; } /* --------------------------------- inswp ---------------------------------- */ function inswp(dir) { var s0=selected[0],s1=selected[1],bounds,del,pos,data=segments[s0].data ; var len = data.length ; if(len==1) pos = data[0].pos ; if(dir>=0) s1 = selected[1] += 1 ; insert(s0,s1,1) ; if(len==1) { bounds = map.getBounds() ; del = bounds.getNorthEast().lng() - bounds.getSouthWest().lng() ; pos = new google.maps.LatLng(pos.lat(),pos.lng()+dir*del/10) ; } else if(s1==0) pos = interp(data[2].pos,data[1].pos,1.5) ; else if(s1<len) pos = interp(data[s1-1].pos,data[s1+1].pos,0.5) ; else pos = interp(data[s1-2].pos,data[s1-1].pos,1.5) ; data[s1].setpos(pos) ; draggit(1) ; } /* -------------------------------- draggit --------------------------------- */ // draggit makes the current waypoint draggable var l1,l2,startpos,seg0,seg1,seg2,colour,inserted ; function draggit(insparm) { var s0=selected[0],s1=selected[1],start,end,i,len=segments[s0].data.length ; startpos = segments[s0].data[s1].pos ; inserted = insparm ; infowindow.close() ; greyout(scissorsbtn) ; greyout(binbtn) ; greyout(penbtn) ; greyout(undobtn) ; greyout(redobtn) ; greyout(dlbtn) ; map.panToBounds(new google.maps.LatLngBounds(startpos,startpos)) ; sel.marker.setMap(null) ; sel.marker = new google.maps.Marker( { position: segments[s0].data[s1].pos, map: map, cursor: 'default', icon: icons.concircle , draggable: true , zIndex: 2 } ) ; if(s0&1) colour = "#ff9999" ; else colour = "#ff0000" ; segments[s0].route.setMap(null) ; if(segments[s0].clickhandler!=null) { google.maps.event.removeListener(segments[s0].clickhandler) ; segments[s0].clickhandler = null ; } seg0 = seg2 = null; if(s1>1) { seg0 = new google.maps.Polyline(new linepath(s0,0,s1,colour)) ; seg0.setMap(map) ; } if(s1==0) start = 0 ; else start = s1-1 ; if(s1==len-1) end = s1+1 ; else end = s1+2 ; seg1 = new google.maps.Polyline(new linepath(s0,start,end,colour)) ; seg1.setMap(map) ; if(s1<segments[s0].data.length-2) { seg2 = new google.maps.Polyline(new linepath(s0,s1+1,len,colour)) ; seg2.setMap(map) ; } l1 = google.maps.event.addListener(sel.marker,'drag',function() { segments[s0].data[s1].setpos(this.getPosition()) ; seg1.setMap(null) ; seg1 = new google.maps.Polyline(new linepath(s0,start,end,colour)) ; seg1.setMap(map) ; if(s1==0&&s0>0) { disconnect(s0-1) ; connect(s0-1) ; } if(s1==len-1&&s0<segments.length-1) { disconnect(s0) ; connect(s0) ; } } ) ; dragging = 1 ; } /* ------------------------------- undraggit -------------------------------- */ // undraggit is invoked by [return] to terminate waypoint dragging function undraggit() { var s0=selected[0],s1=selected[1],i,s1dash,pos=segments[s0].data[s1].pos ; var xpos ; google.maps.event.removeListener(l1) ; dragging = 0 ; if(seg0!=null) seg0.setMap(null) ; seg1.setMap(null) ; if(seg2!=null) seg2.setMap(null) ; segments[s0].route = new google.maps.Polyline(new linepath(s0,-1,0,colour)) ; segments[s0].route.setMap(map) ; segments[s0].data[s1].h = null ; lookupalt(s0,s1) ; sel.marker.setMap(null) ; sel.marker = null ; // force a redraw drawsel(1) ; if(inserted||dist(startpos,pos)>5) done(['move',s0,s1,startpos,pos,inserted]) ; if(segments.length==1) blackout(dlbtn) ; } /* -------------------------------------------------------------------------- */ function seginfo() { var pos = segments[selected[0]].data[selected[1]].pos ; infowindow.close() ; infowindow.open(seginfodiv(segments,selected[0]),pos,'seginfo') ; } /* -------------------------------------------------------------------------- */ function deltimes() { var s0,s1,task=[] ; for(s0=0;s0<segments.length;s0++) for(s1=0;s1<segments[s0].data.length;s1++) if(segments[s0].data[s1].t!=null) { task.push([s0,s1,segments[s0].data[s1].t]) ; segments[s0].data[s1].t = null ; } infowindow.close() ; done(['deltimes',task]) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------------------------------------------------- */ /* CODE TO GET ALTITUDES FOR NEWLY INSERTED POINTS */ /* -------------------------------------------------------------------------- */ function lookupalt(s0,s1) // set up a request for the alt of the new point { var hi,lo,segno=s0,ptno=s1,lopos,hipos,datum=segments[s0].data[s1] ; for(lo=null,s0=segno,s1=ptno-1;lo==null;s1--) { if(s1<0) { s0 -= 1 ; if(s0<0) break ; s1 = segments[s0].data.length-1 ; } if(segments[s0].data[s1].h!=null) lo = [s0,s1] ; } for(hi=null,s0=segno,s1=ptno+1;hi==null;s1++) { if(s1==segments[s0].data.length) { s0 += 1 ; if(s0==segments.length) break ; s1 = 0 ; } if(segments[s0].data[s1].h!=null) hi = [s0,s1] ; } if(lo==null&&hi==null) { alert('no points left with altitudes: unable to proceed') ; throw '' ; } if(lo!=null) lopos = segments[lo[0]].data[lo[1]].pos ; if(hi!=null) hipos = segments[hi[0]].data[hi[1]].pos ; if(lo==null||(hi!=null&&dist(lopos,datum.pos)>dist(hipos,datum.pos))) { lo = hi ; lopos = hipos ; } pending.push([datum,lopos,segments[lo[0]].data[lo[1]].h]) ; elevator.getElevationForLocations({locations:[datum.pos,lopos]},calibrate) ; } /* -------------------------------------------------------------------------- */ // pending is the list of inserted points for which google altitudes are needed // note a pitfall with the elevation service - it's hard to tell which // response corresponds to which request: the coordinates may not match // because google truncates to 0.00001 deg; hence the use of the dist function. function calibrate(results,status) { var pno,flag ; if(status===google.maps.ElevationStatus.OVER_QUERY_LIMIT) alert('No calibration data available: over Google query limit') ; else if(status!==google.maps.ElevationStatus.OK||results.length!=2) alert('Calibration error') ; // now find whether any of our elevation results allow us to fix an altitude for(pno=0;pno<pending.length;pno++) if( dist(pending[pno][0].pos,results[0].location)<5 && dist(pending[pno][1],results[1].location)<5 ) { if(pno>0) alert('Warning: Google elevation results out of sequence') ; diff = results[0].elevation - results[1].elevation ; pending[pno][0].h = pending[pno][2] + diff ; } for(i=pno=0;pno<pending.length;pno++) if(pending[pno][0].h==null) { if(pno!=i) pending[i] = pending[pno] ; i += 1 ; } pending.length = i ; } /* -------------------------------------------------------------------------- */ /* FUNCTIONS FOR HANDLING THE ALTITUDE PROFILE */ /* -------------------------------------------------------------------------- */ function drawprofile() { infowindow.close() ; if((pro=procoords(segments))==null) return ; drawpro(pro) ; body.appendChild(pro.prodiv) ; body.appendChild(pro.curdiv) ; drawxcur(pro,selected) ; } /* ------------------------------- unprofile -------------------------------- */ function unprofile() { var i,match,node ; infowindow.close() ; if(pro==null||pro.prodiv==null) return ; pro.curdiv.removeEventListener('click',pro.curhandle) ; for(match=0,i=body.childNodes.length-1;match==0&&i>=0;i--) { node = body.childNodes[i] ; match = (node==pro.prodiv) ; body.removeChild(node) ; } pro = null ; } function reprofile() { if(pro!=null&&pro.prodiv!=null) { unprofile() ; drawprofile() ; } } /* -------------------------------------------------------------------------- */ /* ROUTEINFO IS A MENU WHICH ITSELF SUPPORTS THE COMBINE FUNCTION */ /* -------------------------------------------------------------------------- */ function routeinfo() { var s0,s1,s,d,asc,des,oalt,alt,nlabels,nowpts,i,unsaved,props,spacing,npix ; var maxsep,sep,outoforder,tlast,otime,time,tdist,ttime,ntimes ; infowindow.close() ; props = actions[loadno][4] ; tlast = null ; tdist = ttime = outoforder = 0 ; maxsep = nlabels = npix = des = asc = d = nowpts = ntimes = 0 ; for(s0=0;s0<segments.length;s0++) { nowpts += segments[s0].data.length ; for(oalt=null,s1=0;s1<segments[s0].data.length;otime=time,s1++) { if((alt=segments[s0].data[s1].h)!=null) { if(oalt!=null) { if(alt>oalt) asc += alt-oalt ; else des += oalt - alt ; } oalt = alt ; } if(segments[s0].data[s1].type!=null) nlabels += 1 ; npix += segments[s0].data[s1].photo.length ; time = segments[s0].data[s1].t ; if(time!=null) { time = time.getTime() ; ntimes += 1 ; } if(tlast!=null&&time!=null&&time<tlast) outoforder = 1 ; // out of order if(time!=null) tlast = time ; if(s1) { sep = dist(segments[s0].data[s1-1].pos,segments[s0].data[s1].pos) ; d += sep ; if(sep>maxsep) maxsep = sep ; if(time!=null&&otime!=null) { tdist += sep/1000 ; ttime += (time-otime)/(3600*1000) ; } } } } s = finalbox +'<nobr>Title: <b>'+routetitle+'</b> [' ; s += active + '"retitle()">Edit</span>' + ']</nobr><br>' ; if(longtitle==null) s += '<nobr>[' + active + '"longretitle()">' + 'Add long title</span>]</nobr><br>' ; else { if(longtitle.length<50) s += '<nobr>Long title: <b>' + longtitle + '</b>' ; else s += '<div style="margin-bottom:2px;border-bottom:solid 1px silver;' + 'padding-bottom:2px">Long title: ' + longtitle ; s += ' [' + active + '"longretitle()">Edit</span>]' ; if(longtitle.length<50) s += '</nobr><br>' ; else s += '</div>' ; } if(loadno>0) { s += '<nobr>&nbsp;&nbsp;&nbsp;Last added route' ; if(props.title!=null) s += ' (' + props.title + ')' ; s += ':</nobr><br><nobr>&nbsp;&nbsp;&nbsp;' ; } else s += '<nobr>' ; s += 'Track points on input: ' + props.inputlen ; if(props.optim.already) { s += ' (previously optimised)</nobr>' ; if(nowpts!=props.inputlen) s += '<br>Now ' + nowpts + ' track points' ; } else if(props.optim.ndel==0) { if(nactions==loadno+1) s += ' [' + active + '"optimprompt()">' ; else s += ' [' + inactive ; s += 'Optimise' + neutral + ']</span></nobr>' ; } else s += ', optimised to ' + (props.inputlen-props.optim.ndel) + '</nobr>' ; if(!props.optim.already&&props.inputlen-props.optim.ndel!=nowpts) s += '<br>Now ' + nowpts + ' track points' ; s += '<br>' ; if(outoforder==0) { if(ntimes==0) s += 'No timings provided<br>' ; else { s += '<nobr>' ; if(ntimes<nowpts) s += (nowpts-ntimes) + ' points have no associated timings ' ; s += '[' + active + '"deltimes()">Discard timings</span>' + neutral + ']<nobr><br>' ; } if(tdist>0&&ttime>0) s += 'Average speed = ' + (tdist/ttime).toFixed(1) + ' km/hr<br>' ; } else s += 'Times are out of sequence (will be discarded on download)<br>' ; if(nlabels>0) s += nlabels + ' labelled course point' + (nlabels>1?'s':'') + '<br>' ; if(npix>0) s += npix + ' photo' + (npix>1?'s':'') + '<br>' ; unsaved = unsavedchanges.length ; if(unsaved>0) s += unsaved + ' unsaved change' + (unsaved>1?'s':'') + '<br>' ; if(segments.length>1) s += segments.length + ' segments [' + active + '"combine()">Combine</span>' + neutral + ']<br>' + '<i>Note that segments must be combined before saving</i><br>' ; s += 'Max waypoint separation: '+maxsep.toFixed(0)+'m<br>' ; if(maxsep>=100) s += '<i>Note that separations &gt;100m are illegal on Garmin</i><br>' + '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[</span>' + active + '"extrapts()">' + 'Interpolate extra points</span>]<br>' + neutral ; s += 'Total distance: '+(d/1000).toFixed(3)+'km<br>' ; s += 'Total ascent: '+asc.toFixed(0)+'m<br>' ; s += 'Total descent: '+des.toFixed(0)+'m</div>' ; s += '</div>' ; infowindow.open(s,getbtnpos(0),'routeinfo') ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ------------------------- interpolate extra points ----------------------- */ function extrapts(opt) { var s0,s1,sep,data,n,opos,npos,i,lambda,lox,nlox,taskno,ind ; var task = [ 'extra' , selected[0] , selected[1] ] ; infowindow.close() ; for(nlox=s0=0;s0<segments.length;s0++) for(data=segments[s0].data,s1=1;s1<data.length;s1++) if((sep=dist(opos=data[s1-1].pos,npos=data[s1].pos))>100) { n = Math.floor(sep/95) ; insert(s0,s1,n) ; for(i=0;i<n;i++) { lambda = (i+1) / (n+1) ; data[s1+i].setpos(new google.maps. LatLng(lambda*npos.lat()+(1-lambda)*opos.lat(), lambda*npos.lng()+(1-lambda)*opos.lng())) ; } if(selected[0]==s0&&s1<=selected[1]) selected[1] += n ; task.push([s0,s1,data.slice(s1-1,s1+n+1)]) ; s1 += n ; nlox += n+2 ; } if(nlox==0) return ; done(task) ; xpending.push(task) ; lox = new Array(nlox) ; for(ind=0,taskno=3;taskno<task.length;taskno++) { n = task[taskno][2].length ; for(i=0;i<n;i++) lox[ind++] = task[taskno][2][i].pos ; } elevator.getElevationForLocations( {locations:lox} , function (results,status) { // assume that the results come in sequence, ie. correspond to xpending[0] var task=xpending.shift(),taskno,d0,dn,lambda ; if(status===google.maps.ElevationStatus.OVER_QUERY_LIMIT) alert('No calibration data available: over Google query limit') ; else if(status!==google.maps.ElevationStatus.OK) alert('Calibration error') ; for(ind=0,taskno=3;taskno<task.length;taskno++,ind+=n+2) { n = task[taskno][2].length-2 ; d0 = task[taskno][2][0].h - results[ind].elevation ; dn = task[taskno][2][n+1].h - results[ind+n+1].elevation ; if( dist(task[taskno][2][0].pos,results[ind].location)>5 || dist(task[taskno][2][n+1].pos,results[ind+n+1].location)>5 ) alert('Anomaly with Google elevation results') ; for(i=0;i<n;i++) { lambda = (i+1) / (n+1) ; task[taskno][2][1+i].h = results[ind+1+i].elevation + lambda*dn + (1-lambda)*d0 ; } } } ) ; routeinfo() ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ------------------------------- combine1 --------------------------------- */ function combine1(sa,sb) { var i,ca,cb,calen,cblen,la=null,lb=null,cdup=0 ; undraw(sb) ; disconnect(sb-1) ; calen = segments[sa].data.length ; cblen = segments[sb].data.length ; cb = segments[sb].data[0].pos ; cdup = ( ca = cb.equals(segments[sa].data[calen-1].pos) ) ; if(cdup) { la = segments[sa].data[calen-1] ; lb = segments[sb].data[0] ; segments[sa].data.length = ( calen -= 1 ) ; } if(selected[0]==sb) selected = [ sa , selected[1]+calen ] ; segments[sa].data = segments[sa].data.concat(segments[sb].data) ; return [ cblen , cdup , la , lb ] ; } function combinework() { var task,s0 ; for(task=['combine',segments.length],s0=1;s0<segments.length;s0++) task.push(combine1(0,s0)) ; segments.length = 1 ; return task ; } /* -------------------------------------------------------------------------- */ function combine() { infowindow.close() ; done(combinework()) ; drawsel(1) ; redraw(0) ; blackout(dlbtn) ; } function recombine() { var s0 ; for(s0=1;s0<segments.length;s0++) { undraw(s0) ; combine1(0,s0) ;} segments.length = 1 ; drawsel(1) ; redraw(0) ; blackout(dlbtn) ; } /* -------------------------------------------------------------------------- */ // combine returns [ cblen , cdup , la , lb ] ; function uncombine(task) { var i,j,llen,flag,subtask ; for(flag=0,s0=task[1]-1,i=task.length-1;i>=2;i--,s0--) { subtask = task[i] ; cblen = subtask[0] ; cdup = subtask[1] ; llen = segments[0].data.length ; segments[s0] = { data: segments[0].data.slice(llen-cblen,llen) , route: null , clickhandler: null } ; llen = segments[0].data.length = llen+cdup-cblen ; if(cdup) { segments[0].data[llen-1] = subtask[2] ; segments[s0].data[0] = subtask[3] ; } if(flag==0&&selected[1]>=llen) { selected = [ s0 , selected[1]-llen ] ; flag = 1 ; } } drawsel(1) ; undraw(0) ; for(i=0;i<segments.length;i++) { draw(i) ; connect(i) ; } greyout(dlbtn) ; } /* -------------------------------------------------------------------------- */ /* WPINFO IS A MENU GIVING ACCESS TO THE SETALT FUNCTION */ /* -------------------------------------------------------------------------- */ function wpinfo() { infowindow.close() ; var s0=selected[0],s1=selected[1],alt,nalt,s,x,lat,lng,grad,gradstr,time ; var datum = segments[s0].data[s1] , pos = datum.pos ; lat = pos.lat() ; lng = pos.lng() ; s = finalbox ; if(lat>=0) s += lat.toFixed(5) + '\u00b0 N, ' ; else { lat = -lat ; s += lat.toFixed(5) + '\u00b0 S, ' ; } if(lng>=0) s += lng.toFixed(5) + '\u00b0 E<br>' ; else { lng = -lng ; s += lng.toFixed(5) + '\u00b0 W<br>' ; } x = new LatLon(lat,lng) ; if(lat>49.9&&lat<62&&lng>-12&&lng<2.5&&lat-1.5*lng<75) s += 'OS grid ref: ' + OsGridRef.latLonToOsGrid(x) ; else s += 'UTM coords = ' + x.toUtm() ; s += '<br>' ; alt = segments[s0].data[s1].h ; if(alt!=null) s += 'Altitude: ' + alt.toFixed(0) + 'm ' + active + '"setalt(1)">[Edit]' ; else s += active + '"setalt(0)">Set altitude' ; s += '</span><br>' ; time = segments[s0].data[s1].t ; if(time!=null&&time.getFullYear()>1980) s += 'Date: ' + time.toDateString() + '<br>' + 'Time: ' + time.toTimeString() + '<br>' ; if(datum.type!=null) s += datum.type + ': ' + datum.marker.title + ' [' + active + '"labelprompt()">Edit</span>]<br>' ; if(alt==null||s1==segments[s0].data.length-1) nalt = null ; else { nalt = segments[s0].data[s1+1].h ; if(nalt!=null) x = dist(pos,segments[s0].data[s1+1].pos) ; } if(nalt!=null&&Math.abs(nalt-alt)<x) { grad = 100*Math.asin((nalt-alt)/x) ; gradstr = Math.abs(grad).toFixed(0) ; if(gradstr=='0') s += 'Flat<br>' ; else if(grad>0) s += 'Climb '+gradstr+'%<br>' ; else s += 'Descend: '+gradstr+'%<br>' ; } s += '<span style="font-size:80%">' ; if(segments.length>1) s += 'Segment '+s0+' p' ; else s += 'P' ; s += 'oint ' + s1 + '</span></div>' ; infowindow.open(s,pos,'wpinfo') ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------- setalt ---------------------------------- */ function setalt(edit) { infowindow.close() ; var s0=selected[0],s1=selected[1],x,y=null,oldalt ; oldalt = segments[s0].data[s1].h.toFixed(0) ; if(edit) x = prompt('Enter altitude (m):',oldalt) ; else x = prompt('Enter altitude (m):') ; if(x==null) return ; if(x!=''&&isNaN(y=parseFloat(x))) { alert(x+' is not a number') ; return ; } if(y==null&&oldalt==null) return ; if(y!=null&&Math.abs(y-oldalt)<0.1) return ; segments[s0].data[s1].h = y ; done(['setalt',s0,s1,oldalt,y]) ; reprofile() ; wpinfo() ; } /* -------------------------------------------------------------------------- */ /* THE LABELS ARE ACCESSED FROM THE PEN BUTTON OR BY CLICKING ON THE MAP */ /* -------------------------------------------------------------------------- */ function labelprompt() { var oldcaption='',oldtype,type='Generic',s0=selected[0],s1=selected[1] ; var str , flag = (infowindow.close()=='wpinfo') ; var datum = segments[s0].data[s1] ; oldtype = datum.type ; if(oldtype!=null) oldcaption = datum.marker.title ; if(oldcaption==null) oldcaption = '' ; if(oldtype!=null) type = oldtype ; if(oldtype==null) str = 'Enter' ; else str = 'Modify or delete' ; var caption = window.prompt(str+' label:',oldcaption) ; if(caption==null) { if(flag) wpinfo() ; else walkto(s0,s1) ; return ; } else if(caption=='') type = null ; else caption = caption.substring(0,10) ; if(caption==oldcaption) { if(flag) wpinfo() ; else walkto(s0,s1) ; return ; } segments[s0].data[s1].setlabel(type,caption) ; done(['editlabel',s0,s1,oldcaption,caption,oldtype,type]) ; if(flag) wpinfo() ; else walkto(s0,s1) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ------------------------------ labelcycle -------------------------------- */ function labelcycle() { var s0,s1,datum,types,caption,flag=(infowindow.close()=='wpinfo') ; ; for(s0=0;s0<segments.length;s0++) for(s1=0;s1<segments[s0].data.length;s1++) { datum = segments[s0].data[s1] ; if(datum.marker!=this) continue ; types = datum.labelcycle() ; caption = datum.marker.title ; selected = [s0,s1] ; done(['editlabel',s0,s1,caption,caption,types[0],types[1]]) ; if(flag) wpinfo() ; return ; } } function photoprompt(e) { var s0=selected[0],s1=selected[1] ; if(e!=null) e.preventDefault() ; var flag = (infowindow.close()=='wpinfo') ; var datum = segments[s0].data[s1] ; var photo = window.prompt('Enter photo name:','') ; if(photo!=null&&photo!='') { done(['editphoto',s0,s1,datum.photo.length,null,photo]) ; datum.addphoto(photo) ; } if(flag) wpinfo() ; else walkto(s0,s1) ; } function photoedit(ind) { var s0=selected[0],s1=selected[1],i ; var flag = (infowindow.close()=='wpinfo') ; var datum = segments[s0].data[s1] ; var photo = window.prompt('New photo name:',datum.photo[ind]) ; if(photo!=null&&photo!='') for(i=0;i<datum.photo.length;i++) if(datum.photo[i]==photo) { photo = null ; break ; } if(photo!=null) { if(photo=='') photo = null ; done(['editphoto',s0,s1,ind,datum.photo[ind],photo]) ; datum.setphoto(ind,photo) ; } if(flag) wpinfo() ; else walkto(s0,s1) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ----------------------------- display photo ------------------------------ */ var lmove,rmove ; function advance(s0,s1,ind) { for(ind++;;ind++) { if(ind>=segments[s0].data[s1].photo.length) { ind = 0 ; s1 += 1 ; } if(s1==segments[s0].data.length) { s0 += 1 ; if(s0==segments.length) return null ; s1 = 0 ; } if(ind<segments[s0].data[s1].photo.length) return [s0,s1,ind] ; } } function retreat(s0,s1,ind) { for(ind--;;ind--) { if(ind<0) { s1 -= 1 ; ind = null ; } if(s1<0) { if(s0==0) return null ; else s0 -= 1 ; s1 = segments[s0].data.length-1 ; } if(ind==null) ind = segments[s0].data[s1].photo.length - 1 ; if(ind>=0) return [s0,s1,ind] ; } } function prev() { dodisplay(lmove[0],lmove[1],lmove[2],-1) ; } function backtogps() { document.onkeydown = keystroke ; window.removeEventListener('resize',resize) ; body.removeChild(imgdiv) ; } function next() { dodisplay(rmove[0],rmove[1],rmove[2],1) ; } function display(ind) { var phind ; document.onkeydown = imgwalk ; window.addEventListener('resize',resize) ; infowindow.close() ; imgdiv = document.createElement('div') ; imgdiv.setAttribute('style','position:fixed;width:100%;height:100%;'+ 'left:0;top:0;background:black') ; dodisplay(selected[0],selected[1],ind,1) ; body.appendChild(imgdiv) ; } function dodisplay(s0,s1,ind,dir) { var phind=findimg(segments[s0].data[s1].photo[ind]) , pre=null ; selected[0] = s0 ; selected[1] = s1 ; lmove = retreat(s0,s1,ind) ; rmove = advance(s0,s1,ind) ; if(dir<0&&lmove!=null) pre = findimg(segments[lmove[0]].data[lmove[1]].photo[lmove[2]]) ; else if(dir>=0&&rmove!=null) pre = findimg(segments[rmove[0]].data[rmove[1]].photo[rmove[2]]) ; if(pre!=null) pre = imginfo.list[pre] ; gendisplay(imgdiv,imginfo.list[findimg(segments[s0].data[s1].photo[ind])], imginfo.sizes,lmove==null?null:'javascript:prev()', 'javascript:backtogps()',rmove==null?null:'javascript:next()', 'GPS track',pre) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ------------------------------ image walk -------------------------------- */ function imgwalk(e) { e.preventDefault() ; if(e.keyCode==39) { if(rmove!=null) next() ; return ; } else if(e.keyCode==37) { if(lmove!=null) prev() ; return ; } else if(e.keyCode==40) reduce() ; else if(e.keyCode==38) enlarge() ; else if(e.keyCode==70) enterfullscreen() ; else backtogps() ; } /* --------------------------------- photo info ----------------------------- */ function phinfo(i) { infowindow.close() ; var s0=selected[0],s1=selected[1],s,shape,ind,hind,r,k ; var list=imginfo.list,sizes=imginfo.sizes ; s = finalbox + 'Name: ' + list[i].name + '<br>Title: ' + list[i].title ; for(hind=null,ind=0;ind<i;ind++) if(list[ind].name==null) hind = ind ; if(hind!=null) s += "<br>Under \u201c" + list[hind].title + "\u201d" ; // how many sizes? for(r=ind=0;ind<sizes.length;ind++) if(sizes[ind].type==undefined) r += 1 ; s += '<br>Available in ' + r + ' size' + (r>1?'s: ':': ') ; // print the sizes for(k=ind=0;ind<sizes.length;ind++) if(sizes[ind].type==undefined) { if(k>0) { if(k==r-1) s += ' and ' ; else s += ', ' ; } shape = imgsize(list[i],sizes,ind) ; s += shape[0] + 'x' + shape[1] ; k += 1 ; } shape = list[i].thumbshape ; s += '<br>Thumb: ' + shape[0] + 'x' + shape[1] ; for(ind=0;ind<sizes.length;ind++) if(sizes[ind].type=='raw') { shape = imgsize(list[i],sizes,ind) ; s += '<br>Raw: ' + shape[0] + 'x' + shape[1] ; } if(imginfo.pixpage!=null) s += '<br><a style="cursor:pointer;color:#0000bd;text-decoration:none" '+ 'href="'+imginfo.pixpage+'" target="_blank">'+ 'Full photo set</a>'+neutral+' (opens in new tab/window)</span>' ; if(list[i].retid!=null) { for(hind=null,ind=0;ind<=i;ind++) if(list[ind].retpage!=undefined&&list[ind].retpage!=null) hind = list[ind].retpage + '.html#' + list[i].retid ; if(hind!=null) s += '<br><a style="cursor:pointer;color:#0000bd;'+ 'text-decoration:none" href="' + hind + '" target="_blank">'+ 'Route notes</a>'+neutral+' (opens in new tab/window)</span>' ; } infowindow.open(s,segments[s0].data[s1].pos,'phinfo') ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ------------------------- snip: apply scissors -------------------------- */ function snipwork(s0,s1) { var i,k,newlen ; undraw(s0) ; segments.length += 1 ; for(i=segments.length-1;i>s0+1;i--) segments[i] = segments[i-1] ; newlen = segments[s0].data.length - s1 ; segments[s0+1] = new genseg(segments[s0].data.slice(s1),segments[s0].props) ; segments[s0+1].dots = segments[s0].dots ; segments[s0+1].dothandler = segments[s0].dothandler ; segments[s0].dots = segments[s0].dothandler = null ; segments[s0].data.length = s1 + 1 ; segments[s0].data[s1] = new datatype(segments[s0].data[s1].pos,segments[s0].data[s1].h) ; draw(s0) ; draw(s0+1) ; for(i=s0+2;i<segments.length;i++) recolour(i) ; drawsel(1,[s0+1,0]) ; greyout(dlbtn) ; } function snip() { var i,s0=selected[0],s1=selected[1] ; infowindow.close() ; done(['snip',s0,s1]) ; snipwork(s0,s1) ; } /* ------------------------ discard: bin a segment ------------------------- */ function binwork(s0) { var i ; obliterate(s0) ; for(i=s0;i<segments.length-1;i++) segments[i] = segments[i+1] ; segments.length -= 1 ; for(i=s0;i<segments.length;i++) recolour(i) ; connect(s0-1) ; selected[1] = 0 ; if(selected[0]==segments.length) selected[0] = 0 ; drawsel(1) ; if(segments.length==1) blackout(dlbtn) ; } function discard() { var i,s0=selected[0] ; infowindow.close() ; done(['bin',s0,segments[s0]]) ; binwork(s0) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------- actionname ------------------------------ */ function actionname(x) { if(x[0]=='bin') return 'delete segment' ; if(x[0]=='snip') return 'split segment' ; if(x[0]=='editlabel') { if(x[4]=='') return 'delete label' ; else if(x[3]=='') return 'label waypoint' ; else return 'edit label' ; } if(x[0]=='edittitle') return 'edit title' ; if(x[0]=='editlongtitle') return 'edit long title' ; if(x[0]=='wpdel') return 'delete waypoint' ; if(x[0]=='move') { if(x[5]) return 'insert waypoint' ; else return 'drag waypoint' ; } if(x[0]=='recal') return 'recalibrate altitudes' ; if(x[0]=='setalt') return 'set waypoint altitude' ; if(x[0]=='resign') return 'change label symbol' ; if(x[0]=='combine') return 'combine '+x[1]+' segments' ; if(x[0]=='revseg') return 'reverse segment' ; if(x[0]=='interpolate') return 'interpolate missing altitudes' ; if(x[0]=='optimise') return 'optimisation' ; if(x[0]=='deltimes') return 'delete times' ; if(x[0]=='editphoto') { if(x[5]==null) return 'delete photo' ; else if(x[4]==null) return 'add photo' ; else return 'change photo' ; } if(x[0]=='extra') return 'interpolate extra points' ; } function actiontype(x) { if( x=='snip'||x=='combine'||x=='interpolate' || x=='optimise'||x=='load' ) return 0 ; else return 1 ; } /* -------------------------------------------------------------------------- */ function done(something) { if( nactions>0 && unsavedchanges.length>0 && something[0]=='editlabel' && actions[nactions-1][0]==something[0] && actions[nactions-1][1]==something[1] // don't merge change with delete && actions[nactions-1][2]==something[2] && something[6]!=null ) { actions[nactions-1][4] = something[4] ; // caption actions[nactions-1][6] = something[6] ; // type } else { actions[nactions++] = something ; donesomething() ; } } function donesomething() { actions.length = nactions ; blackout(undobtn) ; greyout(redobtn) ; if(actiontype(actions[nactions-1][0])!=0) { if(unsavedchanges.length>=3) unsavedchanges.push(null) ; else unsavedchanges.push(actionname(actions[nactions-1])) ; } } /* -------------------------------------------------------------------------- */
/*page*/ /* --------------------------------- undo ---------------------------------- */ function undo() { infowindow.close() ; var opts = active + '"confirmedundo()">Undo ' + actionname(actions[nactions-1])+'</span>' ; infowindow.open(opts,getbtnpos(5),'undo') ; } function confirmedundo() { var i,ano=nactions-1,action=actions[ano][0],s0=actions[ano][1],s1,caption ; var oldcaption,task,ind ; infowindow.close() ; if(action!='revseg'&&action!='interpolate'&&action!='deltimes') s1 = actions[ano][2] ; if(action=='bin') { disconnect(s0-1) ; for(i=segments.length;i>s0;i--) { segments[i] = segments[i-1] ; recolour(i) ; } segments[s0] = s1 ; for(s1=0;s1<segments[s0].data.length;s1++) segments[s0].data[s1].setmap(map) ; draw(s0) ; connect(s0-1) ; connect(s0) ; if(selected[0]>=s0) selected[0] += 1 ; drawsel(1) ; greyout(dlbtn) ; } else if(action=='snip') // undo snip { selected = [ s0 , segments[s0].data.length-1 ] ; combine1(s0,s0+1) ; for(i=s0+1;i<segments.length-1;i++) { segments[i] = segments[i+1] ; recolour(i) ; } segments.length -= 1 ; if(segments.length==1) blackout(dlbtn) ; draw(s0) ; drawsel(1) ; } else if(action=='editlabel') // undo create/edit/delete label segments[s0].data[s1].setlabel(actions[ano][5],actions[ano][3]) ; else if(action=='edittitle') settitle(s0) ; else if(action=='editlongtitle') longtitle = s0 ; else if(action=='wpdel') // ['wpdel',s0,s1,wpdelwork(s0,s1)] { insert(s0,s1,1) ; segments[s0].data[s1] = actions[ano][3] ; segments[s0].data[s1].setmap(map) ; redrawconnect(s0,s1) ; drawsel(1,[s0,s1]) ; } else if(action=='move') { if(actions[ano][5]) wpdelwork(s0,s1) ; else move(s0,s1,actions[ano][3]) ; } else if(action=='recal') calwork(s0,-s1) ; else if(action=='setalt') segments[s0].data[s1].h = actions[ano][3] ; else if(action=='combine') uncombine(actions[ano]) ; else if(action=='revseg') revsegwork(s0) ; else if(action=='interpolate') for(i=0;i<s0.length;i++) segments[0].data[s0[i]].h = null ; else if(action=='deltimes') for(i=0;i<s0.length;i++) segments[s0[i][0]].data[s0[i][1]].t = s0[i][2] ; else if(action=='optimise') // [ 'load' , s0 , data.slice() , loadno , props ] { for(ano--;ano>=0&&actions[ano][0]!='load';ano--) ; segments[s0].data = actions[ano][2] ; actions[loadno][4].optim.ndel = 0 ; redraw(s0) ; drawsel(1,[s0,0]) ; } else if(action=='editphoto') { ind = actions[ano][3] ; if(actions[ano][5]==null) // undo delete for(i=segments[s0].data[s1].photo.length;i>ind;i--) segments[s0].data[s1].photo[i] = segments[s0].data[s1].photo[i-1] ; if(ind>=segments[s0].data[s1].photo.length) segments[s0].data[s1].addphoto(actions[ano][4]) ; else segments[s0].data[s1].setphoto(ind,actions[ano][4]) ; } else if(action=='extra') for(selected=[s0,s1],i=actions[ano].length-1;i>=3;i--) { task = actions[ano][i] segments[task[0]].data.splice(task[1],task[2].length-2) ; } nactions -= 1 ; if(nactions<=1) greyout(undobtn) ; blackout(redobtn) ; if(actiontype(actions[nactions][0])!=0&&unsavedchanges.length>0) unsavedchanges.length -= 1 ; ; if(action=='optimise'||action=='dltimes') routeinfo() ; else if(action=='editphoto'||action=='editlabel') walkto(s0,s1) ; } /* --------------------------------- move ----------------------------------- */ function move(s0,s1,pos) { segments[s0].data[s1].setpos(pos) ; redrawconnect(s0,s1) ; drawsel(1) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* --------------------------------- redo ---------------------------------- */ function redo() { infowindow.close() ; var opts = active + '"confirmedredo()">Redo ' + actionname(actions[nactions])+'</span>' ; infowindow.open(opts,getbtnpos(6),'redo') ; } function confirmedredo() { var i,action=actions[nactions][0],s0=actions[nactions][1],s1,caption,a,b,c ; var task,ind,photo ; if(action!='bin'&&action!='revseg'&&action!='interpolate'&&action!='deltimes') s1 = actions[nactions][2] ; infowindow.close() ; if(action=='bin') binwork(s0) ; else if(action=='snip') snipwork(s0,s1) ; else if(action=='editlabel') // redo create/edit/delete label segments[s0].data[s1].setlabel(actions[nactions][6],actions[nactions][4]) ; else if(action=='edittitle') settitle(s1) ; else if(action=='editlongtitle') longtitle = s1 ; else if(action=='wpdel') wpdelwork(s0,s1) ; else if(action=='move') // ['move',s0,s1,oldpos,newpos,inserted] { if(actions[nactions][5]) insert(s0,s1,1) ; move(s0,s1,actions[nactions][4]) ; } else if(action=='recal') calwork(s0,s1) ; else if(action=='setalt') segments[s0].data[s1].h = actions[nactions][4] ; else if(action=='combine') recombine(actions[nactions]) ; else if(action=='revseg') revsegwork(s0) ; else if(action=='interpolate') interpolatework(segments[0].data) ; else if(action=='deltimes') for(i=0;i<s0.length;i++) segments[s0[i][0]].data[s0[i][1]].t = null ; else if(action=='optimise') { result = optimise(segments[s0].data,actions[nactions][2]) ; actions[loadno][4].optim.ndel = segments[s0].data.length - result.length ; segments[s0].data = result ; redraw(s0) ; drawsel(1,[s0,0]) ; routeinfo() ; } else if(action=='editphoto') { ind = actions[nactions][3] ; photo = actions[nactions][5] ; if(actions[nactions][4]==null) segments[s0].data[s1].addphoto(photo) ; else segments[s0].data[s1].setphoto(ind,photo) ; } else if(action=='extra') for(selected=[s0,s1],i=3;i<actions[nactions].length;i++) { task = actions[nactions][i] ; a = segments[task[0]].data.slice(0,task[1]) ; b = task[2].slice(1,task[2].length-1) ; c = segments[task[0]].data.slice(task[1]) ; segments[task[0]].data = a.concat(b,c) ; } nactions += 1 ; if(nactions==actions.length) greyout(redobtn) ; blackout(undobtn) ; if(actiontype(actions[nactions-1][0])!=0) unsavedchanges.push(action) ; if(action=='editphoto'||action=='editlabel') walkto(s0,s1) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ---------------------------------- dl ----------------------------------- */ function dl(opt) { var props=new propstype(),str,ndel,origlen,ano,i,npix,noalt,filename ; infowindow.close() ; if(opt==undefined) opt = 0 ; // filename i = routetitle.indexOf(' ') ; if(i<=0) filename = routetitle ; else filename = routetitle.substring(0,i) ; if(filename==''||filename==null) filename = 'Untitled' ; filename += '.tcx' ; // check for photos and altitudeless points for(noalt=npix=i=0;i<segments[0].data.length;i++) { npix += segments[0].data[i].photo.length ; if(segments[0].data[i].h==null) noalt += 1 ; } // photo list if(npix>0&&imginfo.status=='ready') { if(imginfo.type=='tcx') props.list = imginfo.uri ; // vice 'uri' else { props.list = document.URL ; if((i=props.list.lastIndexOf('?'))>=0) props.list = props.list.substring(0,i) ; props.list = reluri(props.list,imginfo.uri) ; } } if(opt) // write overview and return { str = writeoverview(segments,routetitle,props.list) ; saveAs(new Blob([str],{type: "text/plain;charset=utf-8"}),filename) ; return ; } // decide what to do if some points have no altitudes if(noalt&&!confirm(noalt+' waypoints have no associated altitudes.\n'+ 'You can hit [OK] and I will interpolate altitudes (not guaranteed), or\n'+ 'you can hit [Cancel] and try again later when the altitudes may be '+ 'available.')) return null ; if(noalt) interpolate() ; // record optimisation for(ano=-1,ndel=origlen=0,i=loadno;i>=0;i=actions[i][3]) { ndel += actions[i][4].optim.ndel ; origlen += actions[i][4].optim.origlen ; if(actions[i][4].optim.parms!=null&&ano==-1) ano = i ; } if(ano>=0) props.optim.parms = actions[ano][4].optim.parms ; props.optim.origlen = origlen ; props.optim.ndel = ndel ; // title props.title = routetitle ; props.longtitle = longtitle ; str = writetcx(props,segments[0].data) ; if(str==null) return ; unsavedchanges = [] ; saveAs(new Blob([str],{type: "text/plain;charset=utf-8"}),filename) ; } /* ------------------------------ interpolate ------------------------------- */ function interpolate() { infowindow.close() ; var response = interpolatework(segments[0].data) ; if(response.length>0) done([ 'interpolate' , response ]) ; } function interpolatework(a) { var i,j,k,response,x,y,distance,sum,len=a.length ; for(response=[],i=0;i<len;i=j) { for(;i<len&&a[i].h!=null;i++) ; // advance to null for(j=i+1;j<len&&a[j].h==null;j++) response.push(j) ; // advance to non-null if(i==0) { for(y=a[j].h;i<j;i++) a[i].h = y ; continue ; } if(j==len) { for(x=a[i-1].h;i<j;i++) a[i].h = x ; continue ; } distance = new Array(1+j-i) ; for(sum=k=0;k<=j-i;k++) sum = distance[k] = sum + dist(a[i+k-1].pos,a[i+k].pos) ; for(x=a[i-1].h,y=a[j].h,k=0;k<j-i;k++) a[i+k].h = ( x*(sum-distance[i]) + y*distance[i] ) / sum ; } return response ; } /* -------------------------------------------------------------------------- */

Archived from routemaster.html

/* ---------------------------- relative uri ------------------------------- */ function reluri(u1,u2) { var last = u1.lastIndexOf('/') ; if(last<0) return u2 ; u1 = u1.substring(0,last) ; while(u2.substring(0,3)=='../') { last = u1.lastIndexOf('/') ; if(last<0) return u2 ; u2 = u2.substring(3) ; u1 = u1.substring(0,last) ; } return u1 + '/' + u2 ; } /* -------------------------------------------------------------------------- */ function textdiv(title,body,lim) { var div=document.createElement('div'),b,nobr ; if(lim==undefined||lim<=0) lim = null ; if(title!=null) { b = document.createElement('b') ; b.appendChild(document.createTextNode(title+': ')) ; } if(lim==null||body.length<lim) { nobr = document.createElement('nobr') ; if(title!=null) nobr.appendChild(b) ; nobr.appendChild(document.createTextNode(body)) ; div.appendChild(nobr) ; } else { if(title!=null) div.appendChild(b) ; div.appendChild(document.createTextNode(body)) ; div.setAttribute('style', 'margin-bottom:2px;border-bottom:solid 1px silver;padding-bottom:2px') ; } return div ; } /* -------------------------------------------------------------------------- */ function seginfodiv(segments,segno) { var div=document.createElement('div'),props=segments[segno].props,prose ; div.appendChild(textdiv(null,'Segment ' + segno + ' of ' + segments.length + ' (' + segments[segno].data.length + ' points)')) ; if(props.title!=null) div.appendChild(textdiv('Title',props.title)) ; if(props.source!=null) div.appendChild(textdiv('Source',props.source[0])) ; if(props.longtitle!=null) div.appendChild(textdiv('Long Title',props.longtitle,50)) ; if(props.stats!=null) div.appendChild(textdiv('Stats',props.stats)) ; prose = textdiv(null, 'Use the left and right arrow keys to move between segments') ; prose.setAttribute('style','color:silver') ; div.appendChild(prose) ; return div ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------------------------------------------------- */ function highdiv(props,list,sizes,sizeno,names) { var div,scroll,p,a,nfetched=2 ; var items,i,ind,maxh,minw,sum,scroll,image=[null,null,null] ; for(items=[],i=0;i<names.length;i++) if((ind=findimage(list,names[i]))!=null) { scroll = { ind: ind, shape: imgshape(list[ind],sizes,sizeno), top: 0 } ; items.push(scroll) ; } div = document.createElement('div') ; div.setAttribute('style','font-family:helvetica') ; if(items.length>0) { minw = items[0].shape[0] ; if(items.length>1) minw += items[items.length-1].shape[0] ; for(i=0;i<2&&i<items.length;i++) image[i] = new scrolltype(i) ; for(maxh=i=0;i<items.length;i++) { if(items[i].shape[1]>maxh) maxh = items[i].shape[1] ; if(i) { sum = items[i].shape[0] + items[i-1].shape[0] ; if(sum<minw) minw = sum ; } } for(i=0;i<items.length;i++) items[i].top = Math.floor(0.5+(maxh-items[i].shape[1])/2) ; scroll = document.createElement('div') ; if(items.length==1) minw -= 4 ; scroll.setAttribute('style','position:absolute;width:'+(minw+4)+'px;'+ 'height:'+maxh+'px;overflow:hidden') ; for(sum=i=0;i<2&&i<items.length;sum+=items[i].shape[0]+4,i++) { image[i].addimage(sum) ; scroll.appendChild(image[i].img) ; } div.appendChild(scroll) ; p = document.createElement('div') ; p.setAttribute('style','width:'+(minw+4)+'px;'+'height:'+(maxh+4)+'px') ; div.appendChild(p) ; } if(props.title!=null) div.appendChild(textdiv('Title',props.title)) ; if(props.longtitle!=null) div.appendChild(textdiv('Long Title',props.longtitle,50)) ; if(props.stats!=null) div.appendChild(textdiv('Stats',props.stats)) ; if(props.tracklink!=null) { nobr = document.createElement('nobr') ; a = document.createElement('a') ; a.setAttribute('style', 'cursor:pointer;color:#0000bd;text-decoration:none') ; a.setAttribute('href',props.tracklink) ; a.setAttribute('target',"_blank") ; a.setAttribute('onclick',"infowindow.close()") ; a.appendChild(document.createTextNode('View track')) ; nobr.appendChild(a) ; nobr.appendChild(document.createTextNode(' (opens in new tab/window)')) ; div.appendChild(nobr) ; } return { div:div , scroller: items.length<=2?null:setInterval(scroller,30) } ; function scrolltype(i) { this.img = this.top = this.pos = null ; this.ind = i ; // index into items this.wid = items[i].shape[0] ; this.addimage = function(pos) { var fetch=null,ind=this.ind ; if(ind<items.length-1&&nfetched<=ind) { nfetched += 1 ; fetch = function() { new Image().src = jpg(list[items[ind+1].ind],sizes,sizeno) ; } ; } this.img = genimage(list[items[ind].ind],sizes,sizeno,fetch) ; this.pos = pos ; this.scrollimage() ; } this.scrollimage = function() { this.img.setAttribute('style', 'position:absolute;top:'+items[this.ind].top+'px;left:'+this.pos+'px') ; } } function scroller() { var i,ind,offset ; for(i=0;i<3;i++) if(image[i]!=null) image[i].pos -= 1 ; if(image[0].pos+image[0].wid<=0) { scroll.removeChild(image[0].img) ; for(i=0;i<2;i++) image[i] = image[i+1] ; image[2] = null ; } for(i=0;i<3&&image[i]!=null;i++) image[i].scrollimage() ; offset = image[i-1].pos + image[i-1].wid + 4 ; if(offset<minw) { if(image[i-1].ind==items.length-1) ind = 0 ; else ind = image[i-1].ind + 1 ; image[i] = new scrolltype(ind) ; image[i].addimage(offset) ; scroll.appendChild(image[i].img) ; } } } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------------------------------------------------- */ function greybtn(uri,name) { if(name=='cursor') name = 'arrow' ; else name = 'grey' + name ; return uri + name + '.png' ; } function blackbtn(uri,name) { if(name=='cursor') name = 'hand' ; else name = 'black' + name ; return uri + name + '.png' ; } function buttonimg(gif) { var img = document.createElement('img') ; img.setAttribute('src',gif) ; img.setAttribute('width','24') ; img.setAttribute('height','24') ; return img ; } function buttoncell(gif1,gif2) { var td=document.createElement('td'),nobr=document.createElement('nobr') ; td.setAttribute('style','padding-bottom:4px') ; nobr.appendChild(buttonimg(gif1)) ; if(gif2!=null&&gif2!=undefined) { nobr.appendChild(document.createTextNode(' ')) ; nobr.appendChild(buttonimg(gif2)) ; } td.appendChild(nobr) ; return td ; } function textcell(p1,p2) { var td=document.createElement('td'),nobr ; td.setAttribute('style','padding-bottom:4px') ; nobr = document.createElement('nobr') ; nobr.appendChild(document.createTextNode(p1)) ; td.appendChild(nobr) ; if(p2!=null&&p2!=undefined) { td.appendChild(document.createElement('br')) ; nobr = document.createElement('nobr') nobr.appendChild(document.createTextNode(p2)) ; td.appendChild(nobr) ; } return td ; } function appendrow(td,p) { var nobr = document.createElement('nobr') ; nobr.appendChild(document.createTextNode(p)) ; td.appendChild(nobr) ; td.appendChild(document.createElement('br')) ; } function genlink(uri,legend) { var a = document.createElement('a') ; a.setAttribute('style','cursor:pointer;color:#0000bd;text-decoration:none') ; a.setAttribute('href',uri) ; a.appendChild(document.createTextNode(legend)) ; return a ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------------------------------------------------- */ function blurbdiv(uri) { var div=document.createElement('div'),img=document.createElement('img'),b,p ; img.setAttribute('width',16) ; img.setAttribute('height',16) ; img.setAttribute('src',uri+'bus.gif') ; div.appendChild(img) ; b = document.createElement('b') ; b.appendChild(document.createTextNode ('\u00a0\u00a0Routemaster GPS track editor:')) ; div.appendChild(b) ; div.appendChild(document.createElement('br')) ; p = document.createElement('div') ; p.appendChild(document.createTextNode ('The main use of routemaster is to load GPS tracks, display them, allow'+ ' them to be edited in various ways, and to save them back to disc.')) ; p.setAttribute('style','padding-top:4px') ; div.appendChild(p) ; p = document.createElement('div') ; p.appendChild(document.createTextNode ('It may also be used to display tracks on a website (see the example '+ 'track in the links below) or to display an overview of tracks in an '+ 'area (for which there is also an example). However these uses require '+ 'you to host your own instance of the tool.')) ; p.setAttribute('style','text-indent:14px;border-bottom:solid 1px silver;'+ 'padding-bottom:2px') ; div.appendChild(p) ; return div ; } /* -------------------------------------------------------------------------- */ function northernmost(data) { var i,maxlat,maxi ; for(i=0;i<data.length;i++) if(i==0||data[i].pos.lat()>maxlat) { maxi = i ; maxlat = data[i].pos.lat() ; } return data[maxi].pos ; } /* -------------------------------------------------------------------------- */ function helpdiv(uri,noblank) { var div=document.createElement('div'),d,t,tr,td,a ; if(noblank==undefined) noblank = 0 ; d = document.createElement('div') d.setAttribute('style','margin-bottom:2px;border-bottom:solid 1px silver;') ; t = document.createElement('table') ; t.setAttribute('cellpadding',0) ; t.setAttribute('cellspacing',0) ; t.setAttribute('style','font-size:100%') ; tr = document.createElement('tr') ; tr.appendChild(buttoncell(greybtn(uri,'cursor'),blackbtn(uri,'cursor'))) ; td = document.createElement('td') ; td.setAttribute('rowspan',100) ; td.appendChild(document.createTextNode('\u00a0\u00a0\u00a0')) ; // &nbsp; tr.appendChild(td) ; tr.appendChild(textcell ('toggle between using the mouse to select waypoints and to drag the map', '(the space bar has the same function)')) ; t.appendChild(tr) ; tr = document.createElement('tr') ; tr.appendChild(buttoncell(blackbtn(uri,'settings'),blackbtn(uri,'dl'))) ; tr.appendChild(textcell('access to help menu and to various tools and ' + 'functions /','download route as .tcx')) ; t.appendChild(tr) ; tr = document.createElement('tr') ; tr.appendChild(buttoncell(blackbtn(uri,'scissors'),blackbtn(uri,'bin'))) ; tr.appendChild(textcell ('split the current segment at the selected point /', 'delete the currrent segment (or use the delete or backspace key)')) ; t.appendChild(tr) ; tr = document.createElement('tr') ; tr.appendChild(buttoncell(blackbtn(uri,'pen'))) ; tr.appendChild(textcell ('add a labelled coursepoint at the current position (1-10chars)', 'click on flag to edit; right-click to change symbol; '+ 'delete label to delete')) ; t.appendChild(tr) ; /* ------------------------------------------------------------------------ */
/*page*/ /* ------------------------------------------------------------------------ */ tr = document.createElement('tr') ; td = document.createElement('td') ; td.setAttribute('valign','top') ; td.appendChild(document.createTextNode('Keyboard: ')) ; tr.appendChild(td) ; td = document.createElement('td') ; appendrow(td, '\u2190/\u2192 move the current waypoint forwards or backwards;') ; appendrow(td,'\u2193 centres the map on the current waypoint;') ; appendrow(td,'[return] makes the current waypoint draggable;') ; appendrow(td,'[tab] inserts a draggable waypoint;') ; appendrow(td,'[space]=toggle cursor mode;') ; appendrow(td,'[del], [backspace]=delete segment (bin button).') ; tr.appendChild(td) ; t.appendChild(tr) ; tr = document.createElement('tr') ; td = document.createElement('td') ; td.setAttribute('valign','top') ; td.appendChild(document.createTextNode('Mouse: ')) ; tr.appendChild(td) ; td = document.createElement('td') ; appendrow(td,'when the cursor is in selection mode:') ; appendrow(td, '[shift click] extends the current segment by the cursor position.') ; tr.appendChild(td) ; t.appendChild(tr) ; d.appendChild(t) ; div.appendChild(d) ; /* ------------------------------------------------------------------------ */ d = document.createElement('div') if(noblank) { a = genlink('http://www.masterlyinactivity.com/routemaster/?routes/'+ 'Caibros.tcx','Example track to experiment with') ; d.appendChild(a) ; d.appendChild(document.createElement('br')) ; a = genlink('http://www.masterlyinactivity.com/routemaster/?routes/'+ 'capeverde.tcx','Example of a route overview') ; d.appendChild(a) ; d.appendChild(document.createElement('br')) ; } a = genlink('http://www.masterlyinactivity.com/software/routemaster.html', 'Technical documentation and source code') ; if(noblank==0) a.setAttribute('target','_blank') ; d.appendChild(a) ; if(noblank==0) d.appendChild(document.createTextNode(' (opens in new tab/window)')) ; div.appendChild(d) ; return div ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------------------------------------------------- */ /* FUNCTIONS FOR COMPUTING & DISPLAYING THE ALTITUDE PROFILE */ /* -------------------------------------------------------------------------- */ function profiletype(x,y,sind) { var i,ymin,ymax ; this.x = x ; this.y = y ; this.sind = sind ; this.sum = x[x.length-1] ; for(ymin=ymax=null,i=0;i<y.length;i++) if(y[i]!=null) { if(ymax==null||y[i]>ymax) ymax = y[i] ; if(ymin==null||y[i]<ymin) ymin = y[i] ; } if(ymin>0) { if(ymax>3*ymin) ymin = 0 ; else ymin *= 1 - (ymax/ymin-1)/2 ; } this.ymin = ymin ; this.ymax = ymax ; this.yspan = Math.max(1,ymax-ymin) ; this.prodiv = this.curdiv = this.curcan = this.curhandle = null ; } // member functions profiletype.prototype.getx = function(x) { return 10 + 600 * x / this.sum ; } ; profiletype.prototype.locx = function(i) { return this.getx(this.x[i]) ; } ; profiletype.prototype.gety = function(y) { return 10 + 180*(this.ymax-y)/this.yspan ; } ; profiletype.prototype.getxy = function(i) { return [this.getx(this.x[i]),this.y[i]==null?null:this.gety(this.y[i])] ; } ; profiletype.prototype.getsel = function(x) { var lo=0,hi=this.x.length-1,m,s0 ; if(x<0) x = 0 ; else if(x>600) x = 600 ; x += 10 ; while(hi>lo+1) // binary search { m = Math.floor((lo+hi)/2) ; if(this.locx(m)>x) hi = m ; else lo = m ; } if(Math.abs(this.locx(lo)-x)<Math.abs(this.locx(hi)-x)) m = lo ; else m = hi ; for(s0=0;s0<this.sind.length-1&&this.sind[s0+1]<=m;s0++) ; return [ s0 , m-this.sind[s0] ] ; } ; /* -------------------------------------------------------------------------- */ function procoords(segments) { var n,x,y,s0,s1,sum,len,pos,oldpos,sind,i ; for(n=s0=0;s0<segments.length;s0++) n += segments[s0].data.length ; x = new Array(n) ; y = new Array(n) ; sind = new Array(segments.length+1) ; x[0] = 0 ; for(ymax=ymin=null,sum=i=s0=0;s0<segments.length;s0++) for(sind[s0]=i,len=segments[s0].data.length,s1=0;s1<len;s1++,i++) { y[i] = segments[s0].data[s1].h ; pos = segments[s0].data[s1].pos ; if(i) sum = x[i] = sum + dist(pos,oldpos) ; oldpos = pos ; } sind[segments.length] = n ; if(n==0) return null ; else return new profiletype(x,y,sind) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------------------------------------------------- */ function drawpro(pro) { var div=document.createElement('div'),c=document.createElement('canvas') ; var ctx,i,n,s0,s1,len,xinit,ox,step,xy ; div.setAttribute ('style','position:absolute;height:190px;right:0;top:0;width:620px') ; c.setAttribute('width',620) ; c.setAttribute('height',200) ; div.appendChild(c) ; ctx = c.getContext("2d") ; ctx.font = "10px Helvetica" ; ctx.lineWidth = 0 ; ctx.globalAlpha = 0.6 ; ctx.fillStyle = 'lightgray' ; ctx.rect(0,0,620,200) ; ctx.fill() ; // draw a profile of each segment for(i=n=s0=0;s0<pro.sind.length-1;s0++) { len = pro.sind[s0+1] - pro.sind[s0] ; if(s0&1) ctx.fillStyle = "#ff9999" ; else ctx.fillStyle = "#ff0000" ; for(xinit=ox=null,s1=0;s1<len;s1++,n++,ox=xy[0]) { xy = pro.getxy(n) ; if(xy[1]!=null) { if(xinit==null) { ctx.beginPath() ; ctx.moveTo(xy[0],xy[1]) ; xinit = xy[0] ; } else ctx.lineTo(xy[0],xy[1]) ; } } ctx.lineTo(xy[0],190) ; ctx.lineTo(xinit,190) ; ctx.closePath() ; ctx.fill() ; } // lines if(pro.yspan>2500) step = 1000 ; else if(pro.yspan>1250) step = 500 ; else step = 100 ; for(i=step*Math.floor(pro.ymin/step+1);i<pro.ymax;i+=step) { y = 0.5 + pro.gety(i) ; ctx.beginPath() ; ctx.lineWidth = 1 ; ctx.strokeStyle = '#555' ; ctx.moveTo(10,y) ; ctx.lineTo(610,y) ; ctx.stroke() ; ctx.strokeText(i,590,y-2) ; } pro.prodiv = div ; // cursor pro.curdiv = document.createElement('div') ; pro.curdiv.setAttribute ('style','position:absolute;height:190px;right:0;top:0;width:620px') ; pro.curcan = null ; pro.curhandle = pro.curdiv.addEventListener("click",function(e) { var pos = e.clientX - (window.innerWidth-610) ; if((pos-594)*(pos-594)+(e.clientY-16)*(e.clientY-16)<200) { unprofile() ; return ; } drawsel(0,pro.getsel(pos)) ; } ) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------------------------------------------------- */ function drawxcur(pro,sel) { if(pro==null||pro.prodiv==null) return null ; var pos = pro.getx(pro.x[sel[1]+pro.sind[sel[0]]]) , i ; var canvas=document.createElement('canvas') , ctx=canvas.getContext("2d") ; canvas.setAttribute('width',620) ; canvas.setAttribute('height',200) ; ctx.beginPath() ; ctx.lineWidth = 1 ; ctx.moveTo(pos,10) ; ctx.lineTo(pos,190) ; ctx.stroke() ; // the circle of the 'x' ctx.beginPath() ; ctx.strokeStyle = '#555' ; ctx.fillStyle = 'white' ; ctx.lineWidth = 3 ; ctx.arc(604,16,14.1,0,2*Math.PI,false) ; ctx.stroke() ; ctx.fill() ; for(i=6;i<=26;i+=20) // the two bars of the 'x' { ctx.beginPath() ; ctx.moveTo(594,i) ; ctx.lineTo(614,32-i) ; ctx.stroke() ; } if(pro.curcan!=null) pro.curdiv.removeChild(pro.curcan) ; pro.curdiv.appendChild(pro.curcan=canvas) ; } /* -------------------------------------------------------------------------- */

Archived from utils.html

#ifndef IJ_H #define IJ_H #ifndef MEMORY_H #include "memory.h" #endif struct ij { long long i,j ; ij() { i = j = 0 ; } ij(long long a,long long b) { i = a ; j = b ; } int fprint(FILE *f) { return fprintf(f,"(%lld,%lld)",i,j) ; } int print() { return fprint(stdout) ; } } ; static inline void swap(ij &a,ij &b) { ij c=a ; a = b ; b = c ; } static void ijsort(ij *u,int n) { shellsort(u,n,ij,i) ; } /* allocate vectors of ijs */ static ij *ijvector(int n) { return (ij *) cjcalloc(n,sizeof(ij)) ; } static ij *ijvector(int *x,int n) { ij *u=ijvector(n) ; for(int i=0;i<n;i++) u[i] = ij(x[i],i) ; return u ; } static ij *ijvector(long long *x,int n) { ij *u=ijvector(n) ; for(int i=0;i<n;i++) u[i] = ij(x[i],i) ; return u ; } static ij *ijvector(ij *a,int n) { return (ij *) cjcrealloc(a,n*sizeof(ij)) ; } #endif

Archived from utils.html

#include "ij.h"

main()
{ ij x ; 
  int i ; 
  for(i=0;i<10;i++) 
  { x = ij(1ll<<(3*i),1ll<<5*i) ; printf("(%lld,%lld)\n",x.i,x.j) ; } 
}

Archived from utils.html

#include <stdio.h>
#include <assert.h>
#include "unreal.h"

double ranf() ; 

main()
{ int t ; 
  complex z,z1,z2 ;  
  double q ; 

  for(t=0;t<100;t++)
  { z = complex(ranf()-0.5,ranf()-0.5) ; 
    q = abs(z) ; 
    assert(norm(z)-q*q<1e-10) ; 

    z1 = 1/z ; 
    assert(abs(1-z*z1)<1e-10) ; 

    z1 = complex(ranf()-0.5,ranf()-0.5) ; 
    z1 *= z/z1 ; 
    assert(abs(z1-z)<1e-10) ; 

    z1 = z2 = complex(ranf()-0.5,ranf()-0.5) ; 
    z1 /= z ;
    assert(abs(z1*z-z2)<1e-10) ; 

    z1 = sqrt(z) ; 
    assert(abs(z1*z1-z)<1e-10) ; 
  } 
}

Archived from utils.html

#include "unreal.h"
#include "memory.h"

main()
{ int i,j,k ; 
  {
  double **a,***b ; 
  a = matrix(3,4) ;
  for(i=0;i<3;i++) for(j=0;j<4;j++) a[i][j] = 10*i + j ; 
  for(i=0;i<12;i++) printf("%d ",(int) a[0][i]) ; 
  printf("\n%d %d\n",dimension0(a),dimension1(a)) ; 
  freematrix(a) ; 

  b = matrix(2,3,4) ;
  for(i=0;i<2;i++) for(j=0;j<3;j++) for(k=0;k<4;k++) 
    b[i][j][k] = 100*i + 10*j + k ; 
  for(i=0;i<24;i++) printf("%d ",(int) b[0][0][i]) ; 
  printf("\n%d %d %d\n",dimension0(b),dimension1(b),dimension2(b)) ; 
  freematrix(b) ; 
  }

  {
  int **a,***b ; 
  a = imatrix(3,4) ;
  for(i=0;i<3;i++) for(j=0;j<4;j++) a[i][j] = 10*i + j ; 
  for(i=0;i<12;i++) printf("%d ",(int) a[0][i]) ; 
  printf("\n%d %d\n",dimension0(a),dimension1(a)) ; 
  freeimatrix(a) ; 

  b = imatrix(2,3,4) ;
  for(i=0;i<2;i++) for(j=0;j<3;j++) for(k=0;k<4;k++) 
    b[i][j][k] = 100*i + 10*j + k ; 
  for(i=0;i<24;i++) printf("%d ",(int) b[0][0][i]) ; 
  printf("\n%d %d %d\n",dimension0(b),dimension1(b),dimension2(b)) ; 
  freeimatrix(b) ; 
  }

  {
  complex **a,***b ; 
  a = cmatrix(3,4) ;
  for(i=0;i<3;i++) for(j=0;j<4;j++) a[i][j] = 10*i + j ; 
  for(i=0;i<12;i++) printf("%d ",(int) a[0][i].re) ; 
  printf("\n%d %d\n",dimension0(a),dimension1(a)) ; 
  freecmatrix(a) ; 

  b = cmatrix(2,3,4) ;
  for(i=0;i<2;i++) for(j=0;j<3;j++) for(k=0;k<4;k++) 
    b[i][j][k] = 100*i + 10*j + k ; 
  for(i=0;i<24;i++) printf("%d ",(int) b[0][0][i].re) ; 
  printf("\n%d %d %d\n",dimension0(b),dimension1(b),dimension2(b)) ; 
  freecmatrix(b) ; 
  }
}

Archived from utils.html

#ifndef UNREAL_H
#define UNREAL_H

struct complex
{ double re,im ; 
  complex() { re = im = 0 ; } 
  complex(double x) { re = x ; im = 0 ; } 
  complex(double x,double y) { re = x ; im = y ; } 
  complex(double *x) { re = x[0] ; im = x[1] ; } 
} ;

// addition
inline complex operator+(complex z1,complex z2)
{ return complex(z1.re+z2.re,z1.im+z2.im) ; }  
inline complex operator +=(complex &z1,complex z2)
{ return z1 = complex(z1.re+z2.re,z1.im+z2.im) ; } 
inline complex operator+(complex z1,double z2)
{ return complex(z1.re+z2,z1.im) ; }  
inline complex operator+=(complex &z1,double z2)
{ return z1 = complex(z1.re+z2,z1.im) ; }  
inline complex operator+(double z1,complex z2)
{ return complex(z1+z2.re,z2.im) ; }  

// subtraction
inline complex operator-(complex z1,complex z2)
{ return complex(z1.re-z2.re,z1.im-z2.im) ; }  
inline complex operator -=(complex &z1,complex z2)
{ return z1 = complex(z1.re-z2.re,z1.im-z2.im) ; } 
inline complex operator-(complex z1,double z2)
{ return complex(z1.re-z2,z1.im) ; }  
inline complex operator-=(complex &z1,double z2)
{ return z1 = complex(z1.re-z2,z1.im) ; }  
inline complex operator-(double z1,complex z2)
{ return complex(z1-z2.re,-z2.im) ; }  

// multiplication
inline complex operator*(complex z1,complex z2)
{ return 
  complex(z1.re*z2.re-z1.im*z2.im,z1.re*z2.im+z1.im*z2.re) ; 
}  
inline complex operator*=(complex &z1,complex z2)
{ return z1 = z1 * z2 ; }  
inline complex operator*(complex z1,double z2)
{ return complex(z1.re*z2,z1.im*z2) ; }  
inline complex operator*=(complex &z1,double z2)
{ return z1 = complex(z1.re*z2,z1.im*z2) ; }  
inline complex operator*(double z1,complex z2)
{ return complex(z1*z2.re,z1*z2.im) ; }  

// simple functions
inline double arg(complex z) { return atan2(z.im,z.re) ; } 
inline double norm(complex z) { return z.re*z.re+z.im*z.im ; }  
inline double real(complex z) { return z.re ; }  
inline double imag(complex z) { return z.im ; }  
inline complex conj(complex z) { return 
complex(z.re,-z.im) ; }  
inline complex operator -(complex z) 
{ return complex(-z.re,-z.im) ; }  

// abs
inline double abs(complex z) 
{ double q,qr=fabs(z.re),qi=fabs(z.im) ; 
  if(qr>qi) { q = z.im/z.re ; return qr*sqrt(1+q*q) ; } 
  else { if(qi==0) return 0 ; q = z.re/z.im ; return qi*sqrt(1+q*q) ; }
}

// powers
inline complex exp(complex z) 
{ double u=exp(z.re),v=z.im ; return complex(u*cos(v),u*sin(v)) ; }
inline complex sqrt(complex z) 
{ double r=sqrt(abs(z)),phi=arg(z)/2 ; 
  return complex(r*cos(phi),r*sin(phi)) ; 
}

// various forms of division
inline complex operator/(complex z,double x) { return z * (1/x) ; }
inline complex operator/=(complex &z,double x) { return z *= 1/x ; }

inline complex operator/(complex z1,complex z2)
{ double q,qr=fabs(z2.re),qi=fabs(z2.im) ; 
  if(qr>=qi)
  { q = z2.im / z2.re ; 
    return complex(z1.re+z1.im*q,z1.im-z1.re*q) / (z2.re+q*z2.im) ; 
  }
  else 
  { q = z2.re / z2.im ; 
    return complex(z1.re*q+z1.im,z1.im*q-z1.re) / (z2.re*q+z2.im) ; 
  }
}
inline complex operator/(double x,complex z)
{ double q,qr=fabs(z.re),qi=fabs(z.im) ; 
  if(qr>=qi)
  { q = z.im / z.re ; return complex(x,-x*q) / (z.re+q*z.im) ; }
  else { q = z.re / z.im ; return complex(x*q,-x) / (z.re*q+z.im) ; }
}

inline complex operator/=(complex &z1,complex z2) 
{ return z1 = z1 / z2 ; }  

// test for equality/inequality with complex/double/int
inline bool operator==(complex z1,complex z2) 
{ return z1.re==z2.re && z1.im==z2.im ; } 
inline bool operator==(complex z,double y) 
{ return z.re==y && z.im==0 ; } 
inline bool operator==(double y,complex z) 
{ return z.re==y && z.im==0 ; } 
inline bool operator==(complex z,int y) 
{ return z.re==y && z.im==0 ; } 
inline bool operator==(int y,complex z) 
{ return z.re==y && z.im==0 ; } 
inline bool operator!=(complex z1,complex z2) 
{ return z1.re!=z2.re || z1.im!=z2.im ; } 
inline bool operator!=(complex z,double y) 
{ return z.re!=y || z.im!=0 ; } 
inline bool operator!=(double y,complex z) 
{ return z.re!=y || z.im!=0 ; }  
inline bool operator!=(complex z,int y) 
{ return z.re!=y || z.im!=0 ; } 
inline bool operator!=(int y,complex z) 
{ return z.re!=y || z.im!=0 ; }

/* swap */
static void swap(complex &a,complex &b) 
{ complex c=b ; b = a ; a = c ; } 

#ifdef MATRIX_H
/* allocate vectors and matrices of complexes */
static complex *cvector(int n) 
{ return (complex *) cjcalloc(n,sizeof(complex)) ; } 

static complex **cmatrix(int m,int n)
{ int i ; 
  complex **a = (complex **) cjcalloc(m,sizeof(complex *)) ;
  a[0] = (complex *) cjcalloc(m*n,sizeof(complex)) ; 
  for(i=1;i<m;i++) a[i] = a[i-1] + n ; 
  return a ; 
}  
static complex ***cmatrix(int m,int n,int l)
{ int i ; 
  complex ***a = (complex ***) cjcalloc(m,sizeof(complex **)) ;
  a[0] = (complex **) cjcalloc(m*n,sizeof(complex *)) ; 
  for(i=1;i<m;i++) a[i] = a[i-1] + n ; 
  a[0][0] = (complex *) cjcalloc(m*n*l,sizeof(complex)) ; 
  for(i=1;i<m*n;i++) a[0][i] = a[0][i-1] + l ; 
  return a ; 
}  
/* free them */
static void freecmatrix(complex **a) 
{ if(a==NULL) return ; free(a[0]) ; free(a) ; } 
static void freecmatrix(complex ***a) 
{ if(a==NULL) return ; free(a[0][0]) ; free(a[0]) ; free(a) ; } 

#endif
#endif

Archived from utils.html

#include "memory.h"
#include <math.h>
 
void qr(double **a,int m,int n,double **Q,double **R)
{ int i,j,k,t ; 
  double alpha,s,*x=vector(m),**aa,**z=matrix(m,m),**q=matrix(m,m) ;
  insist(m>=n,orscream("qr needs m>=n (m=%d, n=%d)",m,n)) ; 

  for(aa=a,k=0;k<n&&k<m-1;aa=R,k++)
  { for(i=0;i<m;i++) for(j=0;j<n;j++) z[i][j] = 0 ;
    for(i=0;i<k;i++) z[i][i] = 1 ;
    for(i=k;i<m;i++) for(j=k;j<n;j++) z[i][j] = aa[i][j] ; 

    for(alpha=i=0;i<m;i++) { x[i] = z[i][k] ; alpha += x[i]*x[i] ; }
    if(a[k][k]>0) x[k] -= sqrt(alpha) ; else x[k] += sqrt(alpha) ; 

    for(s=i=0;i<m;i++) s += x[i] * x[i] ; 
    s = - 2/s ; 
    for(i=0;i<m;i++) for(j=0;j<m;j++) Q[i][j] = s * x[i] * x[j] ;
    for(i=0;i<m;i++) Q[i][i] += 1 ; 

    for(i=0;i<m;i++) for(j=0;j<n;j++) 
    { for(s=t=0;t<m;t++) s += Q[i][t] * z[t][j] ; R[i][j] = s ; } 

    if(k==0) for(i=0;i<m;i++) for(j=0;j<m;j++) q[i][j] = Q[i][j] ;
    else
    { for(i=0;i<m;i++) for(j=0;j<m;j++) 
      { for(s=t=0;t<m;t++) s += q[i][t] * Q[t][j] ; z[i][j] = s ; } 
      for(i=0;i<m;i++) for(j=0;j<m;j++) q[i][j] = z[i][j] ;
    }
  }

  for(i=0;i<m;i++) for(j=0;j<m;j++) Q[i][j] = q[i][j] ; 
  for(i=0;i<m;i++) for(j=0;j<n;j++) 
  { for(s=t=0;t<m;t++) s += Q[t][i] * a[t][j] ; R[i][j] = s ; } 

  free(x) ; freematrix(z,q) ; 
}

Archived from utils.html

#include "memory.h" #include <math.h> double cholsqrt(double **,double **,int),logcholsqrt(double **,double **,int) ; void cholinv(double **,double **,int) ; void cholsqr(double **,double **,int) ; /* ------------------------------------------------------------------- cholesky inverts a positive-definite symmetric matrix. */ double cholesky(double **a,double **b,int n) { double qdet = cholsqrt(a,b,n) ; cholinv(b,b,n) ; cholsqr(b,b,n) ; return qdet ; } double logcholesky(double **a,double **b,int n) { double logqdet = logcholsqrt(a,b,n) ; cholinv(b,b,n) ; cholsqr(b,b,n) ; return logqdet ; } /* ------------------------------------------------------------------- cholsqrt looks at a[i][j] only for i<=j, and constructs b with b[i][j]=0 for i>j. it may be run in place if you wish, ie. with a=b. */ double cholsqrt(double **a,double **b,int n) { int i,j,k ; double q,qdet=1 ; for(j=0;j<n;j++) { for(q=a[j][j],i=0;i<j;i++) q -= b[i][j] * b[i][j] ; if(q<=0) return 0.0 ; qdet *= q ; b[j][j] = sqrt(q) ; for(k=j+1;k<n;k++) { for(q=i=0;i<j;i++) q += b[i][j] * b[i][k] ; b[j][k] = ( a[j][k] - q ) / b[j][j] ; } } for(j=0;j<n;j++) for(k=0;k<j;k++) b[j][k] = 0 ; return qdet ; } double logcholsqrt(double **a,double **b,int n) { int i,j,k ; double q,logqdet=0 ; for(j=0;j<n;j++) { for(q=a[j][j],i=0;i<j;i++) q -= b[i][j] * b[i][j] ; insist(q>0,orscream("cholesky doesn\'t think your input matrix is pd")) ; logqdet += log(q) ; b[j][j] = sqrt(q) ; for(k=j+1;k<n;k++) { for(q=i=0;i<j;i++) q += b[i][j] * b[i][k] ; b[j][k] = ( a[j][k] - q ) / b[j][j] ; } } for(j=0;j<n;j++) for(k=0;k<j;k++) b[j][k] = 0 ; return logqdet ; } /* ------------------------------------------------------------------- cholinv looks at a[i][j] only for i<=j, and constructs b with b[i][j] zero for i<j. it may be run in place if you wish, ie. with a=b. */ void cholinv(double **a,double **b,int n) { int i,j,k ; double q ; for(j=0;j<n;j++) { b[j][j] = 1 / a[j][j] ; for(i=j-1;i>=0;i--) { for(q=0,k=i;k<j;k++) q += b[k][i] * a[k][j] ; b[j][i] = - q * b[j][j] ; } } for(j=0;j<n;j++) for(k=0;k<j;k++) b[k][j] = 0 ; } /* ------------------------------------------------------------------- cholsqr looks at a[i][j] only for i>=j, and constructs the complete matrix b. it may be run in place if you wish, ie. with a=b. */ void cholsqr(double **a,double **b,int n) { int i,j,k ; double q ; for(i=0;i<n;i++) for(j=i;j<n;j++) { for(q=0,k=j;k<n;k++) q += a[k][i] * a[k][j] ; b[i][j] = q ; } for(j=0;j<n;j++) for(k=0;k<j;k++) b[j][k] = b[k][j] ; }

Archived from utils.html

#ifndef MEMORY_H #define MEMORY_H #include <stdio.h> #include <stdlib.h> #include <stdarg.h> #ifndef MATRIX_H #define MATRIX_H /* --------------------- define insist(...,orscream(...)) ------------------- */ #define insist(a,b) \ ((a)?0:(fprintf(stdout,"Fatal error at line %d of %s\n",\ __LINE__,__FILE__),(b))) static int orscream(const char *m,...) { va_list vl ; va_start(vl,m) ; vfprintf(stderr,m,vl) ; va_end(vl) ; fprintf(stderr,"\n") ; fflush(0) ; exit(EXIT_FAILURE) ; } ; /* -------------------------- define variadic free() ------------------------ */ static void free(void *a,void *b) { free(a) ; free(b) ; } static void free(void *a,void *b,void *c) { free(a) ; free(b) ; free(c) ; } static void free(void *a,void *b,void *c,void *d) { free(a) ; free(b) ; free(c) ; free(d) ; } static void free(void *a,void *b,void *c,void *d,void *e) { free(a) ; free(b) ; free(c) ; free(d) ; free(e) ; } static void free(void *a,void *b,void *c,void *d,void *e,void *f) { free(a) ; free(b) ; free(c) ; free(d) ; free(e) ; free(f) ; } /* ------------------------- duplicate matrix.h ---------------------------- */ static void *cjcalloc(size_t a,size_t b) { insist((int)a>=0&&(int)b>=0, orscream("negative length %d requested.",a*b)) ; void *p=calloc(a,b) ; insist(p,orscream("unable to allocate %d bytes of memory.",b)) ; return p ; } static void *cjcrealloc(void *a,size_t b) { insist((int)b>=0,orscream("negative length %d requested.",b)) ; if(a==0&&b==0) return 0 ; void *p=realloc(a,b) ; if(b) insist(p,orscream("unable to reallocate %x to %d bytes.",a,b)) ; return p ; } /* vector allocators - free by calling 'free' */ // -- vector static double *vector(int n) { return (double *) cjcalloc(n,sizeof(double)) ; } static double *vector(double *a,int n) { return (double *) cjcrealloc(a,n*sizeof(double)) ; } // -- ivector static int *ivector(int n) { return (int *) cjcalloc(n,sizeof(int)) ; } static int *ivector(int *a,int n) { return (int *) cjcrealloc(a,n*sizeof(int)) ; } // -- charvector static char *charvector(int n) { return (char *) cjcalloc(n,sizeof(char)) ; } static char *charvector(const char *c) { int i ; char *ret ; for(i=0;c[i];i++) ; ret = charvector(1+i) ; for(;i>=0;i--) ret[i] = c[i] ; return ret ; } static char *charvector(char *a,int n) { return (char *) cjcrealloc(a,n*sizeof(char)) ; } // -- strvector static char **strvector(int n) { return (char **) cjcalloc(n,sizeof(char*)) ; } static char **strvector(char **a,int n) { return (char **) cjcrealloc(a,n*sizeof(char*)) ; } // -- shortvector static short *shortvector(int n) { return (short *) cjcalloc(n,sizeof(short)) ; } static short *shortvector(short *a,int n) { return (short *) cjcrealloc(a,n*sizeof(short)) ; } /* 2- and 3- dimensional matrices of doubles */ static double **matrix(int m,int n) { double **a = (double **) cjcalloc(m,sizeof(double *)) ; a[0] = vector(2+m*n) ; ((int *)a[0])[0] = m ; ((int *)a[0])[1] = n ; a[0] += 2 ; for(int i=1;i<m;i++) a[i] = a[i-1] + n ; return a ; } static int dimension0(double **a) { return ((int *)(a[0]-2))[0] ; } static int dimension1(double **a) { return ((int *)(a[0]-2))[1] ; } static double **matrix(double **a0,int m1,int n1) { double **a1 = matrix(m1,n1) ; if(a0==0) return a1 ; int i,j,m0=dimension0(a0),n0=dimension1(a0) ; if(m1>m0) m1 = m0 ; if(n1>n0) n1 = n0 ; for(i=0;i<m1;i++) for(j=0;j<n1;j++) a1[i][j] = a0[i][j] ; return a1 ; } static double ***matrix(int m,int n,int l) { int i ; double ***a = (double ***) cjcalloc(m,sizeof(double **)) ; a[0] = (double **) cjcalloc(3+m*n,sizeof(double *)) ; ((int *)a[0])[0] = m ; ((int *)a[0])[1] = n ; ((int *)a[0])[2] = l ; a[0] += 3 ; for(i=1;i<m;i++) a[i] = a[i-1] + n ; a[0][0] = vector(m*n*l) ; for(i=1;i<m*n;i++) a[0][i] = a[0][i-1] + l ; return a ; } static int dimension0(double ***a) { return ((int *)(a[0]-3))[0] ; } static int dimension1(double ***a) { return ((int *)(a[0]-3))[1] ; } static int dimension2(double ***a) { return ((int *)(a[0]-3))[2] ; } /* free them */ static void freematrix(double **a) { if(a) free(a[0]-2,a) ; } static void freematrix(double **a,double **b) { freematrix(a) ; freematrix(b) ; } static void freematrix(double **a,double **b,double **c) { freematrix(a) ; freematrix(b) ; freematrix(c) ; } static void freematrix(double **a,double **b,double **c,double **d) { freematrix(a) ; freematrix(b) ; freematrix(c) ; freematrix(d) ; } static void freematrix(double **a,double **b,double **c,double **d, double **e) { freematrix(a) ; freematrix(b) ; freematrix(c) ; freematrix(d) ; freematrix(e) ; } static void freematrix(double **a,double **b,double **c,double **d, double **e,double **f) { freematrix(a) ; freematrix(b) ; freematrix(c) ; freematrix(d) ; freematrix(e) ; freematrix(f) ; } static void freematrix(double ***a) { if(a) free(a[0][0],a[0]-3,a) ; } /* 2- and 3- dimensional matrices of ints */ static int **imatrix(int m,int n) { int **a = (int **) cjcalloc(m,sizeof(int *)) ; a[0] = ivector(2+m*n) ; a[0][0] = m ; a[0][1] = n ; a[0] += 2 ; for(int i=1;i<m;i++) a[i] = a[i-1] + n ; return a ; } static int dimension0(int **a) { return ((int *)(a[0]-2))[0] ; } static int dimension1(int **a) { return ((int *)(a[0]-2))[1] ; } static int ***imatrix(int m,int n,int l) { int i ; int ***a = (int ***) cjcalloc(m,sizeof(int **)) ; a[0] = (int **) cjcalloc(3+m*n,sizeof(int *)) ; ((int *)a[0])[0] = m ; ((int *)a[0])[1] = n ; ((int *)a[0])[2] = l ; a[0] += 3 ; for(i=1;i<m;i++) a[i] = a[i-1] + n ; a[0][0] = ivector(m*n*l) ; for(i=1;i<m*n;i++) a[0][i] = a[0][i-1] + l ; return a ; } static int dimension0(int ***a) { return ((int *)(a[0]-3))[0] ; } static int dimension1(int ***a) { return ((int *)(a[0]-3))[1] ; } static int dimension2(int ***a) { return ((int *)(a[0]-3))[2] ; } /* free them */ static void freeimatrix(int **a) { if(a) free(a[0]-2,a) ; } static void freeimatrix(int ***a) { if(a) free(a[0][0],a[0]-3,a) ; } /* swaps */ static void swap(int &a,int &b) { int c=b ; b = a ; a = c ; } static void swap(double &a,double &b) { double c=b ; b = a ; a = c ; } static void swap(char &a,char &b) { char c=b ; b = a ; a = c ; } static void swap(int &a,double &b) { int c=(int) b ; b = a ; a = c ; } static void swap(double &a,int &b) { int c=b ; b = (int) a ; a = c ; } /* ----------------------------- generic sort ------------------------------- */ #define shellsort(u,n,b,x) \ { int i,j,incr ; b v ; \ for(incr=1;1+3*incr<(n);incr=1+3*incr) ; \ for(;incr>0;incr/=3) for(i=incr;i<(n);i++) \ { v = u[i] ; \ for(j=i;j>=incr;j-=incr) \ { if(v.x<u[j-incr].x) u[j] = u[j-incr] ; else break ; } \ u[j] = v ; \ } \ } #define shellsortdesc(u,n,b,x) \ { int i,j,incr ; b v ; \ for(incr=1;1+3*incr<(n);incr=1+3*incr) ; \ for(;incr>0;incr/=3) for(i=incr;i<(n);i++) \ { v = u[i] ; \ for(j=i;j>=incr;j-=incr) \ { if(v.x>u[j-incr].x) u[j] = u[j-incr] ; else break ; } \ u[j] = v ; \ } \ } /* ----------------------------- integer sort ------------------------------- */ static void isort(int *u,int n) { int i,j,incr,v ; for(incr=1;1+3*incr<n;incr=1+3*incr) ; for(;incr>0;incr/=3) for(i=incr;i<n;i++) { for(v=u[i],j=i;j>=incr;j-=incr) if(v<u[j-incr]) u[j] = u[j-incr] ; else break ; u[j] = v ; } } /* --- extend matrix.h to complexes if unreal.h already included ---- */ #ifdef UNREAL_H /* allocate vectors and matrices of complexes */ static complex *cvector(int n) { return (complex *) cjcalloc(n,sizeof(complex)) ; } static complex *cvector(complex *a,int n) { return (complex *) cjcrealloc(a,n*sizeof(complex)) ; } /* 2- and 3- dimensional matrices of doubles */ static complex **cmatrix(int m,int n) { complex **a = (complex **) cjcalloc(m,sizeof(complex *)) ; a[0] = cvector(2+m*n) ; ((int *)a[0])[0] = m ; ((int *)a[0])[1] = n ; a[0] += 2 ; for(int i=1;i<m;i++) a[i] = a[i-1] + n ; return a ; } static int dimension0(complex **a) { return ((int *)(a[0]-2))[0] ; } static int dimension1(complex **a) { return ((int *)(a[0]-2))[1] ; } static complex ***cmatrix(int m,int n,int l) { int i ; complex ***a = (complex ***) cjcalloc(m,sizeof(complex **)) ; a[0] = (complex **) cjcalloc(3+m*n,sizeof(complex *)) ; ((int *)a[0])[0] = m ; ((int *)a[0])[1] = n ; ((int *)a[0])[2] = l ; a[0] += 3 ; for(i=1;i<m;i++) a[i] = a[i-1] + n ; a[0][0] = cvector(m*n*l) ; for(i=1;i<m*n;i++) a[0][i] = a[0][i-1] + l ; return a ; } static int dimension0(complex ***a) { return ((int *)(a[0]-3))[0] ; } static int dimension1(complex ***a) { return ((int *)(a[0]-3))[1] ; } static int dimension2(complex ***a) { return ((int *)(a[0]-3))[2] ; } /* free them */ static void freecmatrix(complex **a) { if(a) free(a[0]-2,a) ; } static void freecmatrix(complex ***a) { if(a) free(a[0][0],a[0]-3,a) ; } #endif /* -------------------------- define robust fopens -------------------------- */ #ifndef NO_MEMORY_IO static int cjcwarning=0 ; static FILE *fopenread(char *name) { FILE *f ; if(name[0]=='-'&&name[1]=='-'&&name[2]==0) f = stdin ; else f = fopen(name,"r") ; insist(f,(perror(0), orscream("Your input file %s could not be found.\n",name))) ; return f ; } static FILE *fopenwrite(char *name) { FILE *f ; if(name[0]=='-'&&name[1]=='-'&&name[2]==0) f = stdout ; else f = fopen(name,"w") ; insist(f,(perror(0),orscream("Unable to write to your file %s.\n",name))) ; return f ; } static FILE *fopenappend(char *name) { FILE *f ; if(cjcwarning==0) { printf("fopenappend deprecated\n") ; cjcwarning = 1 ; } if(name[0]=='-'&&name[1]=='-'&&name[2]==0) f = stdout ; else f = fopen(name,"a") ; insist(f,(perror(0),orscream("Unable to append to your file %s",name))) ; return f ; } static FILE *fopenreadb(char *name) { FILE *f ; if(name[0]=='-'&&name[1]=='-'&&name[2]==0) f = stdin ; else f = fopen(name,"rb") ; insist(f,(perror(0), orscream("Your input file %s could not be found.",name))) ; return f ; } static FILE *fopenwriteb(char *name) { FILE *f ; if(name[0]=='-'&&name[1]=='-'&&name[2]==0) f = stdout ; else f = fopen(name,"wb") ; insist(f,(perror(0),orscream("Unable to write to your file %s",name))) ; return f ; } static FILE *fopenappendb(char *name) { FILE *f ; if(cjcwarning==0) { printf("fopenappendb deprecated\n") ; cjcwarning = 1 ; } if(name[0]=='-'&&name[1]=='-'&&name[2]==0) f = stdout ; else f = fopen(name,"ab") ; insist(f,(perror(0),orscream("Unable to append to your file %s",name))) ; return f ; } #endif static char *freadline(FILE *ifl) { char *s=0 ; int slen,ns,c,i ; for(slen=ns=0;;) { c = fgetc(ifl) ; if(c==EOF||c=='\n') { if(slen>ns+1||(ns==0&&c=='\n')) s = charvector(s,ns+1) ; return s ; } if(ns>=slen-1) { slen += 10 + slen/2 ; s = charvector(s,slen) ; for(i=ns;i<slen;i++) s[i] = 0 ; } s[ns++] = (char) c ; } } static char *readline() { return freadline(stdin) ; } #endif #endif

Archived from routemaster.html

var icons = { // coursepoint icons flagsign: { path: "M 0.5 20.5 L 0.5 0.5 12.5 6 0.5 11.5 ", fillColor: '#FCDFFF', fillOpacity: 0.7, strokeColor: 'purple', strokeWeight: 1.5, anchor: new google.maps.Point(0.5,20.5), } , turnleft: { path: "M 18.5 20.5 L 16.5 11.5 A 2 2 0 0 0 14.5 9.5 "+ "L 11.5 10 11.5 13.5 "+ "6.5 7.5 11.5 1.5 11.5 5 16.5 5.5 A 3.5 3.5 0 0 1 20 9 z", fillColor: '#FCDFFF', fillOpacity: 0.7, strokeColor: 'purple', strokeWeight: 1.5, anchor: new google.maps.Point(18.5,20.5), } , straighton: { path: "M 7.5 20.5 L 4.5 6.5 0.5 6.5 7.5 0.5 14.5 6.5 10 6.5 z", fillColor: '#FCDFFF', fillOpacity: 0.7, strokeColor: 'purple', strokeWeight: 1.5, anchor: new google.maps.Point(7.5,20.5), } , turnright: { path: "M 3.5 20.5 L 5.5 11.5 A 2 2 0 0 1 7.5 9.5 L 10.5 10 10.5 13.5 "+ "15.5 7.5 10.5 1.5 10.5 5 5.5 5.5 A 3.5 3.5 0 0 0 2 9 z", fillColor: '#FCDFFF', fillOpacity: 0.7, strokeColor: 'purple', strokeWeight: 1.5, anchor: new google.maps.Point(3.5,20.5), } , shriek: { path: "M 8.5 21.5 A 2.5 2.5 0 0 1 8.5 16.5 A 2.5 2.5 0 1 1 8.5 21.5 "+ "M 8.5 14.5 4.5 5.5 A 4.5 4.5 0 1 1 12.5 5.5 L 8.5 14.5" , fillColor: '#FCDFFF', fillOpacity: 0.7, strokeColor: 'purple', strokeWeight: 1.5, anchor: new google.maps.Point(8.5,21.5), } , // icon for arrow representing current waypoint arrow: { path: "M 6 9 0 15 6 0 12 15 z", fillColor: 'black', fillOpacity: 1, strokeColor: 'black', strokeWeight: 0, anchor: new google.maps.Point(6,6), rotation: 0, clickable: false } , // icon for concentric circles representing draggable waypoint concircle: { path: "M 6 0 A 6 6 0 1 0 6 12 A 6 6 0 1 0 6 0 M 6 3 " + "A 3 3 0 1 0 6 9 A 3 3 0 1 0 6 3", fillColor: 'black', fillOpacity: 0, strokeColor: 'black', strokeWeight: 1, strokeOpacity: 1, anchor: new google.maps.Point(6,6), clickable: false } , // camera icon camera: { path: "M 0.5 4 A 1.5 1.5 0 0 1 2 2.5 L 5.5 2.5 7 0.5 11 0.5 " + "12.5 2.5 14 2.5 A 1.5 1.5 0 0 1 16 3 L 20 7 16 11 " + "A 1.5 1.5 0 0 1 15 11.5 L 2 11.5 A 1.5 1.5 0 0 1 0.5 10 z " + "M 9 4 A 3 3 0 0 1 9 10 A 3 3 0 0 1 9 4 " , fillColor: '#FCDFFF', fillOpacity: 0.7, strokeColor: 'purple', strokeWeight: 1.5, anchor: new google.maps.Point(21,7), clickable: false } } ; function xmlfloat(x) { return parseFloat(x.childNodes[0].textContent) ; } function isvaliddate(d) { if(Object.prototype.toString.call(d)!=="[object Date]") return false ; else return !isNaN(d.getTime()) ; } /* ------------------------------- data structure --------------------------- */ // I found the following logic quite hard to get right. A (non-null) label // satisfies the following constraints: // o. the marker is non-null // o. the map may be null, and if it is null the title may also be null and the // icon may be arbitrary // o. if the type is null, the map is null // o. the map is null if and only if the clickhandler is inactive // the same constraints apply (mutatis mutandis) to the photo, so it follows // that the label may have a null map and the photo non-null (and vice versa) // we therefore conclude that a label must be in one of 3 states: // o. type null, map null, handlers inactive, but marker non-null // o. type non-null, map null, handlers inactive, marker non-null // o. type non-null, map non-null, handlers active, marker non-null // the state in which type is non-null and map is null is applied to all // labels in a segment being deleted (we preserve the information in the // action list but don't want the label to be displayed) function datatype(pos,h,t) { this.pos = pos ; this.h = h ; this.marker = this.photomarker = this.type = this.t = null ; if(t!=undefined&&t!=null&&isvaliddate(t)&&t.getTime()>365*24*3600000) this.t = t ; this.photo = [] ; this.caption = '' ; this.clickhandler = this.righthandler = this.photohandler = null ; } // member functions datatype.prototype.geticon = function() { if(this.type=='Left') return icons.turnleft ; else if(this.type=='Straight') return icons.straighton ; else if(this.type=='Right') return icons.turnright ; else if(this.type=='Danger') return icons.shriek ; else return icons.flagsign ; } ; datatype.prototype.setlabelmap = function(m) { if(m==null||this.type==null) m = null ; else m = map ; if(m==null&&this.marker==null) return ; this.marker.setMap(m) ; if(m==null&&this.clickhandler!=null) { google.maps.event.removeListener(this.clickhandler) ; google.maps.event.removeListener(this.righthandler) ; this.clickhandler = this.righthandler = null ; } if(m!=null&&this.clickhandler==null) { this.clickhandler = this.marker.addListener('click',selpoint) ; this.righthandler = this.marker.addListener('rightclick',labelcycle) ; } } ; datatype.prototype.setphotomap = function(m) { if(m==null||this.photo.length==0) m = null ; else m = map ; if(m==null&&this.photomarker==null) return ; this.photomarker.setMap(m) ; if(m==null&&this.photohandler!=null) { google.maps.event.removeListener(this.photohandler) ; this.photohandler = null ; } if(m!=null&&this.photohandler==null) this.photohandler = this.photomarker.addListener('click',selpoint) ; } ; datatype.prototype.setlabel = function(t,c) { this.type = t ; this.caption = c ; if(t==null) { if(this.marker!=null) this.setlabelmap(null) ; return ; } if(this.marker==null) this.marker = new google.maps.Marker ({ position:this.pos,map:map,icon:this.geticon(),title:c,zIndex:1 }) ; else { this.marker.setIcon(this.geticon()) ; this.marker.setTitle(c) ; } this.setlabelmap(map) ; } ; datatype.prototype.setphoto = function(ind,p) { var i ; if(p==null) { for(i=ind;i<this.photo.length-1;i++) this.photo[i] = this.photo[i+1] ; this.photo.length -= 1 ; if(this.photo.length==0&&this.photomarker!=null) this.setphotomap(null) ; return ; } else { this.photo[ind] = p ; if(ind==0) this.photomarker.setTitle(p) ; } } ; datatype.prototype.addphoto = function(p) { this.photo.push(p) ; if(this.photomarker==null) this.photomarker = new google.maps.Marker ({ position:this.pos,map:map,icon:icons.camera,title:p,zIndex:1 }) ; this.setphotomap(map) ; } ; datatype.prototype.setpos = function(p) { this.pos = p ; if(this.type!=null) this.marker.setPosition(p) ; if(this.photo.length>0) this.photomarker.setPosition(p) ; } ; datatype.prototype.setmap = function(m) { this.setlabelmap(m) ; this.setphotomap(m) ; } ; datatype.prototype.settype = function(t) { this.type = t ; this.marker.setIcon(this.geticon()) ; } ; datatype.prototype.labelcycle = function() { var oldtype = this.type , type ; if(oldtype=='Generic') type = 'Left' ; else if(oldtype=='Left') type = 'Straight' ; else if(oldtype=='Straight') type = 'Right' ; else if(oldtype=='Right') type = 'Danger' ; else type = 'Generic' ; this.settype(type) ; return [ oldtype , type ] ; } function addlabel(data,pos,type,caption) { var j,ind,mindist ; for(j=0;j<data.length;j++) if(j==0||dist(pos,data[j].pos)<mindist) { mindist = dist(pos,data[j].pos) ; ind = j ; } data[ind].setlabel(type,caption) ; } function propstype() { this.longtitle = this.title = this.list = this.inputlen = this.source = null ; this.stats = this.tracklink = this.overview = null ; this.photo = [] ; this.optim = { already: 0, ndel: 0, origlen: 0, parms: null } } /* -------------------------------------------------------------------------- */ function readtcx(xmldoc) { var nodeno,type,lat,lon,i,j,node,alt,pos,segment,props,title,list,txt ; var ind,caption,data,photo,time,valid,anc,xmlnodes,nsegment,propno,names ; var track,trackpoint,trackno,course,coursepoint,courseno,courselen,overview ; // loop over trackpoints track = xmldoc.getElementsByTagName('Trackpoint') ; for(courselen=[],data=[],anc=null,trackno=0;trackno<track.length;trackno++) { trackpoint = track[trackno] ; if(trackpoint.parentNode.parentNode.nodeName=='Course') if(trackpoint.parentNode.parentNode!=anc) { anc = trackpoint.parentNode.parentNode ; courselen.push([trackno,anc]) ; } lat = lon = alt = time = null ; photo = [] ; for(valid=1,nodeno=0;nodeno<trackpoint.childNodes.length;nodeno++) { node = trackpoint.childNodes[nodeno] ; if(node.nodeName=='AltitudeMeters') alt = xmlfloat(node) ; else if(node.nodeName=='Time') // '1970-01-01T03:040:08Z' time = new Date(node.childNodes[0].textContent) ; else if(node.nodeName=='Position') for(j=0;j<node.childNodes.length;j++) { if(node.childNodes[j].nodeName=='LatitudeDegrees') lat = xmlfloat(node.childNodes[j]) ; else if(node.childNodes[j].nodeName=='LongitudeDegrees') lon = xmlfloat(node.childNodes[j]) ; } else if(node.nodeName=='Extensions') for(j=0;j<node.childNodes.length;j++) { if(node.childNodes[j].nodeName=='Photo') photo = node.childNodes[j].childNodes[0].textContent.split(' ') ; else if(node.childNodes[j].nodeName=='ValidTime') valid = 0 ; } } if(lat==null||lon==null) continue ; pos = new google.maps.LatLng(lat,lon) ; data.push(new datatype(pos,alt,valid?time:null)) ; for(ind=0;ind<photo.length;ind++) data[data.length-1].addphoto(photo[ind]) ; } if(track.length==0) { alert('no trackpoints') ; throw '' ; } if(courselen.length==0) courselen.push([0,null]) ; courselen.push([track.length,null]) ; // loop over coursepoints course = xmldoc.getElementsByTagName('CoursePoint') ; for(courseno=0;courseno<course.length;courseno++) { coursepoint = course[courseno] ; caption = type = lat = lon = null ; for(nodeno=0;nodeno<coursepoint.childNodes.length;nodeno++) { node = coursepoint.childNodes[nodeno] ; if(node.nodeName=='Name') caption = node.childNodes[0].textContent ; else if(node.nodeName=='PointType') type = node.childNodes[0].textContent ; else if(node.nodeName=='Position') for(j=0;j<node.childNodes.length;j++) { if(node.childNodes[j].nodeName=='LatitudeDegrees') lat = xmlfloat(node.childNodes[j]) ; else if(node.childNodes[j].nodeName=='LongitudeDegrees') lon = xmlfloat(node.childNodes[j]) ; } } if(lat==null||lon==null||caption==null||type==null) { alert('Badly formatted course point' ) ; throw '' ; } addlabel(data,new google.maps.LatLng(lat,lon),type,caption) ; } for(segment=[],i=0;i<courselen.length-1;i++) segment.push(data.slice(courselen[i][0],courselen[i+1][0])) ; nsegment = segment.length ; // props fields props = new Array(nsegment) ; for(i=0;i<nsegment;i++) { props[i] = new propstype() ; props[i].inputlen = data.length ; if(props[i].optim.origlen==0) props[i].optim.origlen = data.length ; } // optimised? xmlnodes = xmldoc.getElementsByTagName('Optimised') ; if(xmlnodes.length) { props[0].optim.already = 1 ; props[0].optim.origlen = parseInt(xmlnodes[0].getAttribute('from')) ; props[0].optim.ndel = props[0].optim.origlen - parseInt(xmlnodes[0].getAttribute('to')) ; props[0].optim.parms = { tol: parseFloat(xmlnodes[0].getAttribute('tol')) , maxsep: parseFloat(xmlnodes[0].getAttribute('maxsep')) , wppenalty: parseFloat(xmlnodes[0].getAttribute('wppenalty')) , vweight: parseFloat(xmlnodes[0].getAttribute('vweight')) } ; for(i=1;i<nsegment;i++) props[i].optim = props[0].optim ; } // overview fields (different values for each segment) all in one big loop names = [ 'Name' , 'LongTitle' , 'Stats' , 'PhotoList' , 'Overview' , 'TrackLink' , 'Photo' ] ; for(title=null,propno=0;propno<7;propno++) { xmlnodes = xmldoc.getElementsByTagName(names[propno]) ; for(i=0;i<xmlnodes.length;i++) { node = xmlnodes[i] ; if(propno==0) anc = node.parentNode ; else anc = node.parentNode.parentNode ; if(propno==3) txt = node.getAttribute('src') ; else if(propno==4||propno==5) txt = node.getAttribute('href') ; else txt = node.childNodes[0].textContent ; for(j=0;j<nsegment&&anc!=courselen[j][1];j++) ; if(j==nsegment) { if(propno==0&&title==null) if( node.parentNode.nodeName=='Courses' || node.parentNode.nodeName=='Lap' ) title = txt ; continue ; } if(propno==0) props[j].title = txt ; else if(propno==3) props[j].list = txt ; else if(propno<6) props[j][names[propno].toLowerCase()] = txt ; else props[j].photo = txt.match(/\S+/g) ; } } return { title: title , props: props , segments: segment } ; } /* -------------------------------------------------------------------------- */ function readgpx(xmldoc) { var xmlcoords,nodeno,type,lat,lon,i,node,alt,pos,caption,data,time ; var props = new propstype() ; // get the route name xmlcoords = xmldoc.getElementsByTagName('name') ; for(i=0;props.title==null&&i<xmlcoords.length;i++) if(xmlcoords[i].parentNode.nodeName!='wpt') props.title = xmlcoords[i].childNodes[0].textContent ; // get the route description xmlcoords = xmldoc.getElementsByTagName('desc') ; if(xmlcoords.length>0) props.longtitle = xmlcoords[0].childNodes[0].textContent ; // loop over the track points to get the coords xmlcoords = xmldoc.getElementsByTagName('trkpt') ; if(xmlcoords.length==0) xmlcoords = xmldoc.getElementsByTagName('rtept') ; for(data=[],i=0;i<xmlcoords.length;i++) { lat = parseFloat(xmlcoords[i].getAttribute('lat')) ; lon = parseFloat(xmlcoords[i].getAttribute('lon')) ; pos = new google.maps.LatLng(lat,lon) ; for(time=alt=null,nodeno=0;nodeno<xmlcoords[i].childNodes.length;nodeno++) { node = xmlcoords[i].childNodes[nodeno] ; if(node.nodeName=='ele') alt = parseFloat(node.textContent) ; else if(node.nodeName=='time') time = new Date(node.childNodes[0].textContent) ; } if(alt==null) { alert(pos+' has no altitude... unable to proceed') ; throw '' ; } data.push(new datatype(pos,alt,time)); } // loop over the course points to get the labels xmlcoords = xmldoc.getElementsByTagName('wpt') ; for(i=0;i<xmlcoords.length;i++) { caption = type = lat = lon = null ; lat = parseFloat(xmlcoords[i].getAttribute('lat')) ; lon = parseFloat(xmlcoords[i].getAttribute('lon')) ; for(nodeno=0;nodeno<xmlcoords[i].childNodes.length;nodeno++) { node = xmlcoords[i].childNodes[nodeno] ; if(node.nodeName=='name') caption = node.childNodes[0].textContent ; else if(node.nodeName=='type') type = node.childNodes[0].textContent ; } if(lat==null||lon==null||caption==null) { alert('Badly formatted course point' ) ; throw '' ; } if(type==null) type = 'Generic' ; addlabel(data,new google.maps.LatLng(lat,lon),type,caption) ; } props.inputlen = data.length ; return { props: [props] , segments: [data] } ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------------------------------------------------- */ function dist(x,y) { return google.maps.geometry.spherical.computeDistanceBetween(x,y) ; } function angle(x,y) { return google.maps.geometry.spherical.computeHeading(x,y)*Math.PI/180 ; } /* -------------------------------------------------------------------------- */ function optimise(idata,parms) { var stk,nnstk,stk2,clen=idata.length,i,j,m,step=new Array(clen-1) ; var opos,oalt,npos,nalt,ndatum,mpos,malt,arccentre,arctol,pathpos ; var legal,theta,omega,x,y,hyp,tdist,maxtheta,mintheta,d,dh,od,odh,odash ; var bearings=new Array(clen),nstk=new Array(clen),pi=Math.PI,tol=parms.tol ; stk = [ { data:[idata[0]] , err:0 , pathpos:1 } ] ; for(i=0;i<clen-1;i++) step[i] = dist(idata[i].pos,idata[i+1].pos) ; // this is a forwards dynamic program. in stk we have a list of hypotheses // each of which advances a different number of points through the data, // sorted increasing on how far they've advanced. at each step we take the // first item from the stack and try extending to each legal successor point. // note that a hypothesis whose pathpos is k is one whose last point is // idata[k-1]. // note too that I could (& should) preallocate stk and stk2 for efficiency while(stk[0].pathpos<clen) { pathpos = stk[0].pathpos ; opos = idata[pathpos-1].pos ; oalt = idata[pathpos-1].h ; // try extending to pathpos+i for(arctol=null,nnstk=i=0;i<clen-pathpos;i++) { ndatum = idata[pathpos+i] ; npos = ndatum.pos ; nalt = ndatum.h ; if(i==0) hyp = step[pathpos-1] ; else if((hyp=dist(opos,npos))>parms.maxsep) break ; omega = angle(opos,npos) ; // find the min and max legal bearing if(hyp>tol) { theta = Math.asin(tol/hyp) ; if(arctol==null) { arccentre = omega ; arctol = theta ; } else { for(odash=omega-arccentre;odash>pi;odash-=2*pi) ; while(odash<-pi) odash += 2*pi ; maxtheta = Math.min(arctol,odash+theta) ; mintheta = Math.max(-arctol,odash-theta) ; if(maxtheta<mintheta) break ; arccentre += (maxtheta+mintheta) /2 ; arctol = (maxtheta-mintheta) /2 ; } } /* -------------------------------------------------------------------- */
/*page*/ /* -------------------------------------------------------------------- */ bearings[i] = { hyp:hyp , omega:omega } ; // see whether this breaches the max error on any intermediate point for(legal=1,od=odh=tdist=m=0;m<i;m++,od=d,odh=dh) { mpos = idata[pathpos+m].pos ; malt = idata[pathpos+m].h ; x = bearings[m].hyp ; theta = bearings[m].omega ; d = x * Math.sin(theta-omega) ; dh = 0 ; if(d*d<tol*tol&&oalt!=null&&nalt!=null&&malt!=null) { y = hyp - x*Math.cos(theta-omega) ; y = Math.sqrt(d*d+y*y) ; dh = parms.vweight * ( malt - (oalt*y+nalt*x)/(x+y) ) ; } if(d*d+dh*dh>tol*tol) { legal = 0 ; break ; } tdist += step[pathpos-1+m] * ( d*d+d*od+od*od + dh*dh+odh*dh+odh*odh ) ; } // if we emerge with 'legal' non-zero then we may advance to pathpos+i // and tdist is the sum of squared errors if(legal) nstk[nnstk++] = ( { data: stk[0].data.concat([ndatum]) , err: stk[0].err + pi*tdist/3 + parms.wppenalty , pathpos: stk[0].pathpos+i+1 } ) ; if(ndatum.type!=null||ndatum.photo.length>0) break ; } // end loop over i // now we have in nstk the possible extensions of stk[0] in increasing // order of end point, so we merge with stk[0..stk.length-1] for(stk2=[],i=1,j=0;i<stk.length||j<nnstk;) if(i==stk.length) stk2.push(nstk[j++]) ; else if(j==nnstk||stk[i].pathpos<nstk[j].pathpos) stk2.push(stk[i++]) ; else if(stk[i].pathpos>nstk[j].pathpos) stk2.push(nstk[j++]) ; else if(stk[i].err<nstk[j].err) { stk2.push(stk[i++]) ; j += 1 ; } else { stk2.push(nstk[j++]) ; i += 1 ; } stk = stk2 ; } return stk[0].data ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ------------------------------- writetcx -------------------------------- */ function writetcx(props,idata) { var i,j,k,xmldoc,course,lap,datum,filename,track,time,routelen,extns ; var trackpoint,coursepoint,str,flag,clen=idata.length,tlast,di,dk ; var origlen,ndel,ano,photo,thisuri,maxsep,sep,tdist,ttime,time,otime ; var distance = new Array(clen) , msecs = new Array(clen) ; var valid = new Array(clen) ; for(tlast=null,maxsep=tdist=ttime=i=flag=0;i<clen;otime=time,i++) { time = idata[i].t ; if(time!=null) msecs[i] = time = time.getTime() ; if(tlast!=null&&time!=null&&time<tlast) flag = 1 ; // out of order if(time!=null) tlast = time ; if(i) { sep = dist(idata[i-1].pos,idata[i].pos) ; distance[i] = distance[i-1] + sep ; if(sep>maxsep) maxsep = sep ; if(time!=null&&otime!=null) { tdist += sep ; ttime += time - otime ; } } else distance[i] = 0 ; } routelen = distance[clen-1] ; if(maxsep>100&&!confirm('Some gaps between waypoints are >100m.\n'+ 'This will cause problems if used for navigation in a Garmin.\n'+ 'You can hit [OK] and I will proceed anyway, or\n'+ 'you can hit [Cancel] to interpolate extra points\n'+ '(recommended - go to Route Info under the cogwheel).')) return null ; if(tdist==0||flag!=0) for(i=0;i<clen;i++) msecs[i] = distance[i] * 333 ; else for(i=0;i<clen;i=k) { for(;i<clen&&idata[i].t!=null;i++) ; // advance to null if(i==clen) break ; for(k=i+1;k<clen&&idata[k].t==null;k++) ; // advance to non-null for(j=i;j<k;j++) valid[i] = 0 ; if(i==0) for(time=msecs[k],j=i;j<k;j++) msecs[j] = time - (distance[k]-distance[j])*ttime/tdist ; else if(k==clen) for(time=msecs[i-1],j=i;j<clen;j++) msecs[j] = time + (distance[j]-distance[i-1])*ttime/tdist ; else for(j=i,di=distance[i-1],dk=distance[k];j<k;j++) msecs[j] = ( msecs[i-1]*(dk-distance[j]) + msecs[k]*(distance[j]-di) ) / (dk-di) ; } str = '<?xml version="1.0" encoding="UTF-8" standalone="no" ?>\n' + '<TrainingCenterDatabase xmlns="http://www.garmin.com/xmlschemas/' + 'TrainingCenterDatabase/v2"\n' + ' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"\n' + ' xsi:schemaLocation="http://www.garmin.com/' + 'xmlschemas/TrainingCenterDatabase/v2 ' + 'http://www.garmin.com/xmlschemas/TrainingCenterDatabasev2.xsd">\n' + '<!-- http://www.masterlyinactivity.com/software/routemaster.html -->' ; str += '\n <Folders><Courses><CourseFolder Name="Courses">\n' ; str += ' <CourseNameRef><Id>'+props.title+'</Id></CourseNameRef>\n' ; str += ' </CourseFolder></Courses></Folders>\n<Courses><Course>\n' ; str += ' <Name>'+props.title+'</Name>\n <Lap>\n' + adddist(routelen) ; time = (msecs[clen-1]-msecs[0]) / 1000 ; str += ' <TotalTimeSeconds>' + time.toFixed(0) + '</TotalTimeSeconds>\n' ; str += addpos('Begin',idata[0].pos) ; str += addpos('End',idata[clen-1].pos) ; str += ' <Intensity>Active</Intensity>\n' + ' </Lap>\n <Track>\n' ; // loop over trackpoints for(i=0;i<idata.length;i++) { str += ' <Trackpoint>\n' + addpos('',idata[i].pos) ; str += adddist(distance[i]) + addalt(idata[i].h) ; str += ' <Time>' + new Date(msecs[i]).toISOString() + '</Time>\n' ; if(idata[i].photo.length>0) { str += ' <Extensions><Photo>' ; for(k=0;k<idata[i].photo.length;k++) { if(k) str += ' ' ; str += idata[i].photo[k] ; } str += '</Photo></Extensions>\n' ; } if(valid[i]==0) str += ' <Extensions><ValidTime>False</ValidTime></Extensions>\n' ; str += ' <SensorState>Absent</SensorState>\n </Trackpoint>\n' ; } str += ' </Track>\n\n' ; extns = props.optim.ndel>0 || props.list!=null || props.longtitle!=null || props.overview != null ; if(extns) str += ' <Extensions>\n' ; if(props.optim.ndel) { str += ' <Optimised from="' + props.optim.origlen + '" to="' + (props.optim.origlen-props.optim.ndel) ; if(props.optim.parms!=null) str += '" tol="' + props.optim.parms.tol.toFixed(0) + '" maxsep="' + props.optim.parms.maxsep.toFixed(0) + '" wppenalty="' + props.optim.parms.wppenalty.toFixed(0) + '" vweight="' + props.optim.parms.vweight.toFixed(1) ; str += '"/>\n' } if(props.list!=null) str += ' <PhotoList src="'+props.list+'"/>\n' ; if(props.longtitle!=null) str += ' <LongTitle>'+props.longtitle+'</LongTitle>\n' ; if(props.overview!=null) str += ' <Overview href=">' + props.overview + '"/>\n' ; if(extns) str += ' </Extensions>\n\n' ; // finally loop over coursepoints for(i=0;i<idata.length;i++) if(idata[i].type!=null) { datum = idata[i] ; str += ' <CoursePoint><Name>'+datum.marker.title+'</Name>\n' ; str += ' <PointType>'+datum.type+'</PointType>\n' ; str += addpos('',idata[i].pos) + addalt(idata[i].h) ; time = new Date(msecs[i]) ; str += ' <Time>' + time.toISOString() + '</Time>\n </CoursePoint>\n' ; } return str + '</Course></Courses></TrainingCenterDatabase>\n' ; } /* -------------------------------------------------------------------------- */ function addpos(tag,pos) { var str = ' <'+tag+'Position>\n <LatitudeDegrees>' ; str += pos.lat().toFixed(5)+'</LatitudeDegrees>\n <LongitudeDegrees>' ; str += pos.lng().toFixed(5)+'</LongitudeDegrees>\n </'+tag+'Position>\n' ; return str ; } function addalt(x) { return ' <AltitudeMeters>' + x.toFixed(0) + '</AltitudeMeters>\n' ; } function adddist(x) { return ' <DistanceMeters>' + x.toFixed(0) + '</DistanceMeters>\n' ; } /* ----------------------------- writeoverview ----------------------------- */ function writeoverview(segments,title,list) { var i,j,h,oh,maxalt,minalt,routelen,up,down,ndata,photo,segno,idata,title ; var uri ; var str = '<?xml version="1.0" encoding="UTF-8" standalone="no" ?>\n' + '<!-- http://www.masterlyinactivity.com/software/routemaster.html -->' + '\n<!-- **** routemaster overview file **** not for navigation ****' + ' -->\n<TrainingCenterDatabase><Courses>\n' ; if(title!='Untitled Route'&&title!=null) str += '<Name>'+title+'</Name>\n' ; str += '\n' ; for(segno=0;segno<segments.length;segno++) { idata = segments[segno].data ; for(oh=maxalt=minalt=null,routelen=up=down=i=0;i<idata.length;oh=h,i++) { if(i) routelen += dist(idata[i-1].pos,idata[i].pos) ; if((h=idata[i].h)==null) continue ; if(maxalt==null||h>maxalt) maxalt = h ; if(minalt==null||h<minalt) minalt = h ; if(h!=null&&oh!=null) { if(h>oh) up += h-oh ; else down += oh-h ; } } for(photo=[],ndata=new Array(idata.length),i=0;i<idata.length;i++) { ndata[i] = new datatype(idata[i].pos,0) ; for(j=0;j<idata[i].photo.length;j++) photo.push(idata[i].photo[j]) ; } idata = optimise(ndata,{tol:500,maxsep:100000,wppenalty:10000,vweight:0}) ; str += '<Course>\n' ; if(segments[segno].props.title!=null) str += ' <Name>' + segments[segno].props.title + '</Name>\n' ; str += ' <Track>\n' ; // loop over trackpoints for(i=0;i<idata.length;i++) str += ' <Trackpoint><Position>\n <LatitudeDegrees>' + idata[i].pos.lat().toFixed(4)+'</LatitudeDegrees>\n ' + '<LongitudeDegrees>' + idata[i].pos.lng().toFixed(4) + '</LongitudeDegrees>\n </Position></Trackpoint>\n' ; str += ' </Track>\n\n <Extensions>\n' ; title = segments[segno].props.longtitle ; if(title!=null) str += ' <LongTitle>' + title + '</LongTitle>\n' ; uri = document.URL ; if((title=segments[segno].props.source)!=null) { if(title[1]=='uri') uri = reluri(uri,'?'+title[0]) ; else uri = reluri(uri,'?$FILE$/'+title[0]) ; } str += ' <Stats>Distance ' + (routelen/1000).toFixed(1) + 'km;' + ' altitude ' + minalt.toFixed(0) + '-' + maxalt.toFixed(0) + 'm; \u2191' + up.toFixed(0) + 'm \u2193' + down.toFixed(0) + 'm</Stats>\n <TrackLink href="' + uri + '"/>\n' ; if(list!=null) str += ' <PhotoList src="' + list + '"/>\n' ; if(photo.length) { str += ' <Photo>' ; for(i=0;i<photo.length;i++) { if(i>0&&i%6==0) str += '\n ' ; else if(i) str += ' ' ; str += photo[i] ; } if(photo.length%6==0) str += '\n ' ; str += '</Photo>\n' ; } str += ' </Extensions>\n</Course>\n\n' ; } return str + '</Courses></TrainingCenterDatabase>\n' ; } /* -------------------------------------------------------------------------- */ function gencolours(n) { var colours=new Array(n) ; var ind,density,k,a,na,i,j,m,r,g,b ; for(ind=0,density=1;ind<n;density*=2) { if(density==1) k = 3 ; else k = Math.floor(0.5+0.75*density*(1+density/2)) ; a = new Array(k) ; if(density==1) { a = [ [0,0] , [0,1] , [1,0] ] ; na = 3 ; } else for(na=i=0;i<=density;i++) { if((i&1)==0) for(j=1;i+j<=density;j+=2) a[na++] = [ i , j ] ; else for(j=density-i;j>=0;j--) a[na++] = [ i , j ] ; } if(na!=k) alert('logic error') ; for(k=0;(1<<k)<na;k++) ; for(i=0;i<(1<<k)&&ind<n;i++) { for(m=j=0;j<k;j++) m |= ((i>>j)&1) << (k-1-j) ; if(m>=na) continue ; r = ( density - a[m][0] - a[m][1] ) * (255/density) ; g = a[m][1] * (180/density) ; b = a[m][0] * (300/density) ; r = ("00"+Math.floor(0.5+r).toString(16)).substr(-2) ; g = ("00"+Math.floor(0.5+g).toString(16)).substr(-2) ; b = ("00"+Math.floor(0.5+b>255?255:b).toString(16)).substr(-2) ; colours[ind++] = '#' + r + g + b ; } } return colours ; }

Archived from routemaster.html

var segments=[],selected,actions,nactions,sel,dragging,loadno,overviewing=0 ; var pending,xpending,mouseopt=0,elevator,resuri,xmlfile=null ; var routetitle,body,mapdiv,pro ; var scroller=null,longtitle=null,overview=null,imgdiv,imghandle,imginfo,imgind ; var cursorbtn,scissorsbtn,binbtn,undobtn,redobtn,penbtn,setbtn=null,dlbtn ; var defparms = {tol:10,maxsep:95,wppenalty:1000,vweight:1} ; var neutral='<span style="font-family:helvetica">' ; var active='<span style="cursor:pointer;color:#0000bd" onclick=' ; var inactive='<span style="color:silver">' ; var textbox='<div style="margin-bottom:2px;border-bottom:solid 1px silver;' + 'padding-bottom:2px;font-family:helvetica">' ; var finalbox='<div style="font-family:helvetica">' ; var parser = new DOMParser() ; var map = null , clickhandle = null ; var unsavedchanges = [] ; var infowindow = { handle: null , type: null , open: function(s,pos,type) { this.handle = new google.maps.InfoWindow({content:s,position:pos}) ; this.handle.open(map) ; google.maps.event.addListener(this.handle,'closeclick',function() { if(infowindow.type=='highlight') { redraw(selected[0]) ; if(scroller!=null) { clearInterval(scroller) ; scroller = null ; } } else if(infowindow.type=='phinfo') walkto(selected[0],selected[1]) ; infowindow.handle = infowindow.type = null ; } ) ; this.type = type ; } , close: function() { if(this.handle==null) return null ; var response = this.type ; this.handle.close() ; if(response=='highlight') { redraw(selected[0]) ; if(scroller!=null) { clearInterval(scroller) ; scroller = null ; } } else if(response=='phinfo') walkto(selected[0],selected[1]) ; this.handle = this.type = null ; return response ; } } ; /* --------------- construct a segment from an xml document ----------------- */ function genseg(a,b) { this.data = a ; this.props = b ; this.route = this.routehandler = this.dots = this.dothandler = null ; this.colour = "red" ; } /* -------------------------------------------------------------------------- */ /* CONSTRUCTORS */ /* -------------------------------------------------------------------------- */ function dotpath(a,b) { this.path = [a,b] ; this.cursor = 'default' ; this.geodesic = true ; this.strokeOpacity = 0 ; this.icons = [ { icon: { path: 'M 0 0 L 1 0',strokeOpacity:1,scale:1 } , offset: '1px' , repeat: '4px' } ] ; this.zIndex = 0 ; } /* -------------------------------------------------------------------------- */ function linepath(s0,start,end,colour,width) { var i,len=(start<0?segments[s0].data.length:end-start) ; if(width==undefined) width = 2 ; this.path = new Array(len) ; if(start<0) for(i=0;i<len;i++) this.path[i] = segments[s0].data[i].pos ; else for(i=0;i<len;i++) this.path[i] = segments[s0].data[start+i].pos ; this.clickable = 'false' ; this.cursor = 'default' ; this.geodesic = true ; this.strokeColor = colour ; this.strokeOpacity = 1.0 ; this.strokeWeight = width ; if(width==2) this.zIndex = 0 ; else this.zIndex = 1 ; } /* -------------------------------------------------------------------------- */ function listinfo() { this.list = [] ; this.sizes = [] ; this.uri = null ; this.thumbind = this.scale = this.status = this.type = this.pixpage = null ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------------------------------------------------- */ /* UTILITY FUNCTIONS */ /* -------------------------------------------------------------------------- */ function interp(x,y,lamda) { return google.maps.geometry.spherical.interpolate(x,y,lamda) ; } function bearing(x,y) { return google.maps.geometry.spherical.computeHeading(x,y) ; } /* --------------------------- button handlers ----------------------------- */ function greyout(btn) { if(overviewing||btn.active==0) return 0 ; btn.btn.setAttribute('src',btn.greyimg) ; btn.ui.removeEventListener('click',btn.handler) ; if(btn==penbtn) btn.ui.removeEventListener('contextmenu',photoprompt) ; btn.ui.style.cursor = 'default' ; btn.active = 0 ; return 1 ; } function blackout(btn) { if(overviewing||btn.active) return ; btn.btn.setAttribute('src',btn.blackimg) ; btn.ui.addEventListener('click',btn.handler) ; if(btn==penbtn) btn.ui.addEventListener('contextmenu',photoprompt) ; btn.ui.style.cursor = 'pointer' ; btn.active = 1 ; } /* ------------------------ enter/exit full screen -------------------------- */ // most of the code is available from pixlib function enterFullscreen() { infowindow.close() ; enterfullscreen() ; } function exitFullscreen() { infowindow.close() ; if(document.exitFullscreen) document.exitFullscreen() ; else if(document.mozCancelFullScreen) document.mozCancelFullScreen() ; else if(document.webkitExitFullscreen) document.webkitExitFullscreen() ; } /* -------------------------------------------------------------------------- */ function findimg(id) { var i ; for(i=0;i<imginfo.list.length;i++) if(imginfo.list[i].name!=undefined&&imginfo.list[i].name==id) return i ; return -1 ; } /* ------------------- message warning of unsaved changes ------------------- */ function unsavedmsg(ok) { var msg , len = unsavedchanges.length , i ; if(len==0) return null ; msg = 'You have ' + len + ' unsaved change' + (len==1?"":'s') ; if(len<=3) for(i=0;i<len;i++) msg += (i?',':' (') + unsavedchanges[i] + (i==len-1?')':'') ; msg += '\nIf you hit ' + (ok?'[OK]':'[Leave page]') ; return msg + (len==1?' this change':' these changes') + ' will be lost.' ; } /* -------------------------------------------------------------------------- */
/*page*/ /* --------------- selpoint: choose the clicked waypoint ------------------- */ function selpoint(event) { var i,j,closest,d,mindist,s0,s1 ; if(dragging) return ; var flag = (infowindow.close()=='wpinfo') && (event.shiftKey==0) ; if(overviewing==0&&event.shiftKey) { s0 = selected[0] ; s1 = segments[s0].data.length ; insert(s0,s1,1) ; segments[s0].data[s1].setpos(event.latLng) ; lookupalt(s0,s1) ; redrawconnect(s0,s1) ; done(['move',s0,s1,event.latLng,event.latLng,1]) ; } else for(s1=s0=-1,i=0;i<segments.length;i++) for(j=0;j<segments[i].data.length;j++) { d = dist(segments[i].data[j].pos,event.latLng) ; if(s0<0||d<mindist) { s0 = i ; s1 = j ; mindist = d ; } } walkto(s0,s1,flag) ; } /* -------------------------- track highlighter ---------------------------- */ function highlight() { var s0=selected[0],scroll,thind=null ; infowindow.close() ; undraw(s0) ; draw(s0,4) ; if(imginfo.uri!=null) thind = thumbind(imginfo.sizes) ; scroll = highdiv(segments[s0].props,imginfo.list,imginfo.sizes, thind,segments[s0].props.photo) ; scroller = scroll.scroller ; infowindow.open(scroll.div,northernmost(segments[s0].data),'highlight') ; } /* ------------------------------- getbtnpos -------------------------------- */ function getbtnpos(btnno) { var bounds=map.getBounds(),sw,ne,lat,lon,lam ; sw = bounds.getSouthWest() ; ne = bounds.getNorthEast() ; lam = 52.0 / window.innerHeight ; lat = lam*ne.lat() + (1-lam)*sw.lat() ; lam = 0.5 + (btnno*32-112.0)/window.innerWidth ; lon = lam*ne.lng() + (1-lam)*sw.lng() ; return new google.maps.LatLng(lat,lon) ; } /* ----- unambig: does the selected waypoint determine a unique segment? ---- */ function unambig() // does the selected waypoint determine a unique segment? { var s0=selected[0],s1=selected[1] ; if(segments.length==1) return 1 ; if( ( s0==segments.length-1 || s1!=segments[s0].data.length-1 || ! segments[s0].data[s1].pos.equals(segments[s0+1].data[0].pos) ) && ( s0==0 || s1!=0 || ! segments[s0].data[s1].pos.equals (segments[s0-1].data[segments[s0-1].data.length-1].pos) ) ) return 1 ; else return 0 ; } /* -------------------------------------------------------------------------- */
/*page*/ /* --------------------------------- walkto --------------------------------- */ // draw a selection point (and possibly an info box) at [s0,s1], bringing up // a wpinfo window if flag = 0 or a selinfo window if flag = 1 function walkto(s0,s1,flag) { var s='',i,ind,excuse,imgname=null,phind=-1,list=imginfo.list,sset ; var datum = segments[s0].data[s1] , pos = datum.pos ; if(flag==undefined) flag = 0 ; selected = [ s0,s1 ] ; if(overviewing) return highlight() ; map.panToBounds(new google.maps.LatLngBounds(pos,pos)) ; drawsel(0,[s0,s1]) ; if(flag||(datum.type==null&&datum.photo.length==0)) { if(flag==1) wpinfo() ; else if(flag==2) seginfo() ; else if(flag==3) highlight() ; return ; } if(datum.type!=null) { if(datum.photo.length>0) s = textbox ; else s = finalbox ; if(datum.type!='Generic') s += datum.type + ': ' ; s += datum.marker.title + ' [' + active + '"labelprompt()">Edit</span>]' ; s += '</div>' ; } for(ind=0;ind<datum.photo.length;ind++) { s += textbox ; if(imginfo.status=='ready'&&(phind=findimg(datum.photo[ind]))>=0) { sset = srcset(list[phind],imginfo.sizes,imginfo.thumbind) ; s += '<img src="' + jpg(list[phind],imginfo.sizes,imginfo.thumbind) + '" width=' + list[phind].thumbshape[0] + ' height=' + list[phind].thumbshape[1] + (sset==''?'':(' srcset="'+sset+'"')) + '><br>' + '<b>' + list[phind].name + '</b>: ' ; } else { if(imginfo.status=='null') excuse = 'no list provided' ; else if(imginfo.status=='ready') { imgname = imginfo.uri ; i = imgname.lastIndexOf('/') ; if(i>=0) imgname = imgname.substring(i+1) ; excuse = 'not present in ' + imgname ; } else if(imginfo.status=='waiting') excuse = imgname + ' is not available' ; else excuse = 'imginfo.status = ' + imginfo.status ; s += 'Photo: ' + datum.photo[ind] + ' (' + excuse + ') ' ; } s += '['+active+ '"photoedit('+ind+')">Edit</span>'+']' ; if(phind>=0) s += ' : ['+active+ '"phinfo('+phind+')">Info</span>'+']' + ' : ['+active+ '"display('+ind+')">Enlarge</span>'+']' ; s += '</div>' ; } if(datum.photo.length>0) s += finalbox + '[' + active + '"photoprompt' + '(null)">Add photo</span>' + ']</div>' ; infowindow.open(s,pos,'walking') ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------- keystroke handler ---------------------------- */ function keystroke(e) { var s0=selected[0],s1=selected[1],slast,flag ; if(e.keyCode==40&&overviewing==0) { map.panTo(segments[s0].data[s1].pos) ; return ; } flag = infowindow.close() ; if(flag=='highlight') flag = 3 ; else if(flag=='seginfo') flag = 2 ; else if(flag=='wpinfo') flag = 1 ; else flag = 0 ; if(e.keyCode==32) { selclick() ; return ; } // space if(overviewing) { if(e.keyCode==70) enterfullscreen() ; if(flag!=3||(e.keyCode!=39&&e.keyCode!=37&&e.keyCode!=8&&e.keyCode!=46)) return ; } if(e.keyCode==13) // return { if(dragging) undraggit() ; else if(s0>=0) draggit(0) ; return ; } if(dragging) return ; if(e.keyCode==8||e.keyCode==46) // delete/backspace { e.preventDefault() ; if((flag==3&&segments.length>1)||binbtn.active) discard() ; return ; } if(e.keyCode==9) { e.preventDefault() ; inswp(1) ; return ; } // tab if(e.keyCode==39) // forwards { e.preventDefault() ; if(flag>=2) { s1 = 0 ; s0 += 1 ; if(s0==segments.length) s0 = 0 ; } else if(s1<segments[s0].data.length-1) s1 += 1 ; else { s0 += 1 ; if(s0==segments.length) s0 = 0 ; s1 = 0 ; } } else if(e.keyCode==37) // backwards { e.preventDefault() ; if(flag>=2) { s1 = 0 ; s0 -= 1 ; if(s0<0) s0 = segments.length-1 ; } else if(s1>0) s1 -= 1 ; else { s0 -= 1 ; if(s0<0) s0 = segments.length-1 ; s1 = segments[s0].data.length-1 ; } } else return ; walkto(s0,s1,flag) ; } /* --------------------- undraw & redraw segments -------------------------- */ function undraw(i) { segments[i].route.setMap(null) ; if(segments[i].clickhandler!=null) { google.maps.event.removeListener(segments[i].clickhandler) ; segments[i].clickhandler = null ; } } function redraw(i) { undraw(i) ; draw(i) ; } function recolour(i) { if(overviewing) return ; else if(i&1) segments[i].route.setOptions({strokeColor:"#ff9999"}) ; else segments[i].route.setOptions({strokeColor:"#ff0000"}) ; } function obliterate(s0) // undraw route and all labels { var i ; for(i=0;i<segments[s0].data.length;i++) segments[s0].data[i].setmap(null) ; undraw(s0) ; disconnect(s0-1) ; disconnect(s0) ; } /* ----------------------------- draw segments ------------------------------ */ function draw(i,width) { var colour,poly ; if(overviewing) colour = segments[i].colour ; else if(i&1) colour = "#ff9999" ; else colour = "#ff0000" ; if(width==undefined) poly = new linepath(i,-1,0,colour) ; else poly = new linepath(i,-1,0,colour,width) ; segments[i].route = new google.maps.Polyline(poly) ; segments[i].route.setMap(map) ; if(segments[i].clickhandler==null) segments[i].clickhandler = google.maps.event.addListener(segments[i].route,"click",selpoint) ; } /* ----------------------- connect and disconnect segments ------------------ */ function disconnect(i) { if(overviewing||i<0||i>=segments.length-1||segments[i].dots==null) return ; segments[i].dots.setMap(null) ; if(segments[i].dothandler!=null) { google.maps.event.removeListener(segments[i].dothandler) ; segments[i].dothandler = null ; } } function reconnect(i) { disconnect(i) ; connect(i) ; } function connect(i) { if(overviewing||i<0||i>=segments.length-1) return ; var opos = segments[i].data[segments[i].data.length-1].pos ; var npos = segments[i+1].data[0].pos ; if(opos.equals(npos)) return ; segments[i].dots = new google.maps.Polyline(new dotpath(opos,npos)) ; segments[i].dots.setMap(map) ; segments[i].dothandler = google.maps.event.addListener(segments[i].dots,"click",selpoint) ; } function redrawconnect(s0,s1) { redraw(s0) ; if(s1==0) reconnect(s0-1) ; if(s1=segments[s0].data.length-1) reconnect(s0) ; } /* ---------------------- draw the selection point -------------------------- */ // note: there's no point in allowing clicking on a marker because the // event position is always the marker position rather than the click position function drawsel(opt,selection) { if(selection!=undefined) selected = selection ; var ind,clen,s0=selected[0],s1=selected[1],pos=segments[s0].data[s1].pos ; if(opt) reprofile() ; clen = segments[s0].data.length ; if(clen==1) arrow.rotation = 90 ; else { if(s1==clen-1) ind = s1-1 ; else ind = s1 ; icons.arrow.rotation = bearing(segments[s0].data[ind].pos,segments[s0].data[ind+1].pos) ; } if(sel.marker==null) sel.marker = new google.maps.Marker ({ position:pos, map:map, cursor:'default', icon:icons.arrow , zIndex:2 }) ; else // avoid unnecessary redraws { if(icons.arrow.rotation!=sel.orientation) sel.marker.setIcon(icons.arrow) ; if(!pos.equals(sel.marker.getPosition())) sel.marker.setPosition(pos) ; } sel.orientation = icons.arrow.rotation ; drawxcur(pro,selected) ; blackout(penbtn) ; if(segments.length>1&&unambig()) blackout(binbtn) ; else greyout(binbtn) ; if(s1!=0&&s1!=clen-1) blackout(scissorsbtn) ; else greyout(scissorsbtn) ; } /* ------------- selclick: respond to click of cursor button --------------- */ function selclick() { mouseopt = 1-mouseopt ; infowindow.close() ; if(mouseopt) { map.setOptions({draggable:false, draggableCursor:'default'}) ; if(overviewing==0) cursorbtn.btn.setAttribute('src','hand.png') ; clickhandle = google.maps.event.addListener(map,"click",selpoint) ; } else { map.setOptions({draggable:true, draggableCursor:''}) ; if(overviewing==0) cursorbtn.btn.setAttribute('src','arrow.png') ; google.maps.event.removeListener(clickhandle) ; } } /* -------------------------------------------------------------------------- */ function genhead(uri,key) { if(uri==undefined||uri==null) resuri = 'http://www.masterlyinactivity.com/routemaster/resources/' ; else { resuri = uri + '/' ; document.write('<script src="http://maps.google.com/maps/api/js?' + ((key==null||key==undefined)?'':('key='+key+'&')) + 'libraries=geometry"></scr' + 'ipt>') ; } document.write ('<script src="' + resuri + 'dms.js"></scr' + 'ipt>' + '<script src="' + resuri + 'vector3d.js"></scr' + 'ipt>' + '<script src="' + resuri + 'latlon-ellipsoidal.js"></scr' + 'ipt>' + '<script src="' + resuri + 'utm.js"></scr' + 'ipt>' + '<script src="' + resuri + 'osgridref.js"></scr' + 'ipt>' + '<script src="' + resuri + 'FileSaver.js"></scr' + 'ipt>' + '<script src="' + resuri + 'Blob.js"></scr' + 'ipt>' + '<script src="' + resuri + 'tcxlib.js"></scr' + 'ipt>' + '<script src="' + resuri + 'routemasterlib.js"></scr' + 'ipt>' + '<script src="http://www.masterlyinactivity.com/pixlib.js"></scr' + 'ipt>' + '<style type="text/css">html, body {width: 100%; height: 100%}' + 'body {margin:0px}a:link{color:#66aaaa}' + 'a:visited{color:#cc3388}a:active{color:#404040}</style>' + '</style><title>Routemaster</title>' + '<link rel="shortcut icon" href=' + resuri + 'bus.gif>' ) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------------------------------------------------- */ /* FUNCTIONS TO GENERATE THE INITIAL MAP */ /* -------------------------------------------------------------------------- */ function genpage() { var thispage=document.URL,xhttp,quotind,plusind,listxhttp,div ; imginfo = new listinfo() ; imgdiv = null ; elevator = new google.maps.ElevationService ; window.onload = function() { window.addEventListener("beforeunload",function(e) { var msg = unsavedmsg(0) ; if(msg==null) return undefined ; (e || window.event).returnValue = msg ; //Gecko + IE return msg ; //Gecko + Webkit, Safari, Chrome etc. (msg is ignored by ffx) } ) ; } ; body = document.getElementsByTagName("body")[0] ; while(body.childNodes.length>0) body.removeChild(body.childNodes[body.childNodes.length-1]) ; mapdiv = document.createElement('div') ; mapdiv.setAttribute('id','map') ; mapdiv.setAttribute('style','width:100%;height:100%;position:absolute') ; body.appendChild(mapdiv) ; if((quotind=thispage.indexOf('?'))>=0) { thispage = thispage.substring(quotind+1) ; if((plusind=thispage.indexOf('+'))>0) { getlist(thispage.substring(plusind+1),'uri') ; thispage = thispage.substring(0,plusind) ; } else if(thispage.substring(thispage.length-3)=='.js') { getlist(thispage,'uri') ; mapdiv.appendChild(filedialogue(0)) ; return ; } xhttp = new XMLHttpRequest() ; xhttp.onreadystatechange = function() { if(xhttp.readyState==4) { if(xhttp.status==200) { var x = parser.parseFromString(xhttp.responseText,"application/xml") ; render(x,thispage,0,'uri') ; } else alert("Unable to read "+thispage+": error code "+xhttp.status) ; } } xhttp.open("GET",thispage,true) ; xhttp.send() ; } else { mapdiv.appendChild(filedialogue(0)) ; div = blurbdiv(resuri) ; div.setAttribute('style','font-family:helvetica;margin:4px;'+ 'border-top:solid 1px silver;padding-top:2px') ; mapdiv.appendChild(div) ; div = helpdiv(resuri,1) ; div.setAttribute('style','font-family:helvetica;margin:4px;font-size:90%') ; mapdiv.appendChild(div) ; } } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------- getlist --------------------------------- */ function getlist(uri,imgtype) { var xhttp = new XMLHttpRequest(),list=null,sizes=null,pixpage=null,i,r ; var imagedir=null,thumbshape = [] ; imginfo.status = 'waiting' ; imginfo.type = imgtype ; xhttp.onreadystatechange = function() { if(xhttp.readyState==4) { if(xhttp.status!=200) { alert("Unable to read "+uri+": error code "+xhttp.status) ; return ; } eval(xhttp.responseText) ; imginfo.uri = uri ; imginfo.list = list ; imginfo.sizes = sizes ; imginfo.pixpage = reluri(uri,pixpage) ; imginfo.thumbind = thumbind(sizes) ; setthumbshape(list,sizes,thumbshape,reluri(uri,imagedir)) ; for(i=0;i<list.length;i++) if(list[i].retpage!=undefined) list[i].retpage = reluri(uri,list[i].retpage) ; imginfo.status = 'ready' ; } } xhttp.open("GET",uri,true) ; xhttp.send() ; } /* ----------------------------- file dialogue ------------------------------ */ function filedialogue(overwrite) { var input = document.createElement('input') ; var para = document.createElement('p') ; para.appendChild(document.createTextNode ('\u00a0\u00a0\u00a0Select TCX/GPX file: ')) ; input.setAttribute('type','file') ; input.setAttribute('accept','.tcx,.gpx') ; input.addEventListener('change',function(e) { reader = new FileReader() ; reader.onload = function(e) { var xmldoc = parser.parseFromString(reader.result,"application/xml") ; render(xmldoc,input.files[0].name,overwrite,'file') ; } reader.readAsText(input.files[0]) ; } ) ; para.appendChild(input) ; return para ; } /* -------------------------------------------------------------------------- */
/*page*/ /* --------------------- set up the map and buttons ------------------------- */ function render(xmldoc,filename,overwrite,origin) { var i,opts,centre,lat,lon,minlon,maxlon,minlat,maxlat,newseg,s0 ; var colours,segno,istcx ; infowindow.close() ; document.onkeydown = keystroke ; xmlfile = filename ; // read data i = filename.length ; if(filename.substring(i-4,i).toLowerCase()=='.tcx') istcx = 1 ; else if(filename.substring(i-4,i).toLowerCase()=='.gpx') istcx = 0 ; else { alert(filename+' is neither .tcx nor .gcx') ; throw '' ; } if(istcx) newseg = readtcx(xmldoc) ; else newseg = readgpx(xmldoc) ; if(newseg.segments.length==0||newseg.segments[0].length==0) { alert('no data returned') ; return ; } if(newseg.segments.length>1&&setbtn!=null) { alert('trying to add a multitrack overview... not permitted') ; return ; } if(newseg.segments.length==1) for(i=0;i<newseg.segments[0].length;i++) if(newseg.segments[0][i].h==null) { alert(newseg.segments[0][i].pos+' has no altitude... unable to proceed') ; return ; } // check and process photo list if(imginfo.type!='uri') for(i=0;i<newseg.segments.length;i++) if(newseg.props[i].list!=null) { if(imginfo.uri!=null&&newseg.props[i].list!=imginfo.uri) { alert('inconsistent photo lists: ' + imginfo.uri + ' and ' + newseg.props[i].list) ; return ; } imginfo = new listinfo() ; getlist(newseg.props[i].list,'tcx') ; } if(overwrite) { for(i=0;i<segments.length;i++) obliterate(i) ; unprofile() ; if(sel.marker!=null) sel.marker.setMap(null) ; segments = [] ; if(imginfo.type=='tcx') imginfo = new listinfo() ; } s0 = segments.length ; if(s0==0) { sel = { marker:null, orientation: null } ; pending = [] ; xpending = [] ; actions = [] ; unsavedchanges = [] ; nactions = dragging = 0 ; loadno = -1 ; pro = routetitle = null ; } if(newseg.segments.length>1) { overviewing = 1 ; colours = gencolours(newseg.segments.length) ; } if(istcx&&routetitle==null&&newseg.title!=null) settitle(newseg.title) ; // process the new segments for(i=0;i<newseg.segments.length;i++) { newseg.props[i].source = [ filename , origin ] ; if(longtitle==null) longtitle = newseg.props[i].longtitle ; if(overview==null) overview = newseg.props[i].overview ; if(routetitle==null||routetitle=='Untitled Route') if(newseg.props[i].title!=null) settitle(newseg.props[i].title) ; segments.push(new genseg(newseg.segments[i],newseg.props[i])) ; if(overviewing) segments[segments.length-1].colour = colours[i] ; actions[nactions++] = [ 'load',s0+i,newseg.segments[i].slice(),loadno,newseg.props[i] ] ; loadno = nactions-1 ; } if(routetitle==null) settitle('Untitled Route') ; if(!newseg.props[0].optim.already&&!overviewing) optimaction(segments.length-1,defparms,0) ; // find max and min lat and long - have to look at all segs, not just // newly loaded to avoid google's repeatedly adding a margin for(maxlat=null,segno=0;segno<segments.length;segno++) for(i=0;i<segments[segno].data.length;i++) { lat = segments[segno].data[i].pos.lat() ; lon = segments[segno].data[i].pos.lng() ; if(maxlat==null||lon<minlon) minlon = lon ; if(maxlat==null||lon>maxlon) maxlon = lon ; if(maxlat==null||lat<minlat) minlat = lat ; if(maxlat==null||lat>maxlat) maxlat = lat ; } if(s0==0) centre = new google.maps.LatLng((minlat+maxlat)/2,(minlon+maxlon)/2) ; if(map==null) // all this only done on first call { opts = { zoom: 22, center: centre, scaleControl: true, rotateControl: false, streetViewControl: false, mapTypeId: google.maps.MapTypeId.TERRAIN, disableDoubleClickZoom: true, styles: [ { "featureType": "poi", "stylers": [{ "visibility": "off" }] } ], mapTypeControl:true, mapTypeControlOptions: { style:google.maps.MapTypeControlStyle.HORIZONTAL_BAR }, mapTypeIds: [ google.maps.MapTypeId.ROADMAP, google.maps.MapTypeId.TERRAIN, google.maps.MapTypeId.SATELLITE ] } ; map = new google.maps.Map(mapdiv,opts) ; // set up buttons if(overviewing==0) { setbtn = genbutton('settings') ; cursorbtn = genbutton('cursor') ; cursorbtn.ui.addEventListener('click',selclick) ; scissorsbtn = genbutton('scissors') ; binbtn = genbutton('bin') ; penbtn = genbutton('pen') ; undobtn = genbutton('undo') ; redobtn = genbutton('redo') ; dlbtn = genbutton('dl') ; } selclick() ; } map.fitBounds(new google.maps.LatLngBounds (new google.maps.LatLng(minlat,minlon), new google.maps.LatLng(maxlat,maxlon))) ; for(segno=s0;segno<segments.length;segno++) for(i=0;i<segments[segno].data.length;i++) segments[segno].data[i].setmap(map) ; if(nactions>1) donesomething() ; // specifically, done loading & optimisation else actions.length = nactions ; // load with no optimisation hence no undo if(s0==0) { selected = [0,0] ; if(overviewing==0) drawsel(1) ; } else greyout(dlbtn) ; for(segno=s0;segno<segments.length;segno++) { draw(segno) ; connect(segno-1) ; } connect(segments.length-1) ; reprofile() ; } /* ------------------------------- settitle --------------------------------- */ function settitle(newtitle) { routetitle = newtitle ; var h = document.getElementsByTagName('title')[0] ; while(h.firstChild) h.removeChild(h.firstChild) ; h.appendChild(document.createTextNode(routetitle)) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ------------------------------- retitle ---------------------------------- */ function retitle() { infowindow.close() ; var response = window.prompt("Modify title: (max 15 chars)",routetitle) ; if(response==null) return ; else response = response.substring(0,15) ; if(response==routetitle) return ; settitle(response) ; actions[nactions++] = [ 'edittitle' , routetitle , response ] ; donesomething() ; } /* ----------------------------- longretitle -------------------------------- */ function longretitle() { var response ; infowindow.close() ; if(longtitle==null) response = window.prompt("Add long title:","") ; else response = window.prompt("Modify long title:",longtitle) ; if(response==null||response==longtitle) return ; longtitle = response ; actions[nactions++] = [ 'editlongtitle' , longtitle , response ] ; donesomething() ; } /* ------------------------------- genbutton -------------------------------- */ function genbutton(name) { var u,v,w,b,g,k,h,div=document.createElement('div'),act ; u = document.createElement('div') ; u.style.backgroundColor = '#ffffff' ; u.style.border = '2px solid #ffffff' ; u.style.borderRadius = '3px' ; u.style.boxShadow = '0 2px 6px rgba(0,0,0,.3)' ; if(name=='dl'||name=='settings'||name=='cursor') u.style.cursor = 'pointer' ; else u.style.cursor = 'default' ; u.style.marginBottom = '12px' ; if(name!='dl') u.style.marginRight = '4px' ; u.style.textAlign = 'center' ; div.appendChild(u) ; if(name=='scissors') { h = snip ; div.index = 3 ; } else if(name=='bin') { h = discard ; div.index = 4 ; } else if(name=='pen') { h = labelprompt ; div.index = 5 ; } else if(name=='undo') { h = undo ; div.index = 6 ; } else if(name=='redo') { h = redo ; div.index = 7 ; } else if(name=='dl') { h = function() { dl(0) ; } ; div.index = 8 ; } else if(name=='settings') { h = popup ; div.index = 1 ; } else if(name=='cursor') { h = null ; div.index = 2 ; } g = greybtn(resuri,name) ; k = blackbtn(resuri,name) ; if(name=='dl'||name=='settings'||name=='cursor') b = buttonimg(k) ; else b = buttonimg(g) ; u.appendChild(b) ; map.controls[google.maps.ControlPosition.BOTTOM_CENTER].push(div) ; if(name=='dl'||name=='settings') u.addEventListener('click',h) ; if(name=='dl'||name=='settings') act = 1 ; else act = 0 ; return { ui:u , btn:b , active:act , greyimg:g , blackimg:k , handler:h } ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------------------------------------------------- */ /* OPTIMISATION */ /* -------------------------------------------------------------------------- */ function optimaction(segno,parms,force) { var s = segments[segno], result = optimise(s.data,parms) ; var ndel = s.data.length - result.length ; if((force==0&&ndel<s.data.length/2)||ndel==0) return 0 ; actions[loadno][4].optim.ndel = ndel ; actions[nactions++] = [ 'optimise' , segno , parms ] ; segments[segno] = new genseg(result,segments[segno].props) ; actions[loadno][4].optim.parms = { tol: parms.tol , maxsep: parms.maxsep , wppenalty: parms.wppenalty , vweight: parms.vweight } ; return 1 ; } /* -------------------------------------------------------------------------- */ function optimprompt() { var msg = 'Enter 4 optimisation parms (tol, maxsep, wppenalty and vweight)' ; var parmstr = defparms.tol + ' ' + defparms.maxsep.toFixed(0) + ' ' + defparms.wppenalty.toFixed(0) + ' ' + defparms.vweight.toFixed(1) ; var parms,i ; infowindow.close() ; for(i=0;;i++) { newparms = prompt(msg,parmstr) ; if(newparms==null) return ; if(newparms=='') { parms = defparms ; break ; } newparms = newparms.split(' ') ; if(newparms.length==0) { parms = defparms ; break ; } parms = { tol: parseFloat(newparms[0]) , maxsep: parseFloat(newparms[1]) , wppenalty: parseFloat(newparms[2]) , vweight: parseFloat(newparms[3]) } ; if( isNaN(parms.tol)==0 && isNaN(parms.maxsep)==0 && isNaN(parms.wppenalty)==0 && isNaN(parms.vweight)==0 ) break ; if(i==0) msg = '*** Illegal parms ***\n' + msg ; } if(optimaction(segments.length-1,parms,1)) { donesomething() ; draw(segments.length-1) ; } routeinfo() ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------------------------------------------------- */ /* THE MAIN POPUP MENU AND THE FUNCTIONS IT GOVERNS */ /* -------------------------------------------------------------------------- */ function popup() { var opts,pos,s0=selected[0],s1=selected[1] ; infowindow.close() ; if(dragging) { opts = finalbox + "Hit [return] when you've finished dragging.</div>" ; infowindow.open(opts,getbtnpos(0),'settings') ; return ; } // route options opts = textbox + active + '"routeinfo()">Route info</span><br>' ; if(pro==null||pro.prodiv==null) opts += active + '"drawprofile()">Show altitude profile</span><br>' ; else opts += active + '"unprofile()">Hide altitude profile</span><br>' ; opts += active + '"addload(1)">Load new route</span><br>' ; if(overview!=null) opts += '<a style="cursor:pointer;color:#0000bd;text-decoration:none" '+ 'href="' + overview + '" target="_blank">' + 'View routes ' + 'overview</a>'+neutral+' (opens in new tab/window)</span><br> ' ; opts += active + '"dl(1)">Download track as overview</span></div>' ; // segment options opts += textbox + active + '"seginfo()">Segment info</span><br>' ; if(unambig()) opts += active + '"revseg()">' ; else opts += inactive ; opts += 'Reverse segment</span><br>' ; opts += active + '"manualcal()">Calibrate segment altitudes</span><br>' ; opts += active + '"addload(0)">Load route as a new segment</span></div>' ; // waypoint options opts += textbox + active + '"wpinfo()">Waypoint info</span><br>' ; if(segments[selected[0]].data.length>1) opts += active + '"wpdel()">' ; else opts += inactive ; opts += 'Delete waypoint</span><br>' ; opts += active + '"draggit(0)">Make waypoint draggable</span><br>' ; opts += active + '"inswp(1)">Insert draggable waypoint ahead</span><br>' ; opts += active + '"inswp(-1)">Insert draggable waypoint behind</span>' ; opts += '</div>'+finalbox+active ; // tool options if(querycanfullscreen()) { if(queryfullscreen()==0) opts += '"enterFullscreen()">Enter full screen</span><br>' + active ; else opts += '"exitFullscreen()">Leave full screen</span><br>' + active ; } opts += '"help()">Help</span></div>' ; infowindow.open(opts,getbtnpos(0),'settings') ; } /* ------------------------------- calwork --------------------------------- */ function calwork(s0,y) { var i,s1 ; for(s1=0;s1<segments[s0].data.length;s1++) if(segments[s0].data[s1].h!=null) segments[s0].data[s1].h += y ; reprofile() ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ------------------------------ manualcal --------------------------------- */ function manualcal() { infowindow.close() ; var x,y,s0=selected[0] ; x = prompt('Enter offset in metres to add to altidudes:') ; if(x==null) return ; y = parseFloat(x) ; if(isNaN(y)) { alert(x+' is not a number') ; return ; } calwork(s0,y) ; done(['recal',s0,y]) ; } /* --------------------------------- help ----------------------------------- */ function help() { infowindow.close() ; infowindow.open(helpdiv(resuri),getbtnpos(0),'help') ; } /* --------------------------------- wpdel ---------------------------------- */ function wpdelwork(s0,s1) { var i,response=segments[s0].data[s1],clen=segments[s0].data.length ; response.setmap(null) ; for(i=s1;i<clen-1;i++) segments[s0].data[i] = segments[s0].data[i+1] ; segments[s0].data.length = clen-1 ; selected = [s0,s1] ; if(s1==segments[s0].data.length) selected[1] -= 1 ; redrawconnect(s0,s1) ; drawsel(1) ; return response ; } function wpdel() { var s0=selected[0],s1=selected[1],i ; infowindow.close() ; done(['wpdel',s0,s1,wpdelwork(s0,s1)]) ; } /* --------------------------------- revseg --------------------------------- */ function revsegwork(s0) { var i,s=segments[s0],j,x,len=s.data.length ; disconnect(s0-1) ; disconnect(s0) ; for(i=0;i<len/2;i++) { j = (len-1) - i ; x = s.data[i] ; s.data[i] = s.data[j] ; s.data[j] = x ; } for(i=0;i<s.data.length;i++) if(s.data[i].type=='Right') s.data[i].settype('Left') ; else if(s.data[i].type=='Left') s.data[i].settype('Right') ; if(s0==selected[0]) selected[1] = (len-1) - selected[1] ; connect(s0-1) ; connect(s0) ; drawsel(1) ; } /* -------------------------------------------------------------------------- */ function revseg() { infowindow.close() ; revsegwork(selected[0]) ; done(['revseg',selected[0]]) ; } /* -------------------------------------------------------------------------- */ function addload(overwrite) { var msg ; infowindow.close() ; if(overwrite) { msg = unsavedmsg(1) ; if(msg!=null) if(!confirm(msg)) return ; } infowindow.open(filedialogue(overwrite),getbtnpos(0),'addload') ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------------------------------------------------- */ /* DRAGGING A (POSSIBLY NEWLY INSERTED) WAYPOINT IS QUITE A LOT OF WORK */ /* -------------------------------------------------------------------------- */ function insert(s0,s1,n) { var i ; for(i=segments[s0].data.length+n-1;i>s1;i--) segments[s0].data[i] = segments[s0].data[i-n] ; for(i=0;i<n;i++) segments[s0].data[s1+i] = new datatype(null,null) ; } /* --------------------------------- inswp ---------------------------------- */ function inswp(dir) { var s0=selected[0],s1=selected[1],bounds,del,pos,data=segments[s0].data ; var len = data.length ; if(len==1) pos = data[0].pos ; if(dir>=0) s1 = selected[1] += 1 ; insert(s0,s1,1) ; if(len==1) { bounds = map.getBounds() ; del = bounds.getNorthEast().lng() - bounds.getSouthWest().lng() ; pos = new google.maps.LatLng(pos.lat(),pos.lng()+dir*del/10) ; } else if(s1==0) pos = interp(data[2].pos,data[1].pos,1.5) ; else if(s1<len) pos = interp(data[s1-1].pos,data[s1+1].pos,0.5) ; else pos = interp(data[s1-2].pos,data[s1-1].pos,1.5) ; data[s1].setpos(pos) ; draggit(1) ; } /* -------------------------------- draggit --------------------------------- */ // draggit makes the current waypoint draggable var l1,l2,startpos,seg0,seg1,seg2,colour,inserted ; function draggit(insparm) { var s0=selected[0],s1=selected[1],start,end,i,len=segments[s0].data.length ; startpos = segments[s0].data[s1].pos ; inserted = insparm ; infowindow.close() ; greyout(scissorsbtn) ; greyout(binbtn) ; greyout(penbtn) ; greyout(undobtn) ; greyout(redobtn) ; greyout(dlbtn) ; map.panToBounds(new google.maps.LatLngBounds(startpos,startpos)) ; sel.marker.setMap(null) ; sel.marker = new google.maps.Marker( { position: segments[s0].data[s1].pos, map: map, cursor: 'default', icon: icons.concircle , draggable: true , zIndex: 2 } ) ; if(s0&1) colour = "#ff9999" ; else colour = "#ff0000" ; segments[s0].route.setMap(null) ; if(segments[s0].clickhandler!=null) { google.maps.event.removeListener(segments[s0].clickhandler) ; segments[s0].clickhandler = null ; } seg0 = seg2 = null; if(s1>1) { seg0 = new google.maps.Polyline(new linepath(s0,0,s1,colour)) ; seg0.setMap(map) ; } if(s1==0) start = 0 ; else start = s1-1 ; if(s1==len-1) end = s1+1 ; else end = s1+2 ; seg1 = new google.maps.Polyline(new linepath(s0,start,end,colour)) ; seg1.setMap(map) ; if(s1<segments[s0].data.length-2) { seg2 = new google.maps.Polyline(new linepath(s0,s1+1,len,colour)) ; seg2.setMap(map) ; } l1 = google.maps.event.addListener(sel.marker,'drag',function() { segments[s0].data[s1].setpos(this.getPosition()) ; seg1.setMap(null) ; seg1 = new google.maps.Polyline(new linepath(s0,start,end,colour)) ; seg1.setMap(map) ; if(s1==0&&s0>0) { disconnect(s0-1) ; connect(s0-1) ; } if(s1==len-1&&s0<segments.length-1) { disconnect(s0) ; connect(s0) ; } } ) ; dragging = 1 ; } /* ------------------------------- undraggit -------------------------------- */ // undraggit is invoked by [return] to terminate waypoint dragging function undraggit() { var s0=selected[0],s1=selected[1],i,s1dash,pos=segments[s0].data[s1].pos ; var xpos ; google.maps.event.removeListener(l1) ; dragging = 0 ; if(seg0!=null) seg0.setMap(null) ; seg1.setMap(null) ; if(seg2!=null) seg2.setMap(null) ; segments[s0].route = new google.maps.Polyline(new linepath(s0,-1,0,colour)) ; segments[s0].route.setMap(map) ; segments[s0].data[s1].h = null ; lookupalt(s0,s1) ; sel.marker.setMap(null) ; sel.marker = null ; // force a redraw drawsel(1) ; if(inserted||dist(startpos,pos)>5) done(['move',s0,s1,startpos,pos,inserted]) ; if(segments.length==1) blackout(dlbtn) ; } /* -------------------------------------------------------------------------- */ function seginfo() { var pos = segments[selected[0]].data[selected[1]].pos ; infowindow.close() ; infowindow.open(seginfodiv(segments,selected[0]),pos,'seginfo') ; } /* -------------------------------------------------------------------------- */ function deltimes() { var s0,s1,task=[] ; for(s0=0;s0<segments.length;s0++) for(s1=0;s1<segments[s0].data.length;s1++) if(segments[s0].data[s1].t!=null) { task.push([s0,s1,segments[s0].data[s1].t]) ; segments[s0].data[s1].t = null ; } infowindow.close() ; done(['deltimes',task]) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------------------------------------------------- */ /* CODE TO GET ALTITUDES FOR NEWLY INSERTED POINTS */ /* -------------------------------------------------------------------------- */ function lookupalt(s0,s1) // set up a request for the alt of the new point { var hi,lo,segno=s0,ptno=s1,lopos,hipos,datum=segments[s0].data[s1] ; for(lo=null,s0=segno,s1=ptno-1;lo==null;s1--) { if(s1<0) { s0 -= 1 ; if(s0<0) break ; s1 = segments[s0].data.length-1 ; } if(segments[s0].data[s1].h!=null) lo = [s0,s1] ; } for(hi=null,s0=segno,s1=ptno+1;hi==null;s1++) { if(s1==segments[s0].data.length) { s0 += 1 ; if(s0==segments.length) break ; s1 = 0 ; } if(segments[s0].data[s1].h!=null) hi = [s0,s1] ; } if(lo==null&&hi==null) { alert('no points left with altitudes: unable to proceed') ; throw '' ; } if(lo!=null) lopos = segments[lo[0]].data[lo[1]].pos ; if(hi!=null) hipos = segments[hi[0]].data[hi[1]].pos ; if(lo==null||(hi!=null&&dist(lopos,datum.pos)>dist(hipos,datum.pos))) { lo = hi ; lopos = hipos ; } pending.push([datum,lopos,segments[lo[0]].data[lo[1]].h]) ; elevator.getElevationForLocations({locations:[datum.pos,lopos]},calibrate) ; } /* -------------------------------------------------------------------------- */ // pending is the list of inserted points for which google altitudes are needed // note a pitfall with the elevation service - it's hard to tell which // response corresponds to which request: the coordinates may not match // because google truncates to 0.00001 deg; hence the use of the dist function. function calibrate(results,status) { var pno,flag ; if(status===google.maps.ElevationStatus.OVER_QUERY_LIMIT) alert('No calibration data available: over Google query limit') ; else if(status!==google.maps.ElevationStatus.OK||results.length!=2) alert('Calibration error') ; // now find whether any of our elevation results allow us to fix an altitude for(pno=0;pno<pending.length;pno++) if( dist(pending[pno][0].pos,results[0].location)<5 && dist(pending[pno][1],results[1].location)<5 ) { if(pno>0) alert('Warning: Google elevation results out of sequence') ; diff = results[0].elevation - results[1].elevation ; pending[pno][0].h = pending[pno][2] + diff ; } for(i=pno=0;pno<pending.length;pno++) if(pending[pno][0].h==null) { if(pno!=i) pending[i] = pending[pno] ; i += 1 ; } pending.length = i ; } /* -------------------------------------------------------------------------- */ /* FUNCTIONS FOR HANDLING THE ALTITUDE PROFILE */ /* -------------------------------------------------------------------------- */ function drawprofile() { infowindow.close() ; if((pro=procoords(segments))==null) return ; drawpro(pro) ; body.appendChild(pro.prodiv) ; body.appendChild(pro.curdiv) ; drawxcur(pro,selected) ; } /* ------------------------------- unprofile -------------------------------- */ function unprofile() { var i,match,node ; infowindow.close() ; if(pro==null||pro.prodiv==null) return ; pro.curdiv.removeEventListener('click',pro.curhandle) ; for(match=0,i=body.childNodes.length-1;match==0&&i>=0;i--) { node = body.childNodes[i] ; match = (node==pro.prodiv) ; body.removeChild(node) ; } pro = null ; } function reprofile() { if(pro!=null&&pro.prodiv!=null) { unprofile() ; drawprofile() ; } } /* -------------------------------------------------------------------------- */ /* ROUTEINFO IS A MENU WHICH ITSELF SUPPORTS THE COMBINE FUNCTION */ /* -------------------------------------------------------------------------- */ function routeinfo() { var s0,s1,s,d,asc,des,oalt,alt,nlabels,nowpts,i,unsaved,props,spacing,npix ; var maxsep,sep,outoforder,tlast,otime,time,tdist,ttime,ntimes ; infowindow.close() ; props = actions[loadno][4] ; tlast = null ; tdist = ttime = outoforder = 0 ; maxsep = nlabels = npix = des = asc = d = nowpts = ntimes = 0 ; for(s0=0;s0<segments.length;s0++) { nowpts += segments[s0].data.length ; for(oalt=null,s1=0;s1<segments[s0].data.length;otime=time,s1++) { if((alt=segments[s0].data[s1].h)!=null) { if(oalt!=null) { if(alt>oalt) asc += alt-oalt ; else des += oalt - alt ; } oalt = alt ; } if(segments[s0].data[s1].type!=null) nlabels += 1 ; npix += segments[s0].data[s1].photo.length ; time = segments[s0].data[s1].t ; if(time!=null) { time = time.getTime() ; ntimes += 1 ; } if(tlast!=null&&time!=null&&time<tlast) outoforder = 1 ; // out of order if(time!=null) tlast = time ; if(s1) { sep = dist(segments[s0].data[s1-1].pos,segments[s0].data[s1].pos) ; d += sep ; if(sep>maxsep) maxsep = sep ; if(time!=null&&otime!=null) { tdist += sep/1000 ; ttime += (time-otime)/(3600*1000) ; } } } } s = finalbox +'<nobr>Title: <b>'+routetitle+'</b> [' ; s += active + '"retitle()">Edit</span>' + ']</nobr><br>' ; if(longtitle==null) s += '<nobr>[' + active + '"longretitle()">' + 'Add long title</span>]</nobr><br>' ; else { if(longtitle.length<50) s += '<nobr>Long title: <b>' + longtitle + '</b>' ; else s += '<div style="margin-bottom:2px;border-bottom:solid 1px silver;' + 'padding-bottom:2px">Long title: ' + longtitle ; s += ' [' + active + '"longretitle()">Edit</span>]' ; if(longtitle.length<50) s += '</nobr><br>' ; else s += '</div>' ; } if(loadno>0) { s += '<nobr>&nbsp;&nbsp;&nbsp;Last added route' ; if(props.title!=null) s += ' (' + props.title + ')' ; s += ':</nobr><br><nobr>&nbsp;&nbsp;&nbsp;' ; } else s += '<nobr>' ; s += 'Track points on input: ' + props.inputlen ; if(props.optim.already) { s += ' (previously optimised)</nobr>' ; if(nowpts!=props.inputlen) s += '<br>Now ' + nowpts + ' track points' ; } else if(props.optim.ndel==0) { if(nactions==loadno+1) s += ' [' + active + '"optimprompt()">' ; else s += ' [' + inactive ; s += 'Optimise' + neutral + ']</span></nobr>' ; } else s += ', optimised to ' + (props.inputlen-props.optim.ndel) + '</nobr>' ; if(!props.optim.already&&props.inputlen-props.optim.ndel!=nowpts) s += '<br>Now ' + nowpts + ' track points' ; s += '<br>' ; if(outoforder==0) { if(ntimes==0) s += 'No timings provided<br>' ; else { s += '<nobr>' ; if(ntimes<nowpts) s += (nowpts-ntimes) + ' points have no associated timings ' ; s += '[' + active + '"deltimes()">Discard timings</span>' + neutral + ']<nobr><br>' ; } if(tdist>0&&ttime>0) s += 'Average speed = ' + (tdist/ttime).toFixed(1) + ' km/hr<br>' ; } else s += 'Times are out of sequence (will be discarded on download)<br>' ; if(nlabels>0) s += nlabels + ' labelled course point' + (nlabels>1?'s':'') + '<br>' ; if(npix>0) s += npix + ' photo' + (npix>1?'s':'') + '<br>' ; unsaved = unsavedchanges.length ; if(unsaved>0) s += unsaved + ' unsaved change' + (unsaved>1?'s':'') + '<br>' ; if(segments.length>1) s += segments.length + ' segments [' + active + '"combine()">Combine</span>' + neutral + ']<br>' + '<i>Note that segments must be combined before saving</i><br>' ; s += 'Max waypoint separation: '+maxsep.toFixed(0)+'m<br>' ; if(maxsep>=100) s += '<i>Note that separations &gt;100m are illegal on Garmin</i><br>' + '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[</span>' + active + '"extrapts()">' + 'Interpolate extra points</span>]<br>' + neutral ; s += 'Total distance: '+(d/1000).toFixed(3)+'km<br>' ; s += 'Total ascent: '+asc.toFixed(0)+'m<br>' ; s += 'Total descent: '+des.toFixed(0)+'m</div>' ; s += '</div>' ; infowindow.open(s,getbtnpos(0),'routeinfo') ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ------------------------- interpolate extra points ----------------------- */ function extrapts(opt) { var s0,s1,sep,data,n,opos,npos,i,lambda,lox,nlox,taskno,ind ; var task = [ 'extra' , selected[0] , selected[1] ] ; infowindow.close() ; for(nlox=s0=0;s0<segments.length;s0++) for(data=segments[s0].data,s1=1;s1<data.length;s1++) if((sep=dist(opos=data[s1-1].pos,npos=data[s1].pos))>100) { n = Math.floor(sep/95) ; insert(s0,s1,n) ; for(i=0;i<n;i++) { lambda = (i+1) / (n+1) ; data[s1+i].setpos(new google.maps. LatLng(lambda*npos.lat()+(1-lambda)*opos.lat(), lambda*npos.lng()+(1-lambda)*opos.lng())) ; } if(selected[0]==s0&&s1<=selected[1]) selected[1] += n ; task.push([s0,s1,data.slice(s1-1,s1+n+1)]) ; s1 += n ; nlox += n+2 ; } if(nlox==0) return ; done(task) ; xpending.push(task) ; lox = new Array(nlox) ; for(ind=0,taskno=3;taskno<task.length;taskno++) { n = task[taskno][2].length ; for(i=0;i<n;i++) lox[ind++] = task[taskno][2][i].pos ; } elevator.getElevationForLocations( {locations:lox} , function (results,status) { // assume that the results come in sequence, ie. correspond to xpending[0] var task=xpending.shift(),taskno,d0,dn,lambda ; if(status===google.maps.ElevationStatus.OVER_QUERY_LIMIT) alert('No calibration data available: over Google query limit') ; else if(status!==google.maps.ElevationStatus.OK) alert('Calibration error') ; for(ind=0,taskno=3;taskno<task.length;taskno++,ind+=n+2) { n = task[taskno][2].length-2 ; d0 = task[taskno][2][0].h - results[ind].elevation ; dn = task[taskno][2][n+1].h - results[ind+n+1].elevation ; if( dist(task[taskno][2][0].pos,results[ind].location)>5 || dist(task[taskno][2][n+1].pos,results[ind+n+1].location)>5 ) alert('Anomaly with Google elevation results') ; for(i=0;i<n;i++) { lambda = (i+1) / (n+1) ; task[taskno][2][1+i].h = results[ind+1+i].elevation + lambda*dn + (1-lambda)*d0 ; } } } ) ; routeinfo() ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ------------------------------- combine1 --------------------------------- */ function combine1(sa,sb) { var i,ca,cb,calen,cblen,la=null,lb=null,cdup=0 ; undraw(sb) ; disconnect(sb-1) ; calen = segments[sa].data.length ; cblen = segments[sb].data.length ; cb = segments[sb].data[0].pos ; cdup = ( ca = cb.equals(segments[sa].data[calen-1].pos) ) ; if(cdup) { la = segments[sa].data[calen-1] ; lb = segments[sb].data[0] ; segments[sa].data.length = ( calen -= 1 ) ; } if(selected[0]==sb) selected = [ sa , selected[1]+calen ] ; segments[sa].data = segments[sa].data.concat(segments[sb].data) ; return [ cblen , cdup , la , lb ] ; } function combinework() { var task,s0 ; for(task=['combine',segments.length],s0=1;s0<segments.length;s0++) task.push(combine1(0,s0)) ; segments.length = 1 ; return task ; } /* -------------------------------------------------------------------------- */ function combine() { infowindow.close() ; done(combinework()) ; drawsel(1) ; redraw(0) ; blackout(dlbtn) ; } function recombine() { var s0 ; for(s0=1;s0<segments.length;s0++) { undraw(s0) ; combine1(0,s0) ;} segments.length = 1 ; drawsel(1) ; redraw(0) ; blackout(dlbtn) ; } /* -------------------------------------------------------------------------- */ // combine returns [ cblen , cdup , la , lb ] ; function uncombine(task) { var i,j,llen,flag,subtask ; for(flag=0,s0=task[1]-1,i=task.length-1;i>=2;i--,s0--) { subtask = task[i] ; cblen = subtask[0] ; cdup = subtask[1] ; llen = segments[0].data.length ; segments[s0] = { data: segments[0].data.slice(llen-cblen,llen) , route: null , clickhandler: null } ; llen = segments[0].data.length = llen+cdup-cblen ; if(cdup) { segments[0].data[llen-1] = subtask[2] ; segments[s0].data[0] = subtask[3] ; } if(flag==0&&selected[1]>=llen) { selected = [ s0 , selected[1]-llen ] ; flag = 1 ; } } drawsel(1) ; undraw(0) ; for(i=0;i<segments.length;i++) { draw(i) ; connect(i) ; } greyout(dlbtn) ; } /* -------------------------------------------------------------------------- */ /* WPINFO IS A MENU GIVING ACCESS TO THE SETALT FUNCTION */ /* -------------------------------------------------------------------------- */ function wpinfo() { infowindow.close() ; var s0=selected[0],s1=selected[1],alt,nalt,s,x,lat,lng,grad,gradstr,time ; var datum = segments[s0].data[s1] , pos = datum.pos ; lat = pos.lat() ; lng = pos.lng() ; s = finalbox ; if(lat>=0) s += lat.toFixed(5) + '\u00b0 N, ' ; else { lat = -lat ; s += lat.toFixed(5) + '\u00b0 S, ' ; } if(lng>=0) s += lng.toFixed(5) + '\u00b0 E<br>' ; else { lng = -lng ; s += lng.toFixed(5) + '\u00b0 W<br>' ; } x = new LatLon(lat,lng) ; if(lat>49.9&&lat<62&&lng>-12&&lng<2.5&&lat-1.5*lng<75) s += 'OS grid ref: ' + OsGridRef.latLonToOsGrid(x) ; else s += 'UTM coords = ' + x.toUtm() ; s += '<br>' ; alt = segments[s0].data[s1].h ; if(alt!=null) s += 'Altitude: ' + alt.toFixed(0) + 'm ' + active + '"setalt(1)">[Edit]' ; else s += active + '"setalt(0)">Set altitude' ; s += '</span><br>' ; time = segments[s0].data[s1].t ; if(time!=null&&time.getFullYear()>1980) s += 'Date: ' + time.toDateString() + '<br>' + 'Time: ' + time.toTimeString() + '<br>' ; if(datum.type!=null) s += datum.type + ': ' + datum.marker.title + ' [' + active + '"labelprompt()">Edit</span>]<br>' ; if(alt==null||s1==segments[s0].data.length-1) nalt = null ; else { nalt = segments[s0].data[s1+1].h ; if(nalt!=null) x = dist(pos,segments[s0].data[s1+1].pos) ; } if(nalt!=null&&Math.abs(nalt-alt)<x) { grad = 100*Math.asin((nalt-alt)/x) ; gradstr = Math.abs(grad).toFixed(0) ; if(gradstr=='0') s += 'Flat<br>' ; else if(grad>0) s += 'Climb '+gradstr+'%<br>' ; else s += 'Descend: '+gradstr+'%<br>' ; } s += '<span style="font-size:80%">' ; if(segments.length>1) s += 'Segment '+s0+' p' ; else s += 'P' ; s += 'oint ' + s1 + '</span></div>' ; infowindow.open(s,pos,'wpinfo') ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------- setalt ---------------------------------- */ function setalt(edit) { infowindow.close() ; var s0=selected[0],s1=selected[1],x,y=null,oldalt ; oldalt = segments[s0].data[s1].h.toFixed(0) ; if(edit) x = prompt('Enter altitude (m):',oldalt) ; else x = prompt('Enter altitude (m):') ; if(x==null) return ; if(x!=''&&isNaN(y=parseFloat(x))) { alert(x+' is not a number') ; return ; } if(y==null&&oldalt==null) return ; if(y!=null&&Math.abs(y-oldalt)<0.1) return ; segments[s0].data[s1].h = y ; done(['setalt',s0,s1,oldalt,y]) ; reprofile() ; wpinfo() ; } /* -------------------------------------------------------------------------- */ /* THE LABELS ARE ACCESSED FROM THE PEN BUTTON OR BY CLICKING ON THE MAP */ /* -------------------------------------------------------------------------- */ function labelprompt() { var oldcaption='',oldtype,type='Generic',s0=selected[0],s1=selected[1] ; var str , flag = (infowindow.close()=='wpinfo') ; var datum = segments[s0].data[s1] ; oldtype = datum.type ; if(oldtype!=null) oldcaption = datum.marker.title ; if(oldcaption==null) oldcaption = '' ; if(oldtype!=null) type = oldtype ; if(oldtype==null) str = 'Enter' ; else str = 'Modify or delete' ; var caption = window.prompt(str+' label:',oldcaption) ; if(caption==null) { if(flag) wpinfo() ; else walkto(s0,s1) ; return ; } else if(caption=='') type = null ; else caption = caption.substring(0,10) ; if(caption==oldcaption) { if(flag) wpinfo() ; else walkto(s0,s1) ; return ; } segments[s0].data[s1].setlabel(type,caption) ; done(['editlabel',s0,s1,oldcaption,caption,oldtype,type]) ; if(flag) wpinfo() ; else walkto(s0,s1) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ------------------------------ labelcycle -------------------------------- */ function labelcycle() { var s0,s1,datum,types,caption,flag=(infowindow.close()=='wpinfo') ; ; for(s0=0;s0<segments.length;s0++) for(s1=0;s1<segments[s0].data.length;s1++) { datum = segments[s0].data[s1] ; if(datum.marker!=this) continue ; types = datum.labelcycle() ; caption = datum.marker.title ; selected = [s0,s1] ; done(['editlabel',s0,s1,caption,caption,types[0],types[1]]) ; if(flag) wpinfo() ; return ; } } function photoprompt(e) { var s0=selected[0],s1=selected[1] ; if(e!=null) e.preventDefault() ; var flag = (infowindow.close()=='wpinfo') ; var datum = segments[s0].data[s1] ; var photo = window.prompt('Enter photo name:','') ; if(photo!=null&&photo!='') { done(['editphoto',s0,s1,datum.photo.length,null,photo]) ; datum.addphoto(photo) ; } if(flag) wpinfo() ; else walkto(s0,s1) ; } function photoedit(ind) { var s0=selected[0],s1=selected[1],i ; var flag = (infowindow.close()=='wpinfo') ; var datum = segments[s0].data[s1] ; var photo = window.prompt('New photo name:',datum.photo[ind]) ; if(photo!=null&&photo!='') for(i=0;i<datum.photo.length;i++) if(datum.photo[i]==photo) { photo = null ; break ; } if(photo!=null) { if(photo=='') photo = null ; done(['editphoto',s0,s1,ind,datum.photo[ind],photo]) ; datum.setphoto(ind,photo) ; } if(flag) wpinfo() ; else walkto(s0,s1) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ----------------------------- display photo ------------------------------ */ var lmove,rmove ; function advance(s0,s1,ind) { for(ind++;;ind++) { if(ind>=segments[s0].data[s1].photo.length) { ind = 0 ; s1 += 1 ; } if(s1==segments[s0].data.length) { s0 += 1 ; if(s0==segments.length) return null ; s1 = 0 ; } if(ind<segments[s0].data[s1].photo.length) return [s0,s1,ind] ; } } function retreat(s0,s1,ind) { for(ind--;;ind--) { if(ind<0) { s1 -= 1 ; ind = null ; } if(s1<0) { if(s0==0) return null ; else s0 -= 1 ; s1 = segments[s0].data.length-1 ; } if(ind==null) ind = segments[s0].data[s1].photo.length - 1 ; if(ind>=0) return [s0,s1,ind] ; } } function prev() { dodisplay(lmove[0],lmove[1],lmove[2],-1) ; } function backtogps() { document.onkeydown = keystroke ; window.removeEventListener('resize',resize) ; body.removeChild(imgdiv) ; } function next() { dodisplay(rmove[0],rmove[1],rmove[2],1) ; } function display(ind) { var phind ; document.onkeydown = imgwalk ; window.addEventListener('resize',resize) ; infowindow.close() ; imgdiv = document.createElement('div') ; imgdiv.setAttribute('style','position:fixed;width:100%;height:100%;'+ 'left:0;top:0;background:black') ; dodisplay(selected[0],selected[1],ind,1) ; body.appendChild(imgdiv) ; } function dodisplay(s0,s1,ind,dir) { var phind=findimg(segments[s0].data[s1].photo[ind]) , pre=null ; selected[0] = s0 ; selected[1] = s1 ; lmove = retreat(s0,s1,ind) ; rmove = advance(s0,s1,ind) ; if(dir<0&&lmove!=null) pre = findimg(segments[lmove[0]].data[lmove[1]].photo[lmove[2]]) ; else if(dir>=0&&rmove!=null) pre = findimg(segments[rmove[0]].data[rmove[1]].photo[rmove[2]]) ; if(pre!=null) pre = imginfo.list[pre] ; gendisplay(imgdiv,imginfo.list[findimg(segments[s0].data[s1].photo[ind])], imginfo.sizes,lmove==null?null:'javascript:prev()', 'javascript:backtogps()',rmove==null?null:'javascript:next()', 'GPS track',pre) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ------------------------------ image walk -------------------------------- */ function imgwalk(e) { e.preventDefault() ; if(e.keyCode==39) { if(rmove!=null) next() ; return ; } else if(e.keyCode==37) { if(lmove!=null) prev() ; return ; } else if(e.keyCode==40) reduce() ; else if(e.keyCode==38) enlarge() ; else if(e.keyCode==70) enterfullscreen() ; else backtogps() ; } /* --------------------------------- photo info ----------------------------- */ function phinfo(i) { infowindow.close() ; var s0=selected[0],s1=selected[1],s,shape,ind,hind,r,k ; var list=imginfo.list,sizes=imginfo.sizes ; s = finalbox + 'Name: ' + list[i].name + '<br>Title: ' + list[i].title ; for(hind=null,ind=0;ind<i;ind++) if(list[ind].name==null) hind = ind ; if(hind!=null) s += "<br>Under \u201c" + list[hind].title + "\u201d" ; // how many sizes? for(r=ind=0;ind<sizes.length;ind++) if(sizes[ind].type==undefined) r += 1 ; s += '<br>Available in ' + r + ' size' + (r>1?'s: ':': ') ; // print the sizes for(k=ind=0;ind<sizes.length;ind++) if(sizes[ind].type==undefined) { if(k>0) { if(k==r-1) s += ' and ' ; else s += ', ' ; } shape = imgsize(list[i],sizes,ind) ; s += shape[0] + 'x' + shape[1] ; k += 1 ; } shape = list[i].thumbshape ; s += '<br>Thumb: ' + shape[0] + 'x' + shape[1] ; for(ind=0;ind<sizes.length;ind++) if(sizes[ind].type=='raw') { shape = imgsize(list[i],sizes,ind) ; s += '<br>Raw: ' + shape[0] + 'x' + shape[1] ; } if(imginfo.pixpage!=null) s += '<br><a style="cursor:pointer;color:#0000bd;text-decoration:none" '+ 'href="'+imginfo.pixpage+'" target="_blank">'+ 'Full photo set</a>'+neutral+' (opens in new tab/window)</span>' ; if(list[i].retid!=null) { for(hind=null,ind=0;ind<=i;ind++) if(list[ind].retpage!=undefined&&list[ind].retpage!=null) hind = list[ind].retpage + '.html#' + list[i].retid ; if(hind!=null) s += '<br><a style="cursor:pointer;color:#0000bd;'+ 'text-decoration:none" href="' + hind + '" target="_blank">'+ 'Route notes</a>'+neutral+' (opens in new tab/window)</span>' ; } infowindow.open(s,segments[s0].data[s1].pos,'phinfo') ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ------------------------- snip: apply scissors -------------------------- */ function snipwork(s0,s1) { var i,k,newlen ; undraw(s0) ; segments.length += 1 ; for(i=segments.length-1;i>s0+1;i--) segments[i] = segments[i-1] ; newlen = segments[s0].data.length - s1 ; segments[s0+1] = new genseg(segments[s0].data.slice(s1),segments[s0].props) ; segments[s0+1].dots = segments[s0].dots ; segments[s0+1].dothandler = segments[s0].dothandler ; segments[s0].dots = segments[s0].dothandler = null ; segments[s0].data.length = s1 + 1 ; segments[s0].data[s1] = new datatype(segments[s0].data[s1].pos,segments[s0].data[s1].h) ; draw(s0) ; draw(s0+1) ; for(i=s0+2;i<segments.length;i++) recolour(i) ; drawsel(1,[s0+1,0]) ; greyout(dlbtn) ; } function snip() { var i,s0=selected[0],s1=selected[1] ; infowindow.close() ; done(['snip',s0,s1]) ; snipwork(s0,s1) ; } /* ------------------------ discard: bin a segment ------------------------- */ function binwork(s0) { var i ; obliterate(s0) ; for(i=s0;i<segments.length-1;i++) segments[i] = segments[i+1] ; segments.length -= 1 ; for(i=s0;i<segments.length;i++) recolour(i) ; connect(s0-1) ; selected[1] = 0 ; if(selected[0]==segments.length) selected[0] = 0 ; drawsel(1) ; if(segments.length==1) blackout(dlbtn) ; } function discard() { var i,s0=selected[0] ; infowindow.close() ; done(['bin',s0,segments[s0]]) ; binwork(s0) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------- actionname ------------------------------ */ function actionname(x) { if(x[0]=='bin') return 'delete segment' ; if(x[0]=='snip') return 'split segment' ; if(x[0]=='editlabel') { if(x[4]=='') return 'delete label' ; else if(x[3]=='') return 'label waypoint' ; else return 'edit label' ; } if(x[0]=='edittitle') return 'edit title' ; if(x[0]=='editlongtitle') return 'edit long title' ; if(x[0]=='wpdel') return 'delete waypoint' ; if(x[0]=='move') { if(x[5]) return 'insert waypoint' ; else return 'drag waypoint' ; } if(x[0]=='recal') return 'recalibrate altitudes' ; if(x[0]=='setalt') return 'set waypoint altitude' ; if(x[0]=='resign') return 'change label symbol' ; if(x[0]=='combine') return 'combine '+x[1]+' segments' ; if(x[0]=='revseg') return 'reverse segment' ; if(x[0]=='interpolate') return 'interpolate missing altitudes' ; if(x[0]=='optimise') return 'optimisation' ; if(x[0]=='deltimes') return 'delete times' ; if(x[0]=='editphoto') { if(x[5]==null) return 'delete photo' ; else if(x[4]==null) return 'add photo' ; else return 'change photo' ; } if(x[0]=='extra') return 'interpolate extra points' ; } function actiontype(x) { if( x=='snip'||x=='combine'||x=='interpolate' || x=='optimise'||x=='load' ) return 0 ; else return 1 ; } /* -------------------------------------------------------------------------- */ function done(something) { if( nactions>0 && unsavedchanges.length>0 && something[0]=='editlabel' && actions[nactions-1][0]==something[0] && actions[nactions-1][1]==something[1] // don't merge change with delete && actions[nactions-1][2]==something[2] && something[6]!=null ) { actions[nactions-1][4] = something[4] ; // caption actions[nactions-1][6] = something[6] ; // type } else { actions[nactions++] = something ; donesomething() ; } } function donesomething() { actions.length = nactions ; blackout(undobtn) ; greyout(redobtn) ; if(actiontype(actions[nactions-1][0])!=0) { if(unsavedchanges.length>=3) unsavedchanges.push(null) ; else unsavedchanges.push(actionname(actions[nactions-1])) ; } } /* -------------------------------------------------------------------------- */
/*page*/ /* --------------------------------- undo ---------------------------------- */ function undo() { infowindow.close() ; var opts = active + '"confirmedundo()">Undo ' + actionname(actions[nactions-1])+'</span>' ; infowindow.open(opts,getbtnpos(5),'undo') ; } function confirmedundo() { var i,ano=nactions-1,action=actions[ano][0],s0=actions[ano][1],s1,caption ; var oldcaption,task,ind ; infowindow.close() ; if(action!='revseg'&&action!='interpolate'&&action!='deltimes') s1 = actions[ano][2] ; if(action=='bin') { disconnect(s0-1) ; for(i=segments.length;i>s0;i--) { segments[i] = segments[i-1] ; recolour(i) ; } segments[s0] = s1 ; for(s1=0;s1<segments[s0].data.length;s1++) segments[s0].data[s1].setmap(map) ; draw(s0) ; connect(s0-1) ; connect(s0) ; if(selected[0]>=s0) selected[0] += 1 ; drawsel(1) ; greyout(dlbtn) ; } else if(action=='snip') // undo snip { selected = [ s0 , segments[s0].data.length-1 ] ; combine1(s0,s0+1) ; for(i=s0+1;i<segments.length-1;i++) { segments[i] = segments[i+1] ; recolour(i) ; } segments.length -= 1 ; if(segments.length==1) blackout(dlbtn) ; draw(s0) ; drawsel(1) ; } else if(action=='editlabel') // undo create/edit/delete label segments[s0].data[s1].setlabel(actions[ano][5],actions[ano][3]) ; else if(action=='edittitle') settitle(s0) ; else if(action=='editlongtitle') longtitle = s0 ; else if(action=='wpdel') // ['wpdel',s0,s1,wpdelwork(s0,s1)] { insert(s0,s1,1) ; segments[s0].data[s1] = actions[ano][3] ; segments[s0].data[s1].setmap(map) ; redrawconnect(s0,s1) ; drawsel(1,[s0,s1]) ; } else if(action=='move') { if(actions[ano][5]) wpdelwork(s0,s1) ; else move(s0,s1,actions[ano][3]) ; } else if(action=='recal') calwork(s0,-s1) ; else if(action=='setalt') segments[s0].data[s1].h = actions[ano][3] ; else if(action=='combine') uncombine(actions[ano]) ; else if(action=='revseg') revsegwork(s0) ; else if(action=='interpolate') for(i=0;i<s0.length;i++) segments[0].data[s0[i]].h = null ; else if(action=='deltimes') for(i=0;i<s0.length;i++) segments[s0[i][0]].data[s0[i][1]].t = s0[i][2] ; else if(action=='optimise') // [ 'load' , s0 , data.slice() , loadno , props ] { for(ano--;ano>=0&&actions[ano][0]!='load';ano--) ; segments[s0].data = actions[ano][2] ; actions[loadno][4].optim.ndel = 0 ; redraw(s0) ; drawsel(1,[s0,0]) ; } else if(action=='editphoto') { ind = actions[ano][3] ; if(actions[ano][5]==null) // undo delete for(i=segments[s0].data[s1].photo.length;i>ind;i--) segments[s0].data[s1].photo[i] = segments[s0].data[s1].photo[i-1] ; if(ind>=segments[s0].data[s1].photo.length) segments[s0].data[s1].addphoto(actions[ano][4]) ; else segments[s0].data[s1].setphoto(ind,actions[ano][4]) ; } else if(action=='extra') for(selected=[s0,s1],i=actions[ano].length-1;i>=3;i--) { task = actions[ano][i] segments[task[0]].data.splice(task[1],task[2].length-2) ; } nactions -= 1 ; if(nactions<=1) greyout(undobtn) ; blackout(redobtn) ; if(actiontype(actions[nactions][0])!=0&&unsavedchanges.length>0) unsavedchanges.length -= 1 ; ; if(action=='optimise'||action=='dltimes') routeinfo() ; else if(action=='editphoto'||action=='editlabel') walkto(s0,s1) ; } /* --------------------------------- move ----------------------------------- */ function move(s0,s1,pos) { segments[s0].data[s1].setpos(pos) ; redrawconnect(s0,s1) ; drawsel(1) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* --------------------------------- redo ---------------------------------- */ function redo() { infowindow.close() ; var opts = active + '"confirmedredo()">Redo ' + actionname(actions[nactions])+'</span>' ; infowindow.open(opts,getbtnpos(6),'redo') ; } function confirmedredo() { var i,action=actions[nactions][0],s0=actions[nactions][1],s1,caption,a,b,c ; var task,ind,photo ; if(action!='bin'&&action!='revseg'&&action!='interpolate'&&action!='deltimes') s1 = actions[nactions][2] ; infowindow.close() ; if(action=='bin') binwork(s0) ; else if(action=='snip') snipwork(s0,s1) ; else if(action=='editlabel') // redo create/edit/delete label segments[s0].data[s1].setlabel(actions[nactions][6],actions[nactions][4]) ; else if(action=='edittitle') settitle(s1) ; else if(action=='editlongtitle') longtitle = s1 ; else if(action=='wpdel') wpdelwork(s0,s1) ; else if(action=='move') // ['move',s0,s1,oldpos,newpos,inserted] { if(actions[nactions][5]) insert(s0,s1,1) ; move(s0,s1,actions[nactions][4]) ; } else if(action=='recal') calwork(s0,s1) ; else if(action=='setalt') segments[s0].data[s1].h = actions[nactions][4] ; else if(action=='combine') recombine(actions[nactions]) ; else if(action=='revseg') revsegwork(s0) ; else if(action=='interpolate') interpolatework(segments[0].data) ; else if(action=='deltimes') for(i=0;i<s0.length;i++) segments[s0[i][0]].data[s0[i][1]].t = null ; else if(action=='optimise') { result = optimise(segments[s0].data,actions[nactions][2]) ; actions[loadno][4].optim.ndel = segments[s0].data.length - result.length ; segments[s0].data = result ; redraw(s0) ; drawsel(1,[s0,0]) ; routeinfo() ; } else if(action=='editphoto') { ind = actions[nactions][3] ; photo = actions[nactions][5] ; if(actions[nactions][4]==null) segments[s0].data[s1].addphoto(photo) ; else segments[s0].data[s1].setphoto(ind,photo) ; } else if(action=='extra') for(selected=[s0,s1],i=3;i<actions[nactions].length;i++) { task = actions[nactions][i] ; a = segments[task[0]].data.slice(0,task[1]) ; b = task[2].slice(1,task[2].length-1) ; c = segments[task[0]].data.slice(task[1]) ; segments[task[0]].data = a.concat(b,c) ; } nactions += 1 ; if(nactions==actions.length) greyout(redobtn) ; blackout(undobtn) ; if(actiontype(actions[nactions-1][0])!=0) unsavedchanges.push(action) ; if(action=='editphoto'||action=='editlabel') walkto(s0,s1) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ---------------------------------- dl ----------------------------------- */ function dl(opt) { var props=new propstype(),str,ndel,origlen,ano,i,npix,noalt,filename ; infowindow.close() ; if(opt==undefined) opt = 0 ; // filename i = routetitle.indexOf(' ') ; if(i<=0) filename = routetitle ; else filename = routetitle.substring(0,i) ; if(filename==''||filename==null) filename = 'Untitled' ; filename += '.tcx' ; // check for photos and altitudeless points for(noalt=npix=i=0;i<segments[0].data.length;i++) { npix += segments[0].data[i].photo.length ; if(segments[0].data[i].h==null) noalt += 1 ; } // photo list if(npix>0&&imginfo.status=='ready') { if(imginfo.type=='tcx') props.list = imginfo.uri ; // vice 'uri' else { props.list = document.URL ; if((i=props.list.lastIndexOf('?'))>=0) props.list = props.list.substring(0,i) ; props.list = reluri(props.list,imginfo.uri) ; } } if(opt) // write overview and return { str = writeoverview(segments,routetitle,props.list) ; saveAs(new Blob([str],{type: "text/plain;charset=utf-8"}),filename) ; return ; } // decide what to do if some points have no altitudes if(noalt&&!confirm(noalt+' waypoints have no associated altitudes.\n'+ 'You can hit [OK] and I will interpolate altitudes (not guaranteed), or\n'+ 'you can hit [Cancel] and try again later when the altitudes may be '+ 'available.')) return null ; if(noalt) interpolate() ; // record optimisation for(ano=-1,ndel=origlen=0,i=loadno;i>=0;i=actions[i][3]) { ndel += actions[i][4].optim.ndel ; origlen += actions[i][4].optim.origlen ; if(actions[i][4].optim.parms!=null&&ano==-1) ano = i ; } if(ano>=0) props.optim.parms = actions[ano][4].optim.parms ; props.optim.origlen = origlen ; props.optim.ndel = ndel ; // title props.title = routetitle ; props.longtitle = longtitle ; str = writetcx(props,segments[0].data) ; if(str==null) return ; unsavedchanges = [] ; saveAs(new Blob([str],{type: "text/plain;charset=utf-8"}),filename) ; } /* ------------------------------ interpolate ------------------------------- */ function interpolate() { infowindow.close() ; var response = interpolatework(segments[0].data) ; if(response.length>0) done([ 'interpolate' , response ]) ; } function interpolatework(a) { var i,j,k,response,x,y,distance,sum,len=a.length ; for(response=[],i=0;i<len;i=j) { for(;i<len&&a[i].h!=null;i++) ; // advance to null for(j=i+1;j<len&&a[j].h==null;j++) response.push(j) ; // advance to non-null if(i==0) { for(y=a[j].h;i<j;i++) a[i].h = y ; continue ; } if(j==len) { for(x=a[i-1].h;i<j;i++) a[i].h = x ; continue ; } distance = new Array(1+j-i) ; for(sum=k=0;k<=j-i;k++) sum = distance[k] = sum + dist(a[i+k-1].pos,a[i+k].pos) ; for(x=a[i-1].h,y=a[j].h,k=0;k<j-i;k++) a[i+k].h = ( x*(sum-distance[i]) + y*distance[i] ) / sum ; } return response ; } /* -------------------------------------------------------------------------- */

Archived from optim.html

/* The original fortran codes are distributed without restrictions. The C++ codes are distributed under MIT license. */ /* The MIT License Copyright (c) 2004, by M.J.D. Powell <mjdp@cam.ac.uk> 2016, by Colin Champion <colin.champion@masterlyinactivity.com> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include"memory.h" #include <math.h> #define max(a,b) ((a)>(b)?(a):(b)) #define min(a,b) ((a)<(b)?(a):(b)) int update(int,int,double **,double **,int,double *,double,int) ; void shiftxbase(int n,int npt,double **xp,double *xopt,double *vlag,double **b, double **z,double *pq,double *hq,int idz) ; double calcvlagbeta(int n,int npt,double *vlag,double **z,double **b,double *w, double *xopt,double *d,double beta,double dsq,double xsq, int idz) ; int chooseknew(int n,int npt,double beta,double **z,double *vlag,double **xp, double *xopt,double detrat,double rhosq,int ktemp,int idz) ; void genpqw(int n,int npt,double *pqw,double **z,int knew,int idz) ; void updatehq(int n,int npt,double *hq,double pqknew,double **xpt,int knew) ; void updategopt(int n,double *gopt,double *hq,double *step) ; void updategopt2(int n,int npt,double *gopt,double *xopt,double *pq,double **) ; //C These instructions rearrange the active constraints so that the new //C value of IACT(NACT) is the old value of IACT(IC). A sequence of //C Givens rotations is applied to the current QFAC and RFAC. Then NACT //C is reduced by one. static int rearrange(int ic,int nact,int n,int *iact,double *resact, double *rfac,double **qfac,double *vlam,double *resnew) { int jc,i,j,idiag,jw,jdiag ; double temp,cval,sval ; if((resnew[iact[ic]]=resact[ic])<1e-60) resnew[iact[ic]] = 1e-60 ; for(jc=ic;jc<nact-1;jc++) { jdiag = (jc*(jc+1)) / 2 ; idiag = ((jc+1)*(jc+2)) / 2 ; jw = idiag + jc ; temp = sqrt(rfac[jw]*rfac[jw]+rfac[jw+1]*rfac[jw+1]) ; cval = rfac[jw+1] / temp ; sval = rfac[jw] / temp ; rfac[jw] = sval * rfac[idiag-1] ; rfac[jw+1] = cval * rfac[idiag-1] ; rfac[idiag-1] = temp ; for(jw+=2,j=jc+2;j<nact;j++,jw+=j) { temp = sval*rfac[jw+jc] + cval*rfac[jw+jc+1] ; rfac[jw+jc+1] = cval*rfac[jw+jc] - sval*rfac[jw+jc+1] ; rfac[jw+jc] = temp ; } for(i=0;i<jc;i++) swap(rfac[idiag+i],rfac[jdiag+i]) ; for(i=0;i<n;i++) { temp = sval*qfac[jc][i] + cval*qfac[jc+1][i] ; qfac[jc+1][i] = cval*qfac[jc][i] - sval*qfac[jc+1][i] ; qfac[jc][i] = temp ; } iact[jc] = iact[jc+1] ; resact[jc] = resact[jc+1] ; vlam[jc] = vlam[jc+1] ; } return nact-1 ; } /* -------------------------------------------------------------------------- */ double getact(int n,int m,double **amat, double *b,int& nact, int *iact,double **qfac,double *rfac,double & snorm, double *resnew,double *resact,double *g,double *dw) { //C N, M, AMAT, B, NACT, IACT, QFAC and RFAC are the same as the terms //C with these names in SUBROUTINE LINCOB. The current values must be //C set on entry. NACT, IACT, QFAC and RFAC are kept up to date when //C GETACT changes the current active set. //C SNORM, RESNEW, RESACT, G and DW are the same as the terms with these //C names in SUBROUTINE TRSTEP. The elements of RESNEW and RESACT are //C also kept up to date. //C VLAM and W are used for working space, the vector VLAM being reserved //C for the Lagrange multipliers of the calculation. Their lengths must //C be at least N. //C The main purpose of GETACT is to pick the current active set. It is //C defined by the property that the projection of -G into the space //C orthogonal to the active constraint normals is as large as possible, //C subject to this projected steepest descent direction moving no closer //C to the boundary of every constraint whose current residual is at most //C 0.2*SNORM. On return, the settings in NACT, IACT, QFAC and RFAC are //C all appropriate to this choice of active set. //C Occasionally this projected direction is zero, and then the final value //C of W(1) is set to zero. Otherwise, the direction itself is returned //C in DW, and W(1) is set to the square of the length of the direction. //C //C Set some constants and a temporary VLAM. int i,j,k,ic,idiag,jw,l ; double tdel=0.2*snorm,ddsav,violmx,rdiag,sprod,temp,ctol ; double cosv,sinv,sum,dd,test,dnorm,vmult ; double *vlam=vector(n),*w=vector(n) ; for(ddsav=i=0;i<n;i++) { ddsav += g[i]*g[i] ; vlam[i] = 0 ; } ddsav *= 2 ; //C Set the initial QFAC to the identity matrix in the case NACT=0. if(nact==0) for(i=0;i<n;i++) { for(j=0;j<i;j++) qfac[j][i] = qfac[i][j] = 0 ; qfac[i][i] = 1 ; } else //C Remove any constraints from the initial active set whose residuals //C exceed TDEL. { for(ic=nact-1;ic>=0;ic--) if(resact[ic]>tdel) nact = rearrange(ic,nact,n,iact,resact,rfac,qfac,vlam,resnew) ; //C Remove any constraints from the initial active set whose Lagrange //C multipliers are nonnegative, and set the surviving multipliers. for(ic=nact-1;ic>=0;ic--) { for(temp=i=0;i<n;i++) temp += qfac[ic][i] * g[i] ; idiag = ((ic+1)*(ic+2))/2 ; for(jw=idiag+ic,j=ic+1;j<nact;j++,jw+=j) temp -= rfac[jw] * vlam[j] ; if(temp>=0) ic = nact = rearrange(ic,nact,n,iact,resact,rfac,qfac,vlam,resnew) ; else vlam[ic] = temp / rfac[idiag-1] ; } } //C Set the new search direction D. Terminate if the 2-norm of D is zero //C or does not decrease, or if NACT=N holds. The situation NACT=N //C occurs for sufficiently large SNORM if the origin is in the convex //C hull of the constraint gradients. while(nact<n) { for(j=nact;j<n;j++) for(w[j]=i=0;i<n;i++) w[j] += qfac[j][i] * g[i] ; for(dd=i=0;i<n;i++) { for(dw[i]=0,j=nact;j<n;j++) dw[i] -= w[j] * qfac[j][i] ; dd += dw[i] * dw[i] ; } if(dd>=ddsav||dd==0) { free(vlam,w) ; return 0 ; } ddsav = dd ; dnorm = sqrt(dd) ; //C Pick the next integer L or terminate, a positive value of L being //C the index of the most violated constraint. The purpose of CTOL //C below is to estimate whether a positive value of VIOLMX may be //C due to computer rounding errors. l = -1 ; if(m>0) { test = dnorm / snorm ; for(violmx=j=0;j<m;j++) if(resnew[j]>0&&resnew[j]<=tdel) { for(sum=i=0;i<n;i++) sum += amat[j][i] * dw[i] ; if(sum>test*resnew[j]&&sum>violmx) { l = j ; violmx = sum ; } } ctol= 0 ; temp = 0.01 * dnorm ; if(violmx>0&&violmx<temp) for(k=0;k<nact;k++) { for(j=iact[k],sum=i=0;i<n;i++) sum += dw[i] * amat[j][i] ; if(fabs(sum)>ctol) ctol = fabs(sum) ; } } if(l<0||violmx<=10*ctol) { free(vlam,w) ; return dd ; } //C Apply Givens rotations to the last (N-NACT) columns of QFAC so that //C the first (NACT+1) columns of QFAC are the ones required for the //C addition of the L-th constraint, and add the appropriate column //C to RFAC. idiag = (nact*(nact+1))/2 ; for(rdiag=0,j=n-1;j>=0;j--) { for(sprod=i=0;i<n;i++) sprod += qfac[j][i] * amat[l][i] ; if(j<nact) rfac[idiag+j] = sprod ; else if(fabs(rdiag)<=1e-20*fabs(sprod)) rdiag = sprod ; else { temp = sqrt(sprod*sprod+rdiag*rdiag) ; cosv = sprod / temp ; sinv = rdiag / temp ; rdiag = temp ; for(i=0;i<n;i++) { temp = cosv*qfac[j][i] + sinv*qfac[j+1][i] ; qfac[j+1][i] = -sinv*qfac[j][i] + cosv*qfac[j+1][i] ; qfac[j][i] = temp ; } } } if(rdiag<0) for(i=0;i<n;i++) qfac[nact][i] = -qfac[nact][i] ; rfac[idiag+nact] = fabs(rdiag) ; iact[nact] = l ; resact[nact] = resnew[l] ; vlam[nact] = resnew[l] = 0 ; nact += 1 ; //C Set the components of the vector VMU in W. for(ic=0;violmx>0||ic>=0;) // while(violmx>0) but with at least 1 iteration { temp = rfac[(nact*(nact+1))/2-1] ; w[nact-1] = 1/(temp*temp) ; for(i=nact-2;i>=0;i--) { idiag = ((i+1)*(i+2))/2 ; for(sum=0,jw=idiag+i,j=i+1;j<nact;j++,jw+=j) sum -= rfac[jw] * w[j] ; w[i] = sum / rfac[idiag-1] ; } //C Calculate the multiple of VMU to subtract from VLAM, and update VLAM. vmult = violmx ; for(ic=-1,j=0;j<nact-1;j++) if(vlam[j]>=vmult*w[j]) { ic = j ; vmult = vlam[j] / w[j] ; } for(j=0;j<nact;j++) vlam[j] -= vmult * w[j] ; if(ic>=0) vlam[ic] = 0 ; violmx -= vmult ; if(ic<0||violmx<0) violmx = 0 ; //C Reduce the active set if necessary, so that all components of the //C new VLAM are negative, with resetting of the residuals of the //C constraints that become inactive. for(ic=nact-1;ic>=0;ic--) if(vlam[ic]>=0) nact = rearrange(ic,nact,n,iact,resact,rfac,qfac,vlam,resnew) ; //C Calculate the next VMU if VIOLMX is positive. Return if NACT=N holds, //C as then the active constraints imply D=0. Otherwise, go to label //C 100, to calculate the new D and to test for termination. } // end while(violmx>0) } // end while(nact<n) free(vlam,w) ; return 0 ; } /* -------------------------------------------------------------------------- */ int trstep(int n,int npt,int m,double **amat,double *b,double **xpt,double *hq, double *pq,int& nact,int *iact,double *rescon,double **qfac, double *rfac,double& snorm,double *step,double *g) { //C N, NPT, M, AMAT, B, XPT, HQ, PQ, NACT, IACT, RESCON, QFAC and RFAC //C are the same as the terms with these names in LINCOB. If RESCON(J) //C is negative, then |RESCON(J)| must be no less than the trust region //C radius, so that the J-th constraint can be ignored. //C SNORM is set to the trust region radius DELTA initially. On the //C return, however, it is the length of the calculated STEP, which is //C set to zero if the constraints do not allow a long enough step. //C STEP is the total calculated step so far from the trust region centre, //C its final value being given by the sequence of CG iterations, which //C terminate if the trust region boundary is reached. //C G must be set on entry to the gradient of the quadratic model at the //C trust region centre. It is used as working space, however, and is //C always the gradient of the model at the current STEP, except that //C on return the value of G(1) is set to ONE instead of to ZERO if //C and only if GETACT is called more than once. //C RESNEW, RESACT, D, DW and W are used for working space. A negative //C value of RESNEW(J) indicates that the J-th constraint does not //C restrict the CG steps of the current trust region calculation, a //C zero value of RESNEW(J) indicates that the J-th constraint is active, //C and otherwise RESNEW(J) is set to the greater of TINY and the actual //C residual of the J-th constraint for the current STEP. RESACT holds //C the residuals of the active constraints, which may be positive. //C D is the search direction of each line search. DW is either another //C search direction or the change in gradient along D. The length of W //C must be at least MAX[M,2*N]. int i,j,k,continuing,ncall,ir,jsav,icount,ih ; double ctest=0.01,snsq=snorm*snorm,tiny=1e-60,dnorm,scale,resmax,ss,sum,rhs ; double temp,gamma,sumrhs,dd,ds,ad,adw,dg,dgd,alpha,alphm,alpht,alpbd,reduct ; double wgd,beta ; double *resnew=vector(m),*resact=vector(n),*d=vector(n),*dw=vector(n) ; double *w=vector(m>2*n?m:2*n) ; //C Set the initial elements of RESNEW, RESACT and STEP. for(j=0;j<m;j++) { resnew[j] = rescon[j] ; if(resnew[j]>=snorm) resnew[j] = -1 ; else if(resnew[j]>=0&&resnew[j]<tiny) resnew[j] = tiny ; } if(m>0) for(k=0;k<nact;k++) { resact[k] = rescon[iact[k]] ; resnew[iact[k]] = 0 ; } for(i=0;i<n;i++) step[i] = 0 ; //C GETACT picks the active set for the current STEP. It also sets DW to //C the vector closest to -G that is orthogonal to the normals of the //C active constraints. DW is scaled to have length 0.2*SNORM, as then //C a move of DW from STEP is allowed by the linear constraints. for(ss=reduct=ncall=0,continuing=1;continuing;) { dnorm = getact(n,m,amat,b,nact,iact,qfac,rfac,snorm,resnew,resact,g,dw) ; ncall += 1 ; if(dnorm==0) break ; scale = 0.2*snorm/sqrt(dnorm) ; for(i=0;i<n;i++) dw[i] *= scale ; //C If the modulus of the residual of an active constraint is substantial, //C then set D to the shortest move from STEP to the boundaries of the //C active constraints. for(resmax=k=0;k<nact;k++) if(resact[k]>resmax) resmax = resact[k] ; gamma = 0 ; if(resmax>snorm*1e-4) { for(ir=k=0;k<nact;k++,ir++) { for(temp=resact[k],i=0;i<k;ir++,i++) temp -= rfac[ir] * w[i] ; w[k] = temp / rfac[ir] ; } for(i=0;i<n;i++) for(d[i]=k=0;k<nact;k++) d[i] += w[k] * qfac[k][i] ; //C The vector D that has just been calculated is also the shortest move //C from STEP+DW to the boundaries of the active constraints. Set GAMMA //C to the greatest steplength of this move that satisfies the trust //C region bound. for(rhs=snsq,ds=dd=i=0;i<n;i++) { sum = step[i] + dw[i] ; rhs -= sum * sum ; ds += d[i] * sum ; dd += d[i] * d[i] ; } if(rhs>0) { temp = sqrt(ds*ds+dd*rhs) ; if(ds<=0) gamma = (temp-ds) / dd ; else gamma = rhs / (temp+ds) ; } //C Reduce the steplength GAMMA if necessary so that the move along D //C also satisfies the linear constraints. for(j=0;j<m&&gamma>0;j++) if(resnew[j]>0) { for(ad=adw=i=0;i<n;i++) { ad += amat[j][i] * d[i] ; adw += amat[j][i] * dw[i] ; } if(ad>0) { if(0>(temp=(resnew[j]-adw)/ad)) temp = 0 ; if(temp<gamma) gamma = temp ; } } if(gamma>1) gamma = 1 ; } // end if(resmax>snorm*1e-4) //C Set the next direction for seeking a reduction in the model function //C subject to the trust region bound and the linear constraints. if(gamma<=0) { for(i=0;i<n;i++) d[i] = dw[i] ; icount = nact ; } else { for(i=0;i<n;i++) d[i] = dw[i] + gamma*d[i] ; icount = nact-1 ; } //C Set ALPHA to the steplength from STEP along D to the trust region //C boundary. Return if the first derivative term of this step is //C sufficiently small or if no further progress is possible. for(alpbd=1;;alpbd=0) { icount += 1 ; rhs = snsq - ss ; if(rhs<=0) { continuing = 0 ; break ; } // break both loops for(dg=ds=dd=i=0;i<n;i++) { dg += d[i] * g[i] ; ds += d[i] * step[i] ; dd += d[i] * d[i] ; } if(dg>=0) { continuing = 0 ; break ; } temp = sqrt(rhs*dd+ds*ds) ; if(ds<=0) alpha = (temp-ds)/dd ; else alpha = rhs/(temp+ds) ; if(-alpha*dg<=ctest*reduct) { continuing = 0 ; break ; } //C Set DW to the change in gradient along D. for(j=0;j<n;j++) dw[j] = 0 ; updategopt(n,dw,hq,d) ; updategopt2(n,npt,dw,d,pq,xpt) ; //C Set DGD to the curvature of the model along D. Then reduce ALPHA if //C necessary to the value that minimizes the model. for(dgd=i=0;i<n;i++) dgd += d[i] * dw[i] ; alpht = alpha ; if(dg+alpha*dgd>0) alpha = -dg/dgd ; //C Make a further reduction in ALPHA if necessary to preserve feasibility, //C and put some scalar products of D with constraint gradients in W. alphm = alpha ; for(jsav=-1,j=0;j<m;j++) { w[j] = 0 ; if(resnew[j]>0) { for(i=0;i<n;i++) w[j] += amat[j][i] * d[i] ; if(alpha*w[j]>resnew[j]) { alpha = resnew[j] / w[j] ; jsav = j ; } } } if(alpha<alpbd) alpha = alpbd ; if(alpha>alphm) alpha = alphm ; if(icount==nact&&alpha>1) alpha = 1 ; //C Update STEP, G, RESNEW, RESACT and REDUCT. for(ss=i=0;i<n;i++) { step[i] += alpha*d[i] ; ss += step[i]*step[i] ; g[i] += alpha*dw[i] ; } for(j=0;j<m;j++) if(resnew[j]>0) if((resnew[j]-=alpha*w[j])<tiny) resnew[j] = tiny ; if(icount==nact) for(k=0;k<nact;k++) resact[k] *= (1-gamma) ; reduct -= alpha * (dg+0.5*alpha*dgd) ; //C Test for termination. Branch to label 40 if there is a new active //C constraint and if the distance from STEP to the trust region //C boundary is at least 0.2*SNORM. if( alpha==alpht || -alphm*(dg+0.5*alphm*dgd)<=ctest*reduct ) { continuing = 0 ; break ; } if(jsav>=0) { continuing = (ss<=0.64*snsq) ; break ; } if(icount==n) { continuing = 0 ; break ; } //C Calculate the next search direction, which is conjugate to the //C previous one except in the case ICOUNT=NACT. if(nact>0) { for(j=nact;j<n;j++) for(w[j]=i=0;i<n;i++) w[j] += g[i] * qfac[j][i] ; for(i=0;i<n;i++) { for(temp=0,j=nact;j<n;j++) temp += qfac[j][i]*w[j] ; w[n+i] = temp ; } } else for(i=0;i<n;i++) w[n+i] = g[i] ; if(icount==nact) beta = 0 ; else { for(wgd=i=0;i<n;i++) wgd += w[n+i]*dw[i] ; beta = wgd / dgd ; } for(i=0;i<n;i++) d[i] = -w[n+i] + beta*d[i] ; } } //C Return from the subroutine. if(reduct>0) snorm = sqrt(ss) ; else snorm = 0 ; free(resnew,resact,dw,d,w) ; return ncall ; } /* -------------------------------------------------------------------------- */ int qmstep(int n,int npt,int m,double **amat,double **xpt,double *xopt,int nact, int *iact,double *rescon,double **qfac,int kopt,int knew,double del, double *step,double *gl,double *pqw) { //C N, NPT, M, AMAT, B, XPT, XOPT, NACT, IACT, RESCON, QFAC, KOPT are the //C same as the terms with these names in SUBROUTINE LINCOB. //C KNEW is the index of the interpolation point that is going to be moved. //C DEL is the current restriction on the length of STEP, which is never //C greater than the current trust region radius DELTA. //C STEP will be set to the required step from XOPT to the new point. //C GL must be set on entry to the gradient of LFUNC at XBASE, where LFUNC //C is the KNEW-th Lagrange function. It is used also for some other //C gradients of LFUNC. //C PQW provides the second derivative parameters of LFUNC. //C RSTAT and W are used for working space. Their lengths must be at least //C M and N, respectively. RSTAT(J) is set to -1.0, 0.0, or 1.0 if the //C J-th constraint is irrelevant, active, or both inactive and relevant, //C respectively. //C IFEAS will be set to 0 or 1 if XOPT+STEP is infeasible or feasible. //C //C STEP is chosen to provide a relatively large value of the modulus of //C LFUNC(XOPT+STEP), subject to ||STEP|| .LE. DEL. A projected STEP is //C calculated too, within the trust region, that does not alter the //C residuals of the active constraints. The projected step is preferred //C if its value of | LFUNC(XOPT+STEP) | is at least one fifth of the //C original one, but the greatest violation of a linear constraint must //C be at least 0.2*DEL, in order to keep the interpolation points apart. //C The remedy when the maximum constraint violation is too small is to //C restore the original step, which is perturbed if necessary so that //C its maximum constraint violation becomes 0.2*DEL. int i,j,k,ksav,jsav,ifeas,*istat=ivector(m) ; double test=0.2*del,temp,vbig,ss,sp,stp,vlag,stpsav,gg,vgrad,ghg,ww,ctol ; double sum,vnew,resmax,bigv,*w=vector(n) ; //C Replace GL by the gradient of LFUNC at the trust region centre, and //C set the elements of RSTAT. for(k=0;k<npt;k++) { for(temp=j=0;j<n;j++) temp += xpt[k][j] * xopt[j] ; temp *= pqw[k] ; for(i=0;i<n;i++) gl[i] += temp*xpt[k][i] ; } for(j=0;j<m;j++) { if(fabs(rescon[j])>=del) istat[j] = -1 ; else istat[j] = 1 ; } if(m>0) for(k=0;k<nact;k++) istat[iact[k]] = 0 ; //C Find the greatest modulus of LFUNC on a line through XOPT and //C another interpolation point within the trust region. for(vbig=k=0;k<npt;k++) if(k!=kopt) { for(sp=ss=i=0;i<n;i++) { temp = xpt[k][i] - xopt[i] ; ss += temp * temp ; sp += gl[i] * temp ; } stp = -del / sqrt(ss) ; if(k==knew) { if(sp*(sp-1)<0) stp = -stp ; vlag = fabs(stp*sp) + stp*stp*fabs(sp-1) ; } else vlag = fabs(stp*(1-stp)*sp) ; if(vlag>vbig) { ksav = k ; stpsav = stp ; vbig = vlag ; } } //C Set STEP to the move that gives the greatest modulus calculated above. //C This move may be replaced by a steepest ascent step from XOPT. for(gg=i=0;i<n;i++) { gg += gl[i]*gl[i] ; step[i] = stpsav*(xpt[ksav][i]-xopt[i]) ; } vgrad = del * sqrt(gg) ; if(vgrad>vbig/10) { //C Make the replacement if it provides a larger value of VBIG. for(ghg=k=0;k<npt;k++) { for(temp=j=0;j<n;j++) temp += xpt[k][j] * gl[j] ; ghg += pqw[k] * temp * temp ; } vnew = vgrad + fabs(0.5*del*del*ghg/gg) ; if(vnew>vbig) { vbig = vnew ; stp = del / sqrt(gg) ; if(ghg<0) stp = -stp ; for(i=0;i<n;i++) step[i] = stp * gl[i] ; } if(nact!=0&&nact!=n) { //C Overwrite GL by its projection. Then set VNEW to the greatest //C value of |LFUNC| on the projected gradient from XOPT subject to //C the trust region bound. If VNEW is sufficiently large, then STEP //C may be changed to a move along the projected gradient. for(k=nact;k<n;k++) for(w[k]=i=0;i<n;i++) w[k] += gl[i] * qfac[k][i] ; for(gg=i=0;i<n;i++) { for(gl[i]=0,k=nact;k<n;k++) gl[i] += qfac[k][i] * w[k] ; gg += gl[i] * gl[i] ; } vgrad = del * sqrt(gg) ; if(vgrad>vbig/10) { for(ghg=k=0;k<npt;k++) { for(temp=j=0;j<n;j++) temp += xpt[k][j] * gl[j] ; ghg += pqw[k] * temp * temp ; } vnew = vgrad + fabs(0.5*del*del*ghg/gg) ; //C Set W to the possible move along the projected gradient. stp = del / sqrt(gg) ; if(ghg<0) stp = -stp ; for(ww=i=0;i<n;i++) { w[i] = stp * gl[i] ; ww += w[i]*w[i] ; } //C Set STEP to W if W gives a sufficiently large value of the modulus //C of the Lagrange function, and if W either preserves feasibility //C or gives a constraint violation of at least 0.2*DEL. The purpose //C of CTOL below is to provide a check on feasibility that includes //C a tolerance for contributions from computer rounding errors. if(vnew/vbig>=0.2) { for(ifeas=1,bigv=j=0;j<m;j++) { if(istat[j]==1) { for(temp=-rescon[j],i=0;i<n;i++) temp += w[i] * amat[j][i] ; if(temp>bigv) bigv = temp ; } if(bigv>=test) { ifeas = 0 ; break ; } } ctol = 0 ; temp = sqrt(ww) / 100 ; if(bigv>0&&bigv<temp) for(k=0;k<nact;k++) { for(j=iact[k],sum=i=0;i<n;i++) sum += w[i] * amat[j][i] ; if(fabs(sum)>ctol) ctol = fabs(sum) ; } if(bigv<=10*ctol||bigv>=test) { for(i=0;i<n;i++) step[i] = w[i] ; free(istat,w) ; return ifeas ; } } } } } //C Calculate the greatest constraint violation at XOPT+STEP with STEP at //C its original value. Modify STEP if this violation is unacceptable. for(ifeas=1,bigv=resmax=jsav=j=0;j<m;j++) if(istat[j]>=0) { for(temp=-rescon[j],i=0;i<n;i++) temp += step[i] * amat[j][i] ; if(resmax<temp) resmax = temp ; if(temp>=test) { ifeas = 0 ; break ; } if(temp>bigv) { bigv = temp ; jsav = j ; ifeas = -1 ; } } if(ifeas<0) { for(i=0;i<n;i++) step[i] += (test-bigv) * amat[jsav][i] ; ifeas = 0 ; } free(istat,w) ; return ifeas ; } /* -------------------------------------------------------------------------- */ void lincupdate(int n,int npt,double **xpt,double **bmat,double **zmat,int &idz, int ndim,double *sp,double *step,int kopt,int &knew) { int i,j,k,nptm=npt-n-1,jp ; double sum,ssq,temp,tempa,tempb,distsq,hdiag,denom,denabs,denmax ; double beta,*vlag=vector(npt+n),*w=vector(npt) ; //C The arguments N, NPT, XPT, BMAT, ZMAT, IDZ, NDIM ,SP and STEP are //C identical to the corresponding arguments in SUBROUTINE LINCOB. //C KOPT is such that XPT(KOPT,.) is the current trust region centre. //C KNEW on exit is usually positive, and then it is the index of an //C interpolation point to be moved to the position XPT(KOPT,.)+STEP(.). //C It is set on entry either to its final value or to 0. In the latter //C case, the final value of KNEW is chosen to maximize the denominator //C of the matrix updating formula times a weighting factor. //C VLAG and W are used for working space, the first NPT+N elements of //C both of these vectors being required. //C //C The arrays BMAT and ZMAT with IDZ are updated, the new matrices being //C the ones that are suitable after the shift of the KNEW-th point to //C the new position XPT(KOPT,.)+STEP(.). A return with KNEW set to zero //C occurs if the calculation fails due to a zero denominator in the //C updating formula, which should never happen. for(k=0;k<npt;k++) { w[k] = sp[npt+k] * (sp[npt+k]/2+sp[k]) ; for(vlag[k]=j=0;j<n;j++) vlag[k] += bmat[j][k] * step[j] ; } for(ssq=i=0;i<n;i++) ssq += step[i] * step[i] ; beta = calcvlagbeta(n,npt,vlag,zmat,bmat,w,xpt[kopt],step,beta,ssq,sp[kopt],idz) ; vlag[kopt] += 1 ; //C If KNEW is zero initially, then pick the index of the interpolation //C point to be deleted, by maximizing the absolute value of the //C denominator of the updating formula times a weighting factor. if(knew<0) knew = chooseknew(n,npt,beta,zmat,vlag,xpt,xpt[kopt],0,0,-1,idz) ; //C and call the NEWUOA update to do the rest idz = update(n,npt,bmat,zmat,idz,vlag,beta,knew) ; free(vlag,w) ; } /* -------------------------------------------------------------------------- */ int prelim(int n,int npt,int m,double **amat,double *b,double *x,double rhobeg, double *xbase,double **xpt,double *fval,double *xsav, double *gopt,int &kopt,double *pq,double **bmat,double **zmat, int ndim,double *sp,double *rescon,double (*func)(double *)) { int nptm=npt-n-1,kbase=0,i,j,k,itemp,ipt,jpt,jsav,jp,nf,idz ; double rhosq=rhobeg*rhobeg,recip=1/rhosq,test=0.2*rhobeg,feas,bigv,resid,f ; double temp,reciq=sqrt(0.5)/rhosq ; double *step=vector(n),*w=vector(n+npt) ; //C The arguments N, NPT, M, AMAT, B, X, RHOBEG, IPRINT, XBASE, XPT, FVAL, //C XSAV, XOPT, GOPT, HQ, PQ, BMAT, ZMAT, NDIM, SP and RESCON are the //C same as the corresponding arguments in SUBROUTINE LINCOB. //C KOPT is set to the integer such that XPT(KOPT,.) is the initial trust //C region centre. //C IDZ is going to be set to one, so that every element of Diag(DZ) is //C one in the product ZMAT times Diag(DZ) times ZMAT^T, which is the //C factorization of the leading NPT by NPT submatrix of H. //C STEP, PQW and W are used for working space, the arrays STEP and PQW //C being taken from LINCOB. The length of W must be at least N+NPT. //C //C SUBROUTINE PRELIM provides the elements of XBASE, XPT, BMAT and ZMAT //C for the first iteration, an important feature being that, if any of //C of the columns of XPT is an infeasible point, then the largest of //C the constraint violations there is at least 0.2*RHOBEG. It also sets //C the initial elements of FVAL, XOPT, GOPT, HQ, PQ, SP and RESCON. //C Set the initial elements of XPT, BMAT, SP and ZMAT to zero. idz = 0 ; for(j=0;j<n;j++) { xbase[j] = x[j] ; for(k=0;k<npt;k++) xpt[k][j] = 0 ; for(i=0;i<ndim;i++) bmat[j][i] = 0 ; } for(k=0;k<npt;k++) { sp[k] = 0 ; for(j=0;j<nptm;j++) zmat[j][k] = 0 ; } //C Set the nonzero coordinates of XPT(K,.), K=1,2,...,min[2*N+1,NPT], //C but they may be altered later to make a constraint violation //C sufficiently large. The initial nonzero elements of BMAT and of //C the first min[N,NPT-N-1] columns of ZMAT are set also. //C for(j=0;j<n;j++) { xpt[j+1][j] = rhobeg ; if(j<nptm) { jp = n + j + 1 ; xpt[jp][j] = -rhobeg ; bmat[j][j+1] = rhobeg/2 ; bmat[j][jp] = -rhobeg/2 ; zmat[j][0] = -2*reciq ; zmat[j][j+1] = zmat[j][jp] = reciq ; } else { bmat[j][0] = -(bmat[j][j+1] = 1/rhobeg) ; bmat[j][npt+j] = -rhosq/2 ; } } //C Set the remaining initial nonzero elements of XPT and ZMAT when the //C number of interpolation points exceeds 2*N+1. for(k=n;k<nptm;k++) { itemp = k / n ; ipt = k - itemp*n ; jpt = ipt + itemp ; if(jpt>=n) jpt -= n ; xpt[n+k+1][ipt] = xpt[n+k+1][jpt] = rhobeg ; zmat[k][0] = zmat[k][n+k+1] = recip ; zmat[k][ipt+1] = zmat[k][jpt+1] = -recip ; } //C Update the constraint right hand sides to allow for the shift XBASE. for(j=0;j<m;j++) { for(temp=i=0;i<n;i++) temp += amat[j][i] * xbase[i] ; b[j] -= temp ; } //C Go through the initial points, shifting every infeasible point if //C necessary so that its constraint violation is at least 0.2*RHOBEG. for(nf=0;nf<npt;nf++) { for(feas=1,bigv=j=0;j<m&&nf>0;j++) { for(resid=-b[j],i=0;i<n;i++) resid += xpt[nf][i] * amat[j][i] ; if(resid<=bigv) continue ; bigv = resid ; jsav = j ; if(resid>test) { feas = 0 ; break ; } else feas = -1 ; } if(feas<0) { for(i=0;i<n;i++) step[i] = xpt[nf][i] + (test-bigv)*amat[jsav][i] ; for(k=0;k<npt;k++) for(sp[npt+k]=j=0;j<n;j++) sp[npt+k] += xpt[k][j]*step[j] ; lincupdate(n,npt,xpt,bmat,zmat,idz,ndim,sp,step,kbase,nf) ; for(i=0;i<n;i++) xpt[nf][i] = step[i] ; } //C Calculate the objective function at the current interpolation point, //C and set KOPT to the index of the first trust region centre. for(j=0;j<n;j++) x[j] = xbase[j] + xpt[nf][j] ; f = feas ; f = func(x) ; if(nf==0) kopt = 0 ; else if(f<fval[kopt]&&feas>0) kopt = nf ; fval[nf] = f ; } //C Set PQ for the first quadratic model. for(j=0;j<nptm;j++) for(w[j]=k=0;k<npt;k++) w[j] += zmat[j][k]*fval[k] ; for(k=0;k<npt;k++) for(pq[k]=j=0;j<nptm;j++) pq[k] += zmat[j][k] * w[j] ; //C Set XOPT, SP, GOPT and HQ for the first quadratic model. for(j=0;j<n;j++) { xsav[j] = xbase[j] + xpt[kopt][j] ; gopt[j] = 0 ; } for(k=0;k<npt;k++) { for(sp[k]=j=0;j<n;j++) sp[k] += xpt[k][j] * xpt[kopt][j] ; temp = pq[k] * sp[k] ; for(j=0;j<n;j++) gopt[j] += fval[k]*bmat[j][k] + temp*xpt[k][j] ; } //C Set the initial elements of RESCON. for(j=0;j<m;j++) { for(rescon[j]=b[j],i=0;i<n;i++) rescon[j] -= xpt[kopt][i] * amat[j][i] ; if(rescon[j]<0) rescon[j] = 0 ; if(rescon[j]>=rhobeg) rescon[j] = -rescon[j] ; } free(step,w) ; return idz ; } /* -------------------------------------------------------------------------- */ static int cond=-6 ; int lincoacond() { return cond ; } double lincob(int n,int npt,int m,double **amat,double *b,double *x, double rhobeg,double rhoend,int maxfun,int ndim, double (*func)(double *)) { //C The arguments N, NPT, M, X, RHOBEG, RHOEND, IPRINT and MAXFUN are //C identical to the corresponding arguments in SUBROUTINE LINCOA. //C AMAT is a matrix whose columns are the constraint gradients, scaled //C so that they have unit length. //C B contains on entry the right hand sides of the constraints, scaled //C as above, but later B is modified for variables relative to XBASE. //C XBASE holds a shift of origin that should reduce the contributions //C from rounding errors to values of the model and Lagrange functions. //C XPT contains the interpolation point coordinates relative to XBASE. //C FVAL holds the values of F at the interpolation points. //C XSAV holds the best feasible vector of variables so far, without any //C shift of origin. //C XOPT is set to XSAV-XBASE, which is the displacement from XBASE of //C the feasible vector of variables that provides the least calculated //C F so far, this vector being the current trust region centre. //C GOPT holds the gradient of the quadratic model at XSAV = XBASE+XOPT. //C HQ holds the explicit second derivatives of the quadratic model. //C PQ contains the parameters of the implicit second derivatives of the //C quadratic model. //C BMAT holds the last N columns of the big inverse matrix H. //C ZMAT holds the factorization of the leading NPT by NPT submatrix //C of H, this factorization being ZMAT times Diag(DZ) times ZMAT^T, //C where the elements of DZ are plus or minus one, as specified by IDZ. //C NDIM is the first dimension of BMAT and has the value NPT+N. //C STEP is employed for trial steps from XOPT. It is also used for working //C space when XBASE is shifted and in PRELIM. //C SP is reserved for the scalar products XOPT^T XPT(K,.), K=1,2,...,NPT, //C followed by STEP^T XPT(K,.), K=1,2,...,NPT. //C XNEW is the displacement from XBASE of the vector of variables for //C the current calculation of F, except that SUBROUTINE TRSTEP uses it //C for working space. //C IACT is an integer array for the indices of the active constraints. //C RESCON holds useful information about the constraint residuals. Every //C nonnegative RESCON(J) is the residual of the J-th constraint at the //C current trust region centre. Otherwise, if RESCON(J) is negative, the //C J-th constraint holds as a strict inequality at the trust region //C centre, its residual being at least |RESCON(J)|; further, the value //C of |RESCON(J)| is at least the current trust region radius DELTA. //C QFAC is the orthogonal part of the QR factorization of the matrix of //C active constraint gradients, these gradients being ordered in //C accordance with IACT. When NACT is less than N, columns are added //C to QFAC to complete an N by N orthogonal matrix, which is important //C for keeping calculated steps sufficiently close to the boundaries //C of the active constraints. //C RFAC is the upper triangular part of this QR factorization, beginning //C with the first diagonal element, followed by the two elements in the //C upper triangular part of the second column and so on. //C PQW is used for working space, mainly for storing second derivative //C coefficients of quadratic functions. Its length is NPT+N. //C The array W is also used for working space. The required number of //C elements, namely MAX[M+3*N,2*M+N,2*NPT], is set in LINCOA. int i,j,k,np=n+1,nptm=npt-n-1,ncall,nf,nvala,nvalb,knew,iflag,iter ; int itest,kopt,ksave,ifeas,nact,idz=-1,ih,wlen=max(m+3*n,max(2*m+n,2*npt)) ; double delta,fopt,rho,fsave,ratio,xoptsq,sum,sumz,qoptsq,temp,delsav,snorm ; double vquad,xdiff,del,vqalt,diff,f,ssq,dffalt,distsq ; double *xbase=vector(n),**xpt=matrix(npt,n),*fval=vector(npt) ; double *xsav=vector(n),*xopt=vector(n),*gopt=vector(n) ; double *hq=vector((n*(n+1))/2),*pq=vector(npt),**bmat=matrix(n,ndim) ; double **zmat=matrix(nptm,npt),*step=vector(n),*sp=vector(2*npt) ; double *xnew=vector(n),*rescon=vector(m),**qfac=matrix(n,n) ; double *rfac=vector((n*(n+1))/2),*pqw=vector(npt),*w=vector(wlen) ; int *iact=ivector(n) ; //C Set the elements of XBASE, XPT, FVAL, XSAV, XOPT, GOPT, HQ, PQ, BMAT, //C ZMAT and SP for the first iteration. An important feature is that, //C if the interpolation point XPT(K,.) is not feasible, where K is any //C integer from [1,NPT], then a change is made to XPT(K,.) if necessary //C so that the constraint violation is at least 0.2*RHOBEG. Also KOPT //C is set so that XPT(KOPT,.) is the initial trust region centre. idz = prelim(n,npt,m,amat,b,x,rhobeg,xbase,xpt,fval,xsav,gopt,kopt,pq,bmat, zmat,ndim,sp,rescon,func) ; for(j=0;j<n;j++) xopt[j] = xpt[kopt][j] ; //C Begin the iterative procedure. fopt = fval[kopt] ; delta = rho = rhobeg ; itest = 3 ; ifeas = nact = nvala = nvalb = 0 ; cond = knew = -1 ; nf = npt ; for(iter=0;;iter++) // begin main loop { fsave = fopt ; //C Shift XBASE if XOPT may be too far from XBASE. First make the changes //C to BMAT that do not depend on ZMAT. for(xoptsq=i=0;i<n;i++) xoptsq += xopt[i] * xopt[i] ; if(xoptsq>=1e4*delta*delta) { shiftxbase(n,npt,xpt,xopt,step,bmat,zmat,pq,hq,idz) ; for(k=0;k<npt;k++) sp[k] = 0 ; //C Update the right hand sides of the constraints. for(j=0;j<m;j++) { for(temp=i=0;i<n;i++) temp += amat[j][i] * xopt[i] ; b[j] -= temp ; } for(j=0;j<n;j++) { xbase[j] += xopt[j] ; xopt[j] = xpt[kopt][j] = 0 ; } } //C In the case KNEW=0, generate the next trust region step by calling //C TRSTEP, where SNORM is the current trust region radius initially. //C The final value of SNORM is the length of the calculated step, //C except that SNORM is zero on return if the projected gradient is //C unsuitable for starting the conjugate gradient iterations. delsav = delta ; ksave = knew ; iflag = 0 ; if(knew==-1) { snorm = delta ; for(i=0;i<n;i++) xnew[i] = gopt[i] ; ncall = trstep(n,npt,m,amat,b,xpt,hq,pq,nact,iact,rescon,qfac,rfac, snorm,step,xnew) ; //C A trust region step is applied whenever its length, namely SNORM, is at //C least HALF*DELTA. It is also applied if its length is at least 0.1999 //C times DELTA and if a line search of TRSTEP has caused a change to the //C active set. Otherwise there is a branch below to label 530 or 560. if(ncall>1) temp = 0.1999 * delta ; else temp = delta / 2 ; if(snorm<=temp) { if(delta<=2.8*rho) delta = rho ; else delta /= 2 ; nvala += 1 ; nvalb += 1 ; temp = snorm / rho ; if(delsav>rho) temp = 1 ; if(temp>=0.5) nvala = 0 ; if(temp>=0.1) nvalb = 0 ; if(delsav>rho||(nvala<5&&nvalb<3)) iflag = 1 ; else if(snorm>0) iflag = 2 ; else iflag = 3 ; } else nvala = nvalb = 0 ; } else { //C Alternatively, KNEW is positive. Then the model step is calculated //C within a trust region of radius DEL, after setting the gradient at //C XBASE and the second derivative parameters of the KNEW-th Lagrange //C function in W(1) to W(N) and in PQW(1) to PQW(NPT), respectively. if(0.1*delta>rho) del = 0.1*delta ; else del = rho ; for(i=0;i<n;i++) w[i] = bmat[i][knew] ; genpqw(n,npt,pqw,zmat,knew,idz) ; ifeas = qmstep(n,npt,m,amat,xpt,xopt,nact,iact,rescon,qfac,kopt,knew,del, step,w,pqw) ; } //C Set VQUAD to the change to the quadratic model when the move STEP is //C made from XOPT. If STEP is a trust region step, then VQUAD should be //C negative. If it is nonnegative due to rounding errors in this case, //C there is a branch to label 530 to try to improve the model. if(iflag==0) { for(vquad=ih=j=0;j<n;j++) for(vquad+=step[j]*gopt[j],i=0;i<=j;i++,ih++) { temp = step[i] * step[j] ; if(i==j) temp /= 2 ; vquad += temp * hq[ih] ; } for(k=0;k<npt;k++) { for(temp=j=0;j<n;j++) temp += xpt[k][j] * step[j] ; sp[npt+k] = temp ; vquad += 0.5 * pq[k] * temp * temp ; } if(ksave==-1&&vquad>=0) iflag = 1 ; } //C Calculate the next value of the objective function. The difference //C between the actual new value of F and the value predicted by the //C model is recorded in DIFF. if(iflag==0) { nf += 1 ; if(nf>maxfun) { cond = 2 ; break ; } for(xdiff=i=0;i<n;i++) { xnew[i] = xopt[i] + step[i] ; x[i] = xbase[i] + xnew[i] ; temp = x[i] - xsav[i] ; xdiff += temp * temp ; } if(ksave==-2) xdiff = rho ; else xdiff = sqrt(xdiff) ; if(xdiff<=0.1*rho||xdiff>=2*delta) { ifeas = 0 ; cond = 1 ; break ; } if(ksave<0) ifeas = 1 ; f = func(x) ; if(ksave==-2) { cond = 0 ; break ; } diff = f - fopt - vquad ; //C If X is feasible, then set DFFALT to the difference between the new //C value of F and the value predicted by the alternative model. if(ifeas==1&&itest<3) { for(k=0;k<npt;k++) { pqw[k] = 0 ; w[k] = fval[k] - fval[kopt] ; } for(j=0;j<nptm;j++) { for(sum=k=0;k<npt;k++) sum += w[k] * zmat[j][k] ; if(j<idz) sum = -sum ; for(k=0;k<npt;k++) pqw[k] += sum * zmat[j][k] ; } for(vqalt=k=0;k<npt;k++) { for(sum=j=0;j<n;j++) sum += bmat[j][k] * step[j] ; vqalt += sum * w[k] ; vqalt += pqw[k] * sp[npt+k] * (sp[k]+sp[npt+k]/2) ; } dffalt = f - fopt - vqalt ; } else if(itest==3) { dffalt = diff ; itest = 0 ; } //C Pick the next value of DELTA after a trust region step. if(ksave==-1) { ratio = (f-fopt) / vquad ; if(ratio<0.1) delta /= 2 ; else if(ratio<0.7) { if(delta/2>snorm) delta /= 2 ; else delta = snorm ; } else { temp = sqrt(2)*delta ; if(delta/2>2*snorm) delta /= 2 ; else delta = 2*snorm ; if(delta>temp) delta = temp ; } if(delta<=1.4*rho) delta = rho ; } //C Update BMAT, ZMAT and IDZ, so that the KNEW-th interpolation point //C can be moved. If STEP is a trust region step, then KNEW is zero at //C present, but a positive value is picked by subroutine UPDATE. lincupdate(n,npt,xpt,bmat,zmat,idz,ndim,sp,step,kopt,knew) ; if(knew==-1) { cond = 3 ; break ; } //C If ITEST is increased to 3, then the next quadratic model is the //C one whose second derivative matrix is least subject to the new //C interpolation conditions. Otherwise the new model is constructed //C by the symmetric Broyden method in the usual way. if(ifeas==1) { if(fabs(dffalt)>=0.1*fabs(diff)) itest = 0 ; else itest += 1 ; } //C Update the second derivatives of the model by the symmetric Broyden //C method, using PQW for the second derivative parameters of the new //C KNEW-th Lagrange function. The contribution from the old parameter //C PQ(KNEW) is included in the second derivative matrix HQ. W is used //C later for the gradient of the new KNEW-th Lagrange function. if(itest<3) { updatehq(n,npt,hq,pq[knew],xpt,knew) ; pq[knew] = 0 ; genpqw(n,npt,pqw,zmat,knew,idz) ; for(k=0;k<npt;k++) pq[k] += diff * pqw[k] ; } //C Include the new interpolation point with the corresponding updates of //C SP. Also make the changes of the symmetric Broyden method to GOPT at //C the old XOPT if ITEST is less than 3. fval[knew] = f ; sp[knew] = sp[kopt] + sp[npt+kopt] ; for(ssq=i=0;i<n;i++) { xpt[knew][i] = xnew[i] ; ssq += step[i]*step[i] ; } sp[npt+knew] = sp[npt+kopt] + ssq ; if(itest<3) { for(i=0;i<n;i++) w[i] = bmat[i][knew] ; for(k=0;k<npt;k++) for(temp=pqw[k]*sp[k],i=0;i<n;i++) w[i] += temp * xpt[k][i] ; for(i=0;i<n;i++) gopt[i] += diff * w[i] ; } //C Update FOPT, XSAV, XOPT, KOPT, RESCON and SP if the new F is the //C least calculated value so far with a feasible vector of variables. if(f<fopt&&ifeas==1) { fopt = f ; for(j=0;j<n;j++) { xsav[j] = x[j] ; xopt[j] = xnew[j] ; } kopt = knew ; snorm = sqrt(ssq) ; for(j=0;j<m;j++) if(rescon[j]>=delta+snorm) rescon[j] = snorm - rescon[j] ; else { rescon[j] += snorm ; if(rescon[j]+delta>0) { for(temp=b[j],i=0;i<n;i++) temp -= xopt[i] * amat[j][i] ; if(temp<0) temp = 0 ; if(temp>=delta) rescon[j] = -temp ; else rescon[j] = temp ; } } for(k=0;k<npt;k++) sp[k] += sp[npt+k] ; //C Also revise GOPT when symmetric Broyden updating is applied. if(itest<3) { updategopt(n,gopt,hq,step) ; for(k=0;k<npt;k++) for(temp=pq[k]*sp[npt+k],i=0;i<n;i++) gopt[i] += temp * xpt[k][i] ; } } // end if(f<fopt&&ifeas==1) if(itest==3) { //C Replace the current model by the least Frobenius norm interpolant if //C this interpolant gives substantial reductions in the predictions //C of values of F at feasible points. for(k=0;k<npt;k++) { pq[k] = 0 ; w[k] = fval[k] - fval[kopt] ; } for(j=0;j<nptm;j++) { for(sum=k=0;k<npt;k++) sum += w[k] * zmat[j][k] ; if(j<idz) sum = -sum ; for(k=0;k<npt;k++) pq[k] += sum * zmat[j][k] ; } for(j=0;j<n;j++) for(gopt[j]=i=0;i<npt;i++) gopt[j] += w[i] * bmat[j][i] ; for(k=0;k<npt;k++) for(temp=pq[k]*sp[k],i=0;i<n;i++) gopt[i] += temp * xpt[k][i] ; for(i=0;i<(n*(n+1))/2;i++) hq[i] = 0 ; } //C If a trust region step has provided a sufficient decrease in F, then //C branch for another trust region calculation. Every iteration that //C takes a model step is followed by an attempt to take a trust region //C step. knew = -1 ; if(ksave>=0||ratio>=0.1) continue ; } // end if(iflag==0) //C Alternatively, find out if the interpolation points are close enough //C to the best point so far. if(iflag<2) { distsq = max(delta*delta,4*rho*rho) ; for(k=0;k<npt;k++) { for(sum=j=0;j<n;j++) { temp = xpt[k][j] - xopt[j] ; sum += temp * temp ; } if(sum>distsq) { knew = k ; distsq = sum ; } } //C If KNEW is positive, then branch back for the next iteration, which //C will generate a "model step". Otherwise, if the current iteration //C has reduced F, or if DELTA was above its lower bound when the last //C trust region step was calculated, then try a "trust region" step //C instead. if(knew>=0||fopt<fsave||delsav>rho) continue ; } // end if(iflag<2) //C The calculations with the current value of RHO are complete. //C Pick the next value of RHO. if(rho<=rhoend) { cond = 0 ; break ; } delta = rho /2 ; if(rho>250*rhoend) rho *= 0.1 ; else if(rho<16*rhoend) rho = rhoend ; else rho = sqrt(rho*rhoend) ; delta = max(delta,rho) ; knew = -1 ; nvala = nvalb = 0 ; } // end for(iter=0;;iter++) //C Return from the calculation, after branching to label 220 for another //C Newton-Raphson step if it has not been tried before. if(iflag==2) { for(nf++,i=0;i<n;i++) x[i] = xbase[i] + xopt[i] + step[i] ; f = func(x) ; } if(fopt<=f||(ifeas==0&&iflag!=2)) for(f=fopt,i=0;i<n;i++) x[i] = xsav[i] ; free(xbase,fval,xsav,xopt,gopt,hq) ; free(pq,step,sp,xnew,rescon,rfac) ; free(pqw,w,iact) ; freematrix(bmat,zmat,qfac,xpt) ; return f ; } /* -------------------------------------------------------------------------- */ double lincoa(double (*func)(double *),double *x,int n,double **a,double *b, int m,double rhobeg,double rhoend,int maxfun,int npt) { //C This subroutine seeks the least value of a function of many variables, //C subject to general linear inequality constraints, by a trust region //C method that forms quadratic models by interpolation. Usually there //C is much freedom in each new model after satisfying the interpolation //C conditions, which is taken up by minimizing the Frobenius norm of //C the change to the second derivative matrix of the model. One new //C function value is calculated on each iteration, usually at a point //C where the current model predicts a reduction in the least value so //C far of the objective function subject to the linear constraints. //C Alternatively, a new vector of variables may be chosen to replace //C an interpolation point that may be too far away for reliability, and //C then the new point does not have to satisfy the linear constraints. //C The arguments of the subroutine are as follows. //C //C N must be set to the number of variables and must be at least two. //C NPT must be set to the number of interpolation conditions, which is //C required to be in the interval [N+2,(N+1)(N+2)/2]. Typical choices //C of the author are NPT=N+6 and NPT=2*N+1. Larger values tend to be //C highly inefficent when the number of variables is substantial, due //C to the amount of work and extra difficulty of adjusting more points. //C M must be set to the number of linear inequality constraints. //C A is a matrix whose columns are the constraint gradients, which are //C required to be nonzero. //C IA is the first dimension of the array A, which must be at least N. //C B is the vector of right hand sides of the constraints, the J-th //C constraint being that the scalar product of A(.,J) with X(.) is at //C most B(J). The initial vector X(.) is made feasible by increasing //C the value of B(J) if necessary. //C X is the vector of variables. Initial values of X(1),X(2),...,X(N) //C must be supplied. If they do not satisfy the constraints, then B //C is increased as mentioned above. X contains on return the variables //C that have given the least calculated F subject to the constraints. //C RHOBEG and RHOEND must be set to the initial and final values of a //C trust region radius, so both must be positive with RHOEND<=RHOBEG. //C Typically, RHOBEG should be about one tenth of the greatest expected //C change to a variable, and RHOEND should indicate the accuracy that //C is required in the final values of the variables. //C The value of IPRINT should be set to 0, 1, 2 or 3, which controls the //C amount of printing. Specifically, there is no output if IPRINT=0 and //C there is output only at the return if IPRINT=1. Otherwise, the best //C feasible vector of variables so far and the corresponding value of //C the objective function are printed whenever RHO is reduced, where //C RHO is the current lower bound on the trust region radius. Further, //C each new value of F with its variables are output if IPRINT=3. //C MAXFUN must be set to an upper bound on the number of calls of CALFUN, //C its value being at least NPT+1. //C W is an array used for working space. Its length must be at least //C M*(2+N) + NPT*(4+N+NPT) + N*(9+3*N) + MAX [ M+3*N, 2*M+N, 2*NPT ]. //C On return, W(1) is set to the final value of F, and W(2) is set to //C the total number of function evaluations plus 0.5. //C //C SUBROUTINE CALFUN (N,X,F) has to be provided by the user. It must set //C F to the value of the objective function for the variables X(1), //C X(2),...,X(N). The value of the argument F is positive when CALFUN //C is called if and only if the current X satisfies the constraints //C to working accuracy. //C //C Check that N, NPT and MAXFUN are acceptable. double **amat,*bnorm,temp,sum,res ; int i,j,iflag ; if(n<2) { cond = -1 ; return 0 ; } if(npt<n+2||npt>((n+2)*(n+1))/2) { cond = -2 ; return 0 ; } if(maxfun<npt) { cond = -3 ; return 0 ; } amat = matrix(m,n) ; bnorm = vector(m) ; //C Normalize the constraints, and copy the resultant constraint matrix //C and right hand sides into working space, after increasing the right //C hand sides if necessary so that the starting point is feasible. for(j=0;j<m;j++) { for(sum=temp=i=0;i<n;i++) { sum += a[j][i]*x[i] ; temp += a[j][i]*a[j][i] ; } if(temp==0) { cond = -4 ; freematrix(amat) ; free(bnorm) ; return 0 ; } if(sum-b[j]>(1e-6*rhoend)*temp) iflag = 1 ; // if(sum>0) { cond = -5 ; freematrix(amat) ; free(bnorm) ; return 0 ; } temp = sqrt(temp) ; bnorm[j] = max(b[j],sum) / temp ; for(i=0;i<n;i++) amat[j][i] = a[j][i] / temp ; } res = lincob(n,npt,m,amat,bnorm,x,rhobeg,rhoend,maxfun,npt+n,func) ; freematrix(amat) ; free(bnorm) ; return res ; } double lincoa(double (*func)(double *),double *x,int n,double **a,double *b, int m,double rhobeg,double rhoend,int maxfun) { return lincoa(func,x,n,a,b,m,rhobeg,rhoend,maxfun,2*n+1) ; }

Archived from optim.html

#include "memory.h" #include <math.h> #define max(a,b) ((a)>(b)?(a):(b)) #define min(a,b) ((a)<(b)?(a):(b)) double lincoa(double (*func)(double *),double *x,int n,double **a,double *b, int m,double rhobeg,double rhoend,int maxfun,int npt) ; int lincoacond() ; #define newvsn 0 #define pi 3.141592653589793 static double flim ; static int ncall ; double func(double *x) { double v12,v13,v14,v23,v24,v34,del1,del2,del3,del4,f ; ncall += 1 ; v12 = x[0] * x[4] - x[3] * x[1] ; v13 = x[0] * x[7] - x[6] * x[1] ; v14 = x[0] * x[10] - x[9] * x[1] ; v23 = x[3] * x[7] - x[6] * x[4] ; v24 = x[3] * x[10] - x[9] * x[4] ; v34 = x[6] * x[10] - x[9] * x[7] ; del1 = v23 * x[11] - v24 * x[8] + v34 * x[5] ; del2 = -v34 * x[2] - v13 * x[11] + v14 * x[8] ; del3 = -v14 * x[5] + v24 * x[2] + v12 * x[11] ; del4 = -v12 * x[8] + v13 * x[5] - v23 * x[2] ; if(del1>0&&del2>0&&del3>0&&del4>0) { f = del1 + del2 + del3 + del4 ; f = (f*f*f) / (del1*del2*del3*del4) ; return min(f/6,flim) ; } else return flim ; } //C Calculate the tetrahedron of least volume that encloses the points //C (XP(J),YP(J),ZP(J)), J=1,2,...,NP. Our method requires the origin //C to be strictly inside the convex hull of these points. There are //C twelve variables that define the four faces of each tetrahedron //C that is considered. Each face has the form ALPHA*X+BETA*Y+GAMMA*Z=1, //C the variables X(3K-2), X(3K-1) and X(3K) being the values of ALPHA, //C BETA and GAMMA for the K-th face, K=1,2,3,4. Let the set T contain //C all points in three dimensions that can be reached from the origin //C without crossing a face. Because the volume of T may be infinite, //C the objective function is the smaller of FMAX and the volume of T, //C where FMAX is set to an upper bound on the final volume initially. //C There are 4*NP linear constraints on the variables, namely that each //C of the given points (XP(J),YP(J),ZP(J)) shall be in T. Let XS = min //C XP(J), YS = min YP(J), ZS = min ZP(J) and SS = max XP(J)+YP(J)+ZP(J), //C where J runs from 1 to NP. The initial values of the variables are //C X(1)=1/XS, X(5)=1/YS, X(9)=1/ZS, X(2)=X(3)=X(4)=X(6)=X(7) =X(8)=0 //C and X(10)=X(11)=X(12)=1/SS, which satisfy the linear constraints, //C and which provide the bound FMAX=(SS-XS-YS-ZS)**3/6. Other details //C of the test calculation are given below, including the choice of //C the data points (XP(J),YP(J),ZP(J)), J=1,2,...,NP. The smaller final //C value of the objective function in the case NPT=35 shows that the //C problem has local minima. //C int main(int argc,char const* argv[]) { int np=50,ia=12,n=12,m=200,i,j,k ; double xp[50],yp[50],zp[50],theta,sumx,sumy,sumz,xs,ys,zs,ss,res ; double b[200],x[12],**a=matrix(m,n) ; for(sumx=sumy=sumz=j=0;j<np;j++) { theta = j * pi / (np-1) ; sumx += xp[j] = cos(theta) * cos(2*theta) ; sumy += yp[j] = sin(theta) * cos(2*theta) ; sumz += zp[j] = sin(2*theta) ; } sumx /= np ; sumy /= np ; sumz /= np ; for(j=0;j<np;j++) { xp[j] -= sumx ; yp[j] -= sumy ; zp[j] -= sumz ; } for(k=0;k<m;k++) b[k] = 1 ; for(i=0;i<n;i++) for(k=0;k<m;k++) a[k][i] = 0 ; for(j=0;j<np;j++) for(i=0;i<4;i++) { a[4*j+i][3*i ] = xp[j] ; a[4*j+i][3*i+1] = yp[j] ; a[4*j+i][3*i+2] = zp[j] ; } //C Set the initial vector of variables. The JCASE=1,6 loop gives six //C different choices of NPT when LINCOA is called. for(xs=ys=zs=ss=j=0;j<np;j++) { xs = min(xs,xp[j]) ; ys = min(ys,yp[j]) ; zs = min(zs,zp[j]) ; ss = max(ss,xp[j]+yp[j]+zp[j]) ; } flim = ss - xs - ys - zs ; flim *= flim * flim / 6 ; for(j=0;j<6;j++) { for(i=0;i<12;i++) x[i] = 0 ; x[0] = 1 / xs ; x[4] = 1 / ys ; x[8] = 1 / zs ; x[9] = x[10] = x[11] = 1 / ss ; ncall = 0 ; res = lincoa(&func,x,n,a,b,m,1,1e-6,10000,5*j+15) ; printf("optimum = %.15e from %d calls; cond=%d\n",res,ncall,lincoacond()) ; } freematrix(a) ; }

Archived from optim.html

/* Copyright (c) 2016, by Colin Champion <colin.champion@masterlyinactivity.com> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "memory.h" #include <math.h> #define EPS 1e-8 #define debug 0 #define debugper 0 #define rescale 1 struct tableau { double **a,*scale ; int nrow,ncol,nper,*lhvar,*rhvar,nle,nge,neq,n,n0,n1 ; char *lhtype,*rhtype ; void release() { free(lhvar,rhvar,lhtype,rhtype,scale) ; freematrix(a) ; } void delrow(int row) { int i,j ; for(i=row;i<nrow;i++) { for(j=0;j<=ncol+nper;j++) a[i][j] = a[i+1][j] ; lhvar[i] = lhvar[i+1] ; lhtype[i] = lhtype[i+1] ; } nrow -= 1 ; } void delcol(int col) { int i,j ; for(j=col;j<ncol+nper;j++) for(i=0;i<=nrow;i++) a[i][j] = a[i][j+1] ; for(j=col;j<ncol-1;j++) { rhvar[j] = rhvar[j+1] ; rhtype[j] = rhtype[j+1] ; } ncol -= 1 ; } /* ------------------------------------------------------------------------ */
/*page*/ /* ------------------------------------------------------------------------ */ // tableau format: // the total number of columns in the tableau is ncol+1+nper (the extra '1' // is for the constant terms and the extra nper for the perturbations - nper is // the inititial value of nrow, though nrow may get smaller). The columns // include artificial variables and KKT multipliers. // Variables are coded as follows: // x[j] <-> true variables // y[i] <-> slack variables for inequalities, J&B's v // w[j] <-> artificial variables for constraints, NR's z // v[j] <-> KKT multipliers, complementary with x, J&B's y // u[i] <-> " " , " " y, J&B's mu // z[j] <-> artificial variables for stationarity condition // '.' <-> dead variable (could be removed from the tableau) // upper case 'X' are unrestricted; upper case 'U' come from equations rather // than inequalities and are therefore also unrestricted; upper case 'W' come // from equations rather than inequalities and can be eliminated as soon as // they get to the RHS. X and U never move from the LHS. void print() { int i,j ; printf("\n") ; for(i=0;i<=nrow;i++) { if(i==0) { printf("====|") ; for(j=0;j<ncol;j++) printf("======") ; printf("|=======") ; if(debugper) { printf("=|") ; for(j=0;j<nper;j++) printf("======") ; } printf("\n |") ; for(j=0;j<ncol;j++) printf(" %c%d ",rhtype[j],rhvar[j]) ; printf("|") ; if(debugper) printf(" | perturbations") ; printf("\n----|") ; for(j=0;j<ncol;j++) printf("------") ; printf("|-------") ; if(debugper) { printf("-|") ; for(j=0;j<nper;j++) printf("------") ; } printf("\n") ; } if(i<nrow) printf(" %c%d |",lhtype[i],lhvar[i]) ; else printf("obj |") ; for(j=0;j<ncol;j++) printf("%5.2f ",a[i][j]) ; printf("|%7.2f",a[i][ncol]) ; if(debugper) { printf(" |") ; for(j=ncol+1;j<=ncol+nper;j++) printf("%5.2f ",a[i][j]) ; } printf("\n") ; if(i==nrow-1) { printf("----|") ; for(j=0;j<ncol;j++) printf("------") ; printf("|-------") ; if(debugper) { printf("-|") ; for(j=0;j<nper;j++) printf("------") ; } printf("\n") ; } } } /* ------------------------------------------------------------------------ */
/*page*/ /* ------------------------------------------------------------------------ */ tableau(double **Q,double *A,int na,int nb,double **le,double *lerhs,int m1, double **ge,double *gerhs,int m2,double **eq,double *eqrhs,int m3) { int i,j,ind,m=m1+m2+m3 ; double q,sum ; n0 = na ; n1 = nb ; n = n0 + n1 ; nper = nrow = m + n ; ncol = m + 2*n ; nle = nge = neq = 0 ; a = matrix(nrow+1,ncol+ncol+1) ; scale = vector(n) ; // first m rows of the tableau state primal feasibility for(ind=i=0;i<m1;i++) if(lerhs[i]>=0) { for(j=0;j<n;j++) a[ind][j] = -le[i][j] ; a[ind++][ncol] = lerhs[i] ; } for(i=0;i<m2;i++) if(gerhs[i]<0) { for(j=0;j<n;j++) a[ind][j] = ge[i][j] ; a[ind++][ncol] = -gerhs[i] ; } nle = ind ; nge = m1 + m2 - nle ; neq = m3 ; for(i=0;i<m1;i++) if(lerhs[i]<0) { for(j=0;j<n;j++) a[ind][j] = le[i][j] ; a[ind++][ncol] = -lerhs[i] ; } for(i=0;i<m2;i++) if(gerhs[i]>=0) { for(j=0;j<n;j++) a[ind][j] = -ge[i][j] ; a[ind++][ncol] = gerhs[i] ; } for(i=0;i<m3;ind++,i++) if(eqrhs[i]>=0) { for(j=0;j<n;j++) a[ind][j] = -eq[i][j] ; a[ind][ncol] = eqrhs[i] ; } else { for(j=0;j<n;j++) a[ind][j] = eq[i][j] ; a[ind][ncol] = -eqrhs[i] ; } for(i=0;i<n;i++) scale[i] = 1 ; if(rescale>debug) { // scale the quadratic so that terms are of order 1 for(sum=i=0;i<n;i++) for(j=0;j<n;j++) sum += fabs(Q[i][j]) ; for(i=0;i<n;i++) sum += n * A[i] * A[i] ; sum = 1 / sqrt(sum/(2*n)) ; for(i=0;i<n;i++) scale[i] = sum ; // ... and scale the equations to be of order 1/scale[*] for(i=0;i<m;i++) { for(sum=a[i][ncol]*a[i][ncol],j=0;j<n;j++) sum += a[i][j] * a[i][j] ; q = 1 / ( scale[0] * sqrt(sum/(n+1)) ) ; for(a[i][ncol]*=q,j=0;j<n;j++) a[i][j] *= q ; } // now introduce a separate scale factor for each variable for(j=0;j<n;j++) { for(i=0;i<n;i++) sum += scale[i] * scale[j] * fabs(Q[i][j]) ; sum = m * ( sum + n*scale[j]*scale[j]*A[j]*A[j] ) ; for(i=0;i<m;i++) sum += n * scale[j] * scale[j] * a[i][j] * a[i][j] ; scale[j] /= sqrt(sum/(3*m*n)) ; } } for(i=0;i<m;i++) for(j=0;j<n;j++) a[i][j] *= scale[j] ; /* ---------------------------------------------------------------------- */
/*page*/ /* ---------------------------------------------------------------------- */ // remaining n rows state the KKT stationarity condition for(i=0;i<n;i++) { for(j=0;j<n;j++) a[m+i][j] = Q[i][j] * scale[i] * scale[j] ; a[m+i][n+i] = 1 ; for(j=0;j<nle;j++) a[m+i][2*n+j] = a[j][i] ; for(;j<nle+nge;j++) a[m+i][2*n+j] = -a[j][i] ; // correcting Wolfe for(;j<m;j++) a[m+i][2*n+j] = a[j][i] ; a[m+i][ncol] = A[i] * scale[i] ; if(A[i]<0) for(j=0;j<=ncol;j++) a[m+i][j] = -a[m+i][j] ; } // perturbation coefficients go to the right of the constant term for(i=0;i<nrow;i++) a[nrow][ncol+1+i] = a[i][ncol+1+i] = 1 ; // variable ids lhvar = ivector(nrow) ; rhvar = ivector(ncol) ; lhtype = charvector(nrow+1) ; rhtype = charvector(ncol+1) ; for(i=0;i<n0;i++) { rhvar[i] = i ; rhtype[i] = 'x' ; } for(i=n0;i<n;i++) { rhvar[i] = i ; rhtype[i] = 'X' ; } for(i=0;i<n;i++) { rhvar[n+i] = i ; rhtype[n+i] = 'v' ; } for(i=0;i<nle+nge;i++) { rhvar[2*n+i] = i ; rhtype[2*n+i] = 'u' ; } for(i=nle+nge;i<m;i++) { rhvar[2*n+i] = i ; rhtype[2*n+i] = 'U' ; } for(i=0;i<nle;i++) { lhvar[i] = i ; lhtype[i] = 'y' ; } // y for <= for(i=nle;i<nle+nge;i++) { lhvar[i] = i ; lhtype[i] = 'w' ; } // w for >= for(i=nle+nge;i<m;i++) { lhvar[i] = i ; lhtype[i] = 'W' ; } // w for == for(i=0;i<n;i++) { lhvar[m+i] = i ; lhtype[m+i] = 'z' ; } // objective function for(i=0;i<nrow;i++) if(lhtype[i]=='w'||lhtype[i]=='W'||lhtype[i]=='z') for(j=0;j<=ncol;j++) a[nrow][j] -= a[i][j] ; } /* ------------------------------------------------------------------------ */ void pivot(int pivrow,int pivcol) { int i,j ; double piv=1/a[pivrow][pivcol],q ; for(i=0;i<nrow+1;i++) if(i!=pivrow) { for(q=a[i][pivcol]*piv,j=0;j<=ncol+nrow;j++) a[i][j] -= a[pivrow][j] * q ; a[i][pivcol] = q ; } for(j=0;j<=ncol+nper;j++) a[pivrow][j] *= -piv ; a[pivrow][pivcol] = piv ; swap(rhvar[pivcol],lhvar[pivrow]) ; swap(rhtype[pivcol],lhtype[pivrow]) ; } /* ------------------------------------------------------------------------ */
/*page*/ /* ------------------------------------------------------------------------ */ // return -2 if no pivot found, -1 if obj fn can be increased without limit int findpivot() { int i,k,pivrow,pivcol,ret,ind,rtype,ltype,iforce,pivgood,igood,take ; double rmax,r,rbest,qobj,qcell,delscore ; for(ret=-2,pivcol=0;pivcol<ncol;pivcol++) { // complementarity condition: if iforce>=0 it is the only legal i value rtype = rhtype[pivcol] ; if(rtype=='u') ltype = 'y' ; else if(rtype=='y') ltype = 'u' ; else if(rtype=='v') ltype = 'x' ; else if(rtype=='x') ltype = 'v' ; else ltype = '$' ; // dummy for(iforce=-1,i=0;i<nrow&&iforce<0;i++) if(lhtype[i]==ltype&&lhvar[i]==rhvar[pivcol]) iforce = i ; qobj = a[nrow][pivcol] ; if(qobj>EPS) { for(pivrow=-1,i=0;i<nrow;i++) if((qcell=a[i][pivcol])<0) if(lhtype[i]!='U'&&lhtype[i]!='X') { r = -a[i][ncol] * qobj / qcell ; take = 0 ; igood = (iforce<0||i==iforce) ; if(pivrow<0||r<rbest-EPS) { take = 1 ; pivgood = igood ; } else if(igood&&r<rbest+EPS) // tie for objective function { if( rtype=='U' || rtype=='X' || lhtype[i]=='z' || lhtype[i]=='w' || lhtype[i]=='W' ) take = pivgood = 1 ; // redundant safeguard else for(k=ncol+1;k<=ncol+nper;k++) { delscore = -a[i][k] * qobj / qcell ; if(delscore>EPS) { take = pivgood = 1 ; break ; } else if(delscore<-EPS) break ; } } if(take) { pivrow = i ; rbest = r ; } } if(pivrow<0) return -1 ; if(pivgood&&(ret<0||rbest>rmax)) { rmax = rbest ; ret = pivrow + nrow*pivcol ; } } else if(rtype=='U'||rtype=='X') { for(pivrow=-1,i=0;i<nrow;i++) if(a[i][pivcol]>0) if(lhtype[i]!='U'&&lhtype[i]!='X') { r = - a[i][ncol] * qobj / a[i][pivcol] ; if(pivrow<0||r<rbest) { pivrow = i ; rbest = r ; } } if(qobj<-EPS&&pivrow<0) return -1 ; if(ret<0||rbest>rmax) { rmax = rbest ; ret = pivrow + nrow*pivcol ; } } } // end loop over pivcol return ret ; } /* ------------------------------------------------------------------------ */
/*page*/ /* ------------------------------------------------------------------------ */ int simplexalg(double *x) { int i,row,col,ret,iter ; double qmax,qmaxiter ; for(i=0;i<n;i++) x[i] = 0 ; if(debug) print() ; for(qmaxiter=1,i=0;i<nrow;i++) qmaxiter *= (nrow+ncol-i) / (1.0+i) ; for(iter=0;iter<qmaxiter&&(ret=findpivot())>=0;iter++) { row = ret % nrow ; col = ret / nrow ; pivot(row,col) ; // unrestricted LH Lagrangians U and artificial RH vbles can be zapped if(lhtype[row]=='U') delrow(row) ; if(rhtype[col]=='W'||rhtype[col]=='z') delcol(col) ; // the artificial variable for a '>=' needs to become a slack variable else if(rhtype[col]=='w') { rhtype[col] = 'y' ; a[nrow][col] = -(1+a[nrow][col]) ; for(i=0;i<nrow;i++) a[i][col] = -a[i][col] ; } for(row=0;row<nrow;row++) if(lhtype[row]!= 'X') if(a[row][ncol]<0) a[row][ncol] = 0 ; if(debug) print() ; } if(iter>=qmaxiter) ret = -3 ; if(ret==-2&&a[nrow][ncol]>-EPS) ret = 0 ; for(i=0;i<nrow;i++) if(lhtype[i]=='x'||lhtype[i]=='X') x[lhvar[i]] = a[i][ncol] * scale[lhvar[i]] ; return ret ; } } ; /* -------------------------------------------------------------------------- */ int quadprog(double **Q,double *A,double *x,int n0,int n1, double **le,double *lerhs,int nle, double **ge,double *gerhs,int nge, double **eq,double *eqrhs,int neq) { tableau t(Q,A,n0,n1,le,lerhs,nle,ge,gerhs,nge,eq,eqrhs,neq) ; int ret = t.simplexalg(x) ; t.release() ; return ret ; } int quadprog(double **Q,double *A,double *x,int n) { return quadprog(Q,A,x,n,0, 0,0,0, 0,0,0, 0,0,0) ; }

Archived from optim.html

#include "memory.h" int quadprog(double **qmat,double *qadd,double *x,int n0,int n1, double **le,double *lerhs,int nle, double **ge,double *gerhs,int nge, double **eq,double *eqrhs,int neq) ; void printsoln(int ret,double *x,int n) { if(ret==0) { printf("---- solution: ") ; for(int i=0;i<n;i++) printf("%.2f ",x[i]) ; printf("\n") ; } else printf("---- no solution: ret=%d\n",ret) ; } int main(int argc,char **argv) { int i,j,ret,probno=0 ; double x[3],**Q=matrix(3,3),A[3],**eq=matrix(2,3),eqrhs[2] ; if(argc>=2) probno = atoi(argv[1]) ; // the first four problems find the closest point to (-1,-1) // subject to various linear constraints for(i=0;i<3;i++) { Q[i][i] = -1 ; A[i] = -1 ; } eq[0][0] = eq[0][1] = 1 ; eqrhs[0] = 2 ; // x + y = 2 if(probno==1||probno<=0) { printf("---- problem 1 [right ans = (1.00,1.00)]\n") ; ret = quadprog(Q,A,x,2,0, 0,0,0, 0,0,0, eq,eqrhs,1 ) ; printsoln(ret,x,2) ; } // x + y >= 2 if(probno==2||probno<=0) { printf("\n---- problem 2 [right ans = (1.00,1.00)]\n") ; ret = quadprog(Q,A,x,2,0, 0,0,0, eq,eqrhs,1, 0,0,0 ) ; printsoln(ret,x,2) ; } // x + 2y >= -1 if(probno==3||probno<=0) { printf("\n---- problem 3 [right ans = (0.00,-0.50)]\n") ; eq[0][0] = 1 ; eq[0][1] = 2 ; eqrhs[0] = -1 ; ret = quadprog(Q,A,x,1,1, 0,0,0, eq,eqrhs,1, 0,0,0 ) ; printsoln(ret,x,2) ; } // 3x + 4y <= -32 if(probno==4||probno<=0) { printf("\n---- problem 4 [right ans = (-4.00,-5.00)]\n") ; eq[0][0] = 3 ; eq[0][1] = 4 ; eqrhs[0] = -32 ; ret = quadprog(Q,A,x,0,2, eq,eqrhs,1, 0,0,0, 0,0,0 ) ; printsoln(ret,x,2) ; } if(probno==5||probno<=0) { printf("\n---- problem 5 (J&B) [right ans = (3.00,2.00)]\n") ; Q[0][0] = -1 ; Q[1][1] = -4 ; A[0] = 4 ; A[1] = 8 ; eq[0][0] = eq[0][1] = 1 ; eqrhs[0] = 5 ; eq[1][0] = 1 ; eq[1][1] = 0 ; eqrhs[1] = 3 ; ret = quadprog(Q,A,x,2,0, eq,eqrhs,2, 0,0,0, 0,0,0 ) ; printsoln(ret,x,2) ; } if(probno==6||probno<=0) { printf("\n---- problem 6 (Wolfe) [right ans = (0.00,0.50,1.50)]\n") ; Q[0][0] = Q[1][1] = Q[2][2] = -1 ; A[0] = -1 ; A[1] = 0 ; A[2] = 2 ; eq[0][0] = eq[0][2] = 1 ; eq[0][1] = -1 ; eqrhs[0] = 1 ; ret = quadprog(Q,A,x,3,0, 0,0,0, 0,0,0, eq,eqrhs,1 ) ; printsoln(ret,x,3) ; } }

Archived from optim.html

#include "memory.h" #include <math.h> #define EPS 1e-10 #define debug 1 #define debugper 0 #define rescale 1 struct tableau { double **a,*scale ; int nrow,ncol,nper,*lhvar,*rhvar,nle,nge,neq,n,n0,n1 ; char *lhtype,*rhtype ; void release() { free(lhvar,rhvar,lhtype,rhtype,scale) ; freematrix(a) ; } void delrow(int row) { int i,j ; for(i=row;i<nrow;i++) { for(j=0;j<=ncol+nper;j++) a[i][j] = a[i+1][j] ; lhvar[i] = lhvar[i+1] ; lhtype[i] = lhtype[i+1] ; } nrow -= 1 ; } void delcol(int col) { int i,j ; for(j=col;j<ncol+nper;j++) for(i=0;i<=nrow;i++) a[i][j] = a[i][j+1] ; for(j=col;j<ncol-1;j++) { rhvar[j] = rhvar[j+1] ; rhtype[j] = rhtype[j+1] ; } ncol -= 1 ; } /* ------------------------------------------------------------------------ */
/*page*/ /* ------------------------------------------------------------------------ */ // tableau format: // the total number of columns in the tableau is ncol+1+nper (the extra '1' // is for the constant terms and the extra nper for the perturbations - nper is // the inititial value of nrow, though nrow may get smaller). The columns // include artificial variables and KKT multipliers. // Variables are coded as follows: // x[j] <-> true variables // y[i] <-> slack variables for inequalities, J&B's v // w[j] <-> artificial variables for constraints, NR's z // v[j] <-> KKT multipliers, complementary with x, J&B's y // u[i] <-> " " , " " y, J&B's mu // z[j] <-> artificial variables for stationarity condition // '.' <-> dead variable (could be removed from the tableau) // upper case 'X' are unrestricted; upper case 'U' come from equations rather // than inequalities and are therefore also unrestricted; upper case 'W' come // from equations rather than inequalities and can be eliminated as soon as // they get to the RHS. X and U never move from the LHS. void print() { int i,j ; printf("\n") ; for(i=0;i<=nrow;i++) { if(i==0) { printf("====|") ; for(j=0;j<ncol;j++) printf("======") ; printf("|=======") ; if(debugper) { printf("=|") ; for(j=0;j<nper;j++) printf("======") ; } printf("\n |") ; for(j=0;j<ncol;j++) printf(" %c%d ",rhtype[j],rhvar[j]) ; printf("|") ; if(debugper) printf(" | perturbations") ; printf("\n----|") ; for(j=0;j<ncol;j++) printf("------") ; printf("|-------") ; if(debugper) { printf("-|") ; for(j=0;j<nper;j++) printf("------") ; } printf("\n") ; } if(i<nrow) printf(" %c%d |",lhtype[i],lhvar[i]) ; else printf("obj |") ; for(j=0;j<ncol;j++) printf("%5.2f ",a[i][j]) ; printf("|%7.2f",a[i][ncol]) ; if(debugper) { printf(" |") ; for(j=ncol+1;j<=ncol+nper;j++) printf("%5.2f ",a[i][j]) ; } printf("\n") ; if(i==nrow-1) { printf("----|") ; for(j=0;j<ncol;j++) printf("------") ; printf("|-------") ; if(debugper) { printf("-|") ; for(j=0;j<nper;j++) printf("------") ; } printf("\n") ; } } } /* ------------------------------------------------------------------------ */
/*page*/ /* ------------------------------------------------------------------------ */ tableau(double **Q,double *A,int na,int nb,double **le,double *lerhs,int m1, double **ge,double *gerhs,int m2,double **eq,double *eqrhs,int m3) { int i,j,ind,m=m1+m2+m3 ; double q,sum ; n0 = na ; n1 = nb ; n = n0 + n1 ; nper = nrow = m + n ; ncol = m + 2*n ; nle = nge = neq = 0 ; a = matrix(nrow+1,ncol+ncol+1) ; scale = vector(n) ; // first m rows of the tableau state primal feasibility for(ind=i=0;i<m1;i++) if(lerhs[i]>=0) { for(j=0;j<n;j++) a[ind][j] = -le[i][j] ; a[ind++][ncol] = lerhs[i] ; } for(i=0;i<m2;i++) if(gerhs[i]<0) { for(j=0;j<n;j++) a[ind][j] = ge[i][j] ; a[ind++][ncol] = -gerhs[i] ; } nle = ind ; nge = m1 + m2 - nle ; neq = m3 ; for(i=0;i<m1;i++) if(lerhs[i]<0) { for(j=0;j<n;j++) a[ind][j] = le[i][j] ; a[ind++][ncol] = -lerhs[i] ; } for(i=0;i<m2;i++) if(gerhs[i]>=0) { for(j=0;j<n;j++) a[ind][j] = -ge[i][j] ; a[ind++][ncol] = gerhs[i] ; } for(i=0;i<m3;ind++,i++) if(eqrhs[i]>=0) { for(j=0;j<n;j++) a[ind][j] = -eq[i][j] ; a[ind][ncol] = eqrhs[i] ; } else { for(j=0;j<n;j++) a[ind][j] = eq[i][j] ; a[ind][ncol] = -eqrhs[i] ; } for(i=0;i<n;i++) scale[i] = 1 ; if(rescale>debug) { // scale the quadratic so that terms are of order 1 for(sum=i=0;i<n;i++) for(j=0;j<n;j++) sum += fabs(Q[i][j]) ; for(i=0;i<n;i++) sum += n * A[i] * A[i] ; sum = 1 / sqrt(sum/(2*n)) ; for(i=0;i<n;i++) scale[i] = sum ; // ... and scale the equations to be of order 1/scale[*] for(i=0;i<m;i++) { for(sum=a[i][ncol]*a[i][ncol],j=0;j<n;j++) sum += a[i][j] * a[i][j] ; q = 1 / ( scale[0] * sqrt(sum/(n+1)) ) ; for(a[i][ncol]*=q,j=0;j<n;j++) a[i][j] *= q ; } // now introduce a separate scale factor for each variable for(j=0;j<n;j++) { for(i=0;i<n;i++) sum += scale[i] * scale[j] * fabs(Q[i][j]) ; sum = m * ( sum + n*scale[j]*scale[j]*A[j]*A[j] ) ; for(i=0;i<m;i++) sum += n * scale[j] * scale[j] * a[i][j] * a[i][j] ; scale[j] /= sqrt(sum/(3*m*n)) ; } } for(i=0;i<m;i++) for(j=0;j<n;j++) a[i][j] *= scale[j] ; /* ---------------------------------------------------------------------- */
/*page*/ /* ---------------------------------------------------------------------- */ // remaining n rows state the KKT stationarity condition for(i=0;i<n;i++) { for(j=0;j<n;j++) a[m+i][j] = Q[i][j] * scale[i] * scale[j] ; a[m+i][n+i] = 1 ; for(j=0;j<nle;j++) a[m+i][2*n+j] = a[j][i] ; for(;j<nle+nge;j++) a[m+i][2*n+j] = -a[j][i] ; // correcting Wolfe for(;j<m;j++) a[m+i][2*n+j] = a[j][i] ; a[m+i][ncol] = A[i] * scale[i] ; if(A[i]<0) for(j=0;j<=ncol;j++) a[m+i][j] = -a[m+i][j] ; } // perturbation coefficients go to the right of the constant term for(i=0;i<nrow;i++) a[nrow][ncol+1+i] = a[i][ncol+1+i] = 1 ; // variable ids lhvar = ivector(nrow) ; rhvar = ivector(ncol) ; lhtype = charvector(nrow+1) ; rhtype = charvector(ncol+1) ; for(i=0;i<n0;i++) { rhvar[i] = i ; rhtype[i] = 'x' ; } for(i=n0;i<n;i++) { rhvar[i] = i ; rhtype[i] = 'X' ; } for(i=0;i<n;i++) { rhvar[n+i] = i ; rhtype[n+i] = 'v' ; } for(i=0;i<nle+nge;i++) { rhvar[2*n+i] = i ; rhtype[2*n+i] = 'u' ; } for(i=nle+nge;i<m;i++) { rhvar[2*n+i] = i ; rhtype[2*n+i] = 'U' ; } for(i=0;i<nle;i++) { lhvar[i] = i ; lhtype[i] = 'y' ; } // y for <= for(i=nle;i<nle+nge;i++) { lhvar[i] = i ; lhtype[i] = 'w' ; } // w for >= for(i=nle+nge;i<m;i++) { lhvar[i] = i ; lhtype[i] = 'W' ; } // w for == for(i=0;i<n;i++) { lhvar[m+i] = i ; lhtype[m+i] = 'z' ; } // objective function for(i=0;i<nrow;i++) if(lhtype[i]=='w'||lhtype[i]=='W'||lhtype[i]=='z') for(j=0;j<=ncol;j++) a[nrow][j] -= a[i][j] ; } /* ------------------------------------------------------------------------ */ void pivot(int pivrow,int pivcol) { int i,j ; double piv=1/a[pivrow][pivcol],q ; for(i=0;i<nrow+1;i++) if(i!=pivrow) { for(q=a[i][pivcol]*piv,j=0;j<=ncol+nrow;j++) a[i][j] -= a[pivrow][j] * q ; a[i][pivcol] = q ; } for(j=0;j<=ncol+nper;j++) a[pivrow][j] *= -piv ; a[pivrow][pivcol] = piv ; swap(rhvar[pivcol],lhvar[pivrow]) ; swap(rhtype[pivcol],lhtype[pivrow]) ; } /* ------------------------------------------------------------------------ */
/*page*/ /* ------------------------------------------------------------------------ */ // return -2 if no pivot found, -1 if obj fn can be increased without limit int findpivot() { int i,k,pivrow,pivcol,ret,ind,rtype,ltype,iforce,pivgood,igood,take ; double rmax,r,rbest,qobj,qcell,delscore ; for(ret=-2,pivcol=0;pivcol<ncol;pivcol++) { // complementarity condition: if iforce>=0 it is the only legal i value rtype = rhtype[pivcol] ; if(rtype=='u') ltype = 'y' ; else if(rtype=='y') ltype = 'u' ; else if(rtype=='v') ltype = 'x' ; else if(rtype=='x') ltype = 'v' ; else ltype = '$' ; // dummy for(iforce=-1,i=0;i<nrow&&iforce<0;i++) if(lhtype[i]==ltype&&lhvar[i]==rhvar[pivcol]) iforce = i ; qobj = a[nrow][pivcol] ; if(qobj>EPS) { for(pivrow=-1,i=0;i<nrow;i++) if((qcell=a[i][pivcol])<0) if(lhtype[i]!='U'&&lhtype[i]!='X') { r = -a[i][ncol] * qobj / qcell ; take = 0 ; igood = (iforce<0||i==iforce) ; if(pivrow<0||r<rbest-EPS) { take = 1 ; pivgood = igood ; } else if(igood&&r<rbest+EPS) // tie for objective function { if( rtype=='U' || rtype=='X' || lhtype[i]=='z' || lhtype[i]=='w' || lhtype[i]=='W' ) take = pivgood = 1 ; // redundant safeguard else for(k=ncol+1;k<=ncol+nper;k++) { delscore = -a[i][k] * qobj / qcell ; if(delscore>EPS) { take = pivgood = 1 ; break ; } else if(delscore<-EPS) break ; } } if(take) { pivrow = i ; rbest = r ; } } if(pivrow<0) return -1 ; if(pivgood&&(ret<0||rbest>rmax)) { rmax = rbest ; ret = pivrow + nrow*pivcol ; } } else if(rtype=='U'||rtype=='X') { for(pivrow=-1,i=0;i<nrow;i++) if(a[i][pivcol]>0) if(lhtype[i]!='U'&&lhtype[i]!='X') { r = - a[i][ncol] * qobj / a[i][pivcol] ; if(pivrow<0||r<rbest) { pivrow = i ; rbest = r ; } } if(qobj<-EPS&&pivrow<0) return -1 ; if(ret<0||rbest>rmax) { rmax = rbest ; ret = pivrow + nrow*pivcol ; } } } // end loop over pivcol return ret ; } /* ------------------------------------------------------------------------ */
/*page*/ /* ------------------------------------------------------------------------ */ int simplexalg(double *x) { int i,row,col,ret,iter ; double qmax,qmaxiter ; for(i=0;i<n;i++) x[i] = 0 ; if(debug) print() ; for(qmaxiter=1,i=0;i<nrow;i++) qmaxiter *= (nrow+ncol-i) / (1.0+i) ; for(iter=0;iter<qmaxiter&&(ret=findpivot())>=0;iter++) { row = ret % nrow ; col = ret / nrow ; pivot(row,col) ; // unrestricted LH Lagrangians U and artificial RH vbles can be zapped if(lhtype[row]=='U') delrow(row) ; if(rhtype[col]=='W'||rhtype[col]=='z') delcol(col) ; // the artificial variable for a '>=' needs to become a slack variable else if(rhtype[col]=='w') { rhtype[col] = 'y' ; a[nrow][col] = -(1+a[nrow][col]) ; for(i=0;i<nrow;i++) a[i][col] = -a[i][col] ; } for(row=0;row<nrow;row++) if(lhtype[row]!= 'X') if(a[row][ncol]<0) a[row][ncol] = 0 ; if(debug) print() ; } if(iter>=qmaxiter) ret = -3 ; if(ret==-2&&a[nrow][ncol]>-EPS) ret = 0 ; for(i=0;i<nrow;i++) if(lhtype[i]=='x'||lhtype[i]=='X') x[lhvar[i]] = a[i][ncol] * scale[lhvar[i]] ; return ret ; } } ; /* -------------------------------------------------------------------------- */ int quadprog(double **Q,double *A,double *x,int n0,int n1, double **le,double *lerhs,int nle, double **ge,double *gerhs,int nge, double **eq,double *eqrhs,int neq) { tableau t(Q,A,n0,n1,le,lerhs,nle,ge,gerhs,nge,eq,eqrhs,neq) ; int ret = t.simplexalg(x) ; t.release() ; return ret ; } int quadprog(double **Q,double *A,double *x,int n) { return quadprog(Q,A,x,n,0, 0,0,0, 0,0,0, 0,0,0) ; }

Archived from optim.html

#include "memory.h" #include <math.h> #define EPS 1e-10 #define debug 0 #define debugp 0 #define rescale 1 struct tableau { double **a,*scale ; int nrow,ncol,nper,*lhvar,*rhvar,nle,nge,neq,n,n0,n1 ; char *lhtype,*rhtype ; void release() { free(lhvar,rhvar,lhtype,rhtype,scale) ; freematrix(a) ; } void delrow(int row) { int i,j ; for(i=row;i<nrow;i++) { for(j=0;j<=ncol+nper;j++) a[i][j] = a[i+1][j] ; lhvar[i] = lhvar[i+1] ; lhtype[i] = lhtype[i+1] ; } nrow -= 1 ; } void delcol(int col) { int i,j ; for(j=col;j<ncol+nper;j++) for(i=0;i<=nrow;i++) a[i][j] = a[i][j+1] ; for(j=col;j<ncol-1;j++) { rhvar[j] = rhvar[j+1] ; rhtype[j] = rhtype[j+1] ; } ncol -= 1 ; } /* ------------------------------------------------------------------------ */
/*page*/ /* ------------------------------------------------------------------------ */ // tableau format: // the total number of columns in the tableau is ncol+1+nper (the extra '1' // is for the constant terms and the extra nper for the perturbations - nper is // the inititial value of nrow, though nrow may get smaller). The columns // include artificial variables and KKT multipliers. // Variables are coded as follows: // x[j] <-> true variables // y[i] <-> slack variables for inequalities, J&B's v // w[j] <-> artificial variables for constraints, NR's z // v[j] <-> KKT multipliers, complementary with x, J&B's y // u[i] <-> " " , " " y, J&B's mu // z[j] <-> artificial variables for stationarity condition // '.' <-> dead variable (could be removed from the tableau) // upper case 'X' are unrestricted; upper case 'U' come from equations rather // than inequalities and are therefore also unrestricted; upper case 'W' come // from equations rather than inequalities and can be eliminated as soon as // they get to the RHS. X and U never move from the LHS. void print() { int i,j ; printf("\n") ; for(i=0;i<=nrow;i++) { if(i==0) { printf("====|") ; for(j=0;j<ncol;j++) printf("======") ; printf("|=======") ; if(debugp) { printf("|") ; for(j=0;j<nper;j++) printf("======") ; } printf("\n |") ; for(j=0;j<ncol;j++) printf(" %c%d ",rhtype[j],rhvar[j]) ; printf("|") ; if(debugp) printf(" | perturbations") ; printf("\n----|") ; for(j=0;j<ncol;j++) printf("------") ; printf("|-------") ; if(debugp) { printf("|") ; for(j=0;j<nper;j++) printf("------") ; } printf("\n") ; } if(i<nrow) printf(" %c%d |",lhtype[i],lhvar[i]) ; else printf("obj |") ; for(j=0;j<ncol;j++) printf("%5.2f ",a[i][j]) ; printf("|%7.2f",a[i][ncol]) ; if(debugp) { printf("|") ; for(j=ncol+1;j<=ncol+nper;j++) printf("%5.2f ",a[i][j]) ; } printf("\n") ; if(i==nrow-1) { printf("----|") ; for(j=0;j<ncol;j++) printf("------") ; printf("|-------") ; if(debugp) { printf("|") ; for(j=0;j<nper;j++) printf("------") ; } printf("\n") ; } } } /* ------------------------------------------------------------------------ */
/*page*/ /* ------------------------------------------------------------------------ */ tableau(double **Q,double *A,int na,int nb,double **le,double *lerhs,int m1, double **ge,double *gerhs,int m2,double **eq,double *eqrhs,int m3) { int i,j,ind,m=m1+m2+m3 ; double q,sum ; n0 = na ; n1 = nb ; n = n0 + n1 ; nper = nrow = m + n ; ncol = m + 2*n ; nle = nge = neq = 0 ; a = matrix(nrow+1,ncol+ncol+1) ; scale = vector(n) ; // first m rows of the tableau state primal feasibility for(ind=i=0;i<m1;i++) if(lerhs[i]>=0) { for(j=0;j<n;j++) a[ind][j] = -le[i][j] ; a[ind++][ncol] = lerhs[i] ; } for(i=0;i<m2;i++) if(gerhs[i]<0) { for(j=0;j<n;j++) a[ind][j] = ge[i][j] ; a[ind++][ncol] = -gerhs[i] ; } nle = ind ; nge = m1 + m2 - nle ; neq = m3 ; for(i=0;i<m1;i++) if(lerhs[i]<0) { for(j=0;j<n;j++) a[ind][j] = le[i][j] ; a[ind++][ncol] = -lerhs[i] ; } for(i=0;i<m2;i++) if(gerhs[i]>=0) { for(j=0;j<n;j++) a[ind][j] = -ge[i][j] ; a[ind++][ncol] = gerhs[i] ; } for(i=0;i<m3;ind++,i++) if(eqrhs[i]>=0) { for(j=0;j<n;j++) a[ind][j] = -eq[i][j] ; a[ind][ncol] = eqrhs[i] ; } else { for(j=0;j<n;j++) a[ind][j] = eq[i][j] ; a[ind][ncol] = -eqrhs[i] ; } for(i=0;i<n;i++) scale[i] = 1 ; if(rescale>debug) { // scale the quadratic so that terms are of order 1 for(sum=i=0;i<n;i++) for(j=0;j<n;j++) sum += fabs(Q[i][j]) ; for(i=0;i<n;i++) sum += n * A[i] * A[i] ; sum = 1 / sqrt(sum/(2*n)) ; for(i=0;i<n;i++) scale[i] = sum ; // ... and scale the equations to be of order 1/scale[*] for(i=0;i<m;i++) { for(sum=a[i][ncol]*a[i][ncol],j=0;j<n;j++) sum += a[i][j] * a[i][j] ; q = 1 / ( scale[0] * sqrt(sum/(n+1)) ) ; for(a[i][ncol]*=q,j=0;j<n;j++) a[i][j] *= q ; } // now introduce a separate scale factor for each variable for(j=0;j<n;j++) { for(i=0;i<n;i++) sum += scale[i] * scale[j] * fabs(Q[i][j]) ; sum = m * ( sum + n*scale[j]*scale[j]*A[j]*A[j] ) ; for(i=0;i<m;i++) sum += n * scale[j] * scale[j] * a[i][j] * a[i][j] ; scale[j] /= sqrt(sum/(3*m*n)) ; } } for(i=0;i<m;i++) for(j=0;j<n;j++) a[i][j] *= scale[j] ; /* ---------------------------------------------------------------------- */
/*page*/ /* ---------------------------------------------------------------------- */ // remaining n rows state the KKT stationarity condition for(i=0;i<n;i++) { for(j=0;j<n;j++) a[m+i][j] = Q[i][j] * scale[i] * scale[j] ; a[m+i][n+i] = 1 ; for(j=0;j<nle;j++) a[m+i][2*n+j] = a[j][i] ; for(;j<nle+nge;j++) a[m+i][2*n+j] = -a[j][i] ; // correcting Wolfe for(;j<m;j++) a[m+i][2*n+j] = a[j][i] ; a[m+i][ncol] = A[i] * scale[i] ; if(A[i]<0) for(j=0;j<=ncol;j++) a[m+i][j] = -a[m+i][j] ; } // perturbation coefficients go to the right of the constant term for(i=0;i<nrow;i++) a[nrow][ncol+1+i] = a[i][ncol+1+i] = 1 ; // variable ids lhvar = ivector(nrow) ; rhvar = ivector(ncol) ; lhtype = charvector(nrow+1) ; rhtype = charvector(ncol+1) ; for(i=0;i<n0;i++) { rhvar[i] = i ; rhtype[i] = 'x' ; } for(i=n0;i<n;i++) { rhvar[i] = i ; rhtype[i] = 'X' ; } for(i=0;i<n;i++) { rhvar[n+i] = i ; rhtype[n+i] = 'v' ; } for(i=0;i<nle+nge;i++) { rhvar[2*n+i] = i ; rhtype[2*n+i] = 'u' ; } for(i=nle+nge;i<m;i++) { rhvar[2*n+i] = i ; rhtype[2*n+i] = 'U' ; } for(i=0;i<nle;i++) { lhvar[i] = i ; lhtype[i] = 'y' ; } // y for <= for(i=nle;i<nle+nge;i++) { lhvar[i] = i ; lhtype[i] = 'w' ; } // w for >= for(i=nle+nge;i<m;i++) { lhvar[i] = i ; lhtype[i] = 'W' ; } // w for == for(i=0;i<n;i++) { lhvar[m+i] = i ; lhtype[m+i] = 'z' ; } // objective function for(i=0;i<nrow;i++) if(lhtype[i]=='w'||lhtype[i]=='W'||lhtype[i]=='z') for(j=0;j<=ncol;j++) a[nrow][j] -= a[i][j] ; } /* ------------------------------------------------------------------------ */ void pivot(int pivrow,int pivcol) { int i,j ; double piv=1/a[pivrow][pivcol],q ; for(i=0;i<nrow+1;i++) if(i!=pivrow) { for(q=a[i][pivcol]*piv,j=0;j<=ncol+nrow;j++) a[i][j] -= a[pivrow][j] * q ; a[i][pivcol] = q ; } for(j=0;j<=ncol+nper;j++) a[pivrow][j] *= -piv ; a[pivrow][pivcol] = piv ; swap(rhvar[pivcol],lhvar[pivrow]) ; swap(rhtype[pivcol],lhtype[pivrow]) ; } /* ------------------------------------------------------------------------ */
/*page*/ /* ------------------------------------------------------------------------ */ // return -2 if no pivot found, -1 if obj fn can be increased without limit int findpivot() { int i,k,pivrow,pivcol,ret,ind,rtype,ltype,iforce,igood,take ; double rmax,r,rbest,qobj,delscore ; for(ret=-2,pivcol=0;pivcol<ncol;pivcol++) { // complementarity condition: if iforce>=0 it is the only legal i value rtype = rhtype[pivcol] ; if(rtype=='u') ltype = 'y' ; else if(rtype=='y') ltype = 'u' ; else if(rtype=='v') ltype = 'x' ; else if(rtype=='x') ltype = 'v' ; else ltype = '$' ; // dummy for(iforce=-1,i=0;i<nrow&&iforce<0;i++) if(lhtype[i]==ltype&&lhvar[i]==rhvar[pivcol]) iforce = i ; qobj = a[nrow][pivcol] ; if(qobj>EPS) { for(pivrow=-1,i=0;i<nrow;i++) if(a[i][pivcol]<0) if(lhtype[i]!='U'&&lhtype[i]!='X') { r = - a[i][ncol] * qobj / a[i][pivcol] ; take = 0 ; if(pivrow<0||r<rbest-EPS) take = 1 ; else if(r<rbest+EPS&&(iforce<0||i==iforce)) // tie for obj fn { if(igood==0) take = 1 ; else for(k=ncol+1;k<=ncol+nper;k++) { delscore = -a[i][k] * qobj / a[i][pivcol] ; if(fabs(delscore)>EPS) { take = (delscore>0) ; break ; } } } if(take) { pivrow = i ; rbest = r ; igood = (iforce<0||i==iforce) ; } } if(pivrow<0) return -1 ; if(igood&&(ret<0||rbest>rmax)) { rmax = rbest ; ret = pivrow + nrow*pivcol ; } } else if(rtype=='U'||rtype=='X') { for(pivrow=-1,i=0;i<nrow;i++) if(a[i][pivcol]>0) if(lhtype[i]!='U'&&lhtype[i]!='X') { r = - a[i][ncol] * qobj / a[i][pivcol] ; if(pivrow<0||r<rbest) { pivrow = i ; rbest = r ; } } if(qobj<-EPS&&pivrow<0) return -1 ; if(ret<0||rbest>rmax) { rmax = rbest ; ret = pivrow + nrow*pivcol ; } } } // end loop over pivcol return ret ; } /* ------------------------------------------------------------------------ */
/*page*/ /* ------------------------------------------------------------------------ */ int simplexalg(double *x) { int i,row,col,ret,iter ; double qmax,qmaxiter ; for(i=0;i<n;i++) x[i] = 0 ; if(debug) print() ; for(qmaxiter=1,i=0;i<nrow;i++) qmaxiter *= (nrow+ncol-i) / (1.0+i) ; for(iter=0;iter<qmaxiter&&(ret=findpivot())>=0;iter++) { row = ret % nrow ; col = ret / nrow ; pivot(row,col) ; // unrestricted LH Lagrangians U and artificial RH vbles can be zapped if(lhtype[row]=='U') delrow(row) ; if(rhtype[col]=='W'||rhtype[col]=='z') delcol(col) ; // the artificial variable for a '>=' needs to become a slack variable else if(rhtype[col]=='w') { rhtype[col] = 'y' ; a[nrow][col] = -(1+a[nrow][col]) ; for(i=0;i<nrow;i++) a[i][col] = -a[i][col] ; } for(row=0;row<nrow;row++) if(lhtype[row]!= 'X') if(a[row][ncol]<0) a[row][ncol] = 0 ; if(debug) print() ; } if(iter>=qmaxiter) ret = -3 ; if(ret==-2&&a[nrow][ncol]>-EPS) ret = 0 ; for(i=0;i<nrow;i++) if(lhtype[i]=='x'||lhtype[i]=='X') x[lhvar[i]] = a[i][ncol] * scale[lhvar[i]] ; return ret ; } } ; /* -------------------------------------------------------------------------- */ int quadprog(double **Q,double *A,double *x,int n0,int n1, double **le,double *lerhs,int nle, double **ge,double *gerhs,int nge, double **eq,double *eqrhs,int neq) { tableau t(Q,A,n0,n1,le,lerhs,nle,ge,gerhs,nge,eq,eqrhs,neq) ; int ret = t.simplexalg(x) ; t.release() ; return ret ; }

Archived from optim.html

/* This is NEWUOA for unconstrained minimization. The codes were written by Powell in Fortran and then translated to C with f2c. I further modified the code to make it independent of libf2c and f2c.h. Please refer to "The NEWUOA software for unconstrained optimization without derivatives", which is available at www.damtp.cam.ac.uk, for more information. */ /* The original fortran codes are distributed without restrictions. The C++ codes are distributed under MIT license. */ /* The MIT License Copyright (c) 2004, by M.J.D. Powell <mjdp@cam.ac.uk> 2008, by Attractive Chaos <attractivechaos@aol.co.uk> 2015, by Colin Champion <colin.champion@masterlyinactivity.com> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "memory.h" #include <math.h> #define pi 3.141592653589793 static int cond = -1 ; int uoacond() { return cond ; } int update(int,int,double **,double **,int,double *,double,int) ; void shiftxbase(int n,int npt,double **xp,double *xopt,double *vlag,double **b, double **z,double *pq,double *hq,int idz) ; double calcvlagbeta(int n,int npt,double *vlag,double **z,double **b,double *w, double *xopt,double *d,double beta,double dsq,double xsq, int idz) ; int chooseknew(int n,int npt,double beta,double **z,double *vlag,double **xp, double *xopt,double detrat,double rhosq,int ktemp,int idz) ; double genpqw(int n,int npt,double *pqw,double **z,int knew,int idz) ; void updatehq(int n,int npt,double *hq,double pqknew,double **xpt,int knew) ; void updategopt(int n,double *gopt,double *hq,double *step) ; void updategopt2(int n,int npt,double *gopt,double *xopt,double *pq,double **) ; /* -------------------------------------------------------------------------- */ static double biglag(int n,int npt,double *xopt,double **xp,double **b, double **z,int idz,int knew,double delta,double *d) { /* N is the number of variables. NPT is the number of interpolation * equations. XOPT is the best interpolation point so far. XPT * contains the coordinates of the current interpolation * points. BMAT provides the last N columns of H. ZMAT and IDZ give * a factorization of the first NPT by NPT submatrix of H. NDIM is * the first dimension of BMAT and has the value NPT+N. KNEW is the * index of the interpolation point that is going to be moved. DELTA * is the current trust region bound. D will be set to the step from * XOPT to the new point. ALPHA will be set to the KNEW-th diagonal * element of the H matrix. HCOL, GC, GD, S and W will be used for * working space. */ /* The step D is calculated in a way that attempts to maximize the * modulus of LFUNC(XOPT+D), subject to the bound ||D|| <= DELTA, * where LFUNC is the KNEW-th Lagrange function. */ int i,j,k,iterc,isave ; double sp,ss,cf1,cf2,cf3,cf4,cf5,dhd,cth,tau,sth,sum,temp,step,angle,scale ; double denom,delsq,tempa,tempb,taubeg,tauold,taumax,dd,gg,alpha ; double *hcol=vector(npt),*w=vector(n),*s=vector(n),*gd=vector(n) ; double *gc=vector(n) ; delsq = delta * delta ; /* Set the first NPT components of HCOL to the leading elements of * the KNEW-th column of H. */ alpha = genpqw(n,npt,hcol,z,knew,idz) ; /* Set the unscaled initial direction D. Form the gradient of LFUNC * atXOPT, and multiply D by the second derivative matrix of LFUNC. */ for(dd=i=0;i<n;i++) { d[i] = xp[i][knew] - xopt[i] ; gc[i] = b[i][knew] ; dd += d[i]*d[i] ; } for(k=0;k<npt;k++) { for(temp=sum=j=0;j<n;j++) { temp += xp[j][k] * xopt[j] ; sum += xp[j][k] * d[j] ; } temp *= hcol[k] ; sum *= hcol[k] ; for(i=0;i<n;i++) { gc[i] += temp * xp[i][k] ; gd[i] += sum * xp[i][k] ; } } /* Scale D and GD, with a sign change if required. Set S to another * vector in the initial two dimensional subspace. */ for(gg=sp=dhd=i=0;i<n;i++) { gg += gc[i] * gc[i] ; sp += d[i] * gc[i] ; dhd += d[i] * gd[i] ; } scale = delta / sqrt(dd) ; if(sp*dhd<0) scale = -scale ; if(sp*sp>dd*.99*gg) temp = 1 ; else temp = 0 ; tau = scale * (fabs(sp)+0.5*scale*fabs(dhd)) ; if(gg*delsq<tau*.01*tau) temp = 1 ; for(i=0;i<n;i++) { d[i] *= scale ; gd[i] *= scale ; s[i] = gc[i] + temp*gd[i] ; } /* Begin the iteration by overwriting S with a vector that has the * required length and direction, except that termination occurs if * the given D and S are nearly parallel. */ for(iterc=0;iterc<n;iterc++) { for(dd=sp=ss=i=0;i<n;i++) { dd += d[i] * d[i] ; sp += d[i] * s[i] ; ss += s[i] * s[i] ; } temp = dd*ss - sp*sp ; if(temp<=dd*1e-8*ss) break ; denom = sqrt(temp); for(i=0;i<n;i++) { s[i] = (dd*s[i]-sp*d[i]) / denom ; w[i] = 0 ; } /* Calculate the coefficients of the objective function on the * circle, beginning with the multiplication of S by the second * derivative matrix. */ for(k=0;k<npt;k++) { for(sum=j=0;j<n;j++) sum += xp[j][k] * s[j] ; sum *= hcol[k] ; for(j=0;j<n;j++) w[j] += sum * xp[j][k] ; } for(cf1=cf2=cf3=cf4=cf5=i=0;i<n;i++) { cf1 += s[i] * w[i]; cf2 += d[i] * gc[i]; cf3 += s[i] * gc[i]; cf4 += d[i] * gd[i]; cf5 += s[i] * gd[i]; } cf1 /= 2 ; cf4 = cf4/2 - cf1 ; /* Seek the value of the angle that maximizes the modulus of TAU. */ taubeg = cf1 + cf2 + cf4 ; taumax = tauold = taubeg; temp = 2 * pi / 50.0 ; for(isave=0,i=1;i<50;tauold=tau,i++) { angle = (double) i*temp; cth = cos(angle) ; sth = sin(angle) ; tau = cf1 + (cf2 + cf4*cth) * cth + (cf3 + cf5*cth) * sth ; if(fabs(tau)>fabs(taumax)) { taumax = tau ; isave = i ; tempa = tauold ; } else if(i==isave+1) tempb = tau ; } if(isave==0) tempa = tau ; else if(isave==49) tempb = taubeg ; step = 0 ; if(tempa!=tempb) { tempa -= taumax ; tempb -= taumax ; step = 0.5 * (tempa-tempb) / (tempa+tempb) ; } angle = temp * (isave+step) ; /* Calculate the new D and GD. Then test for convergence. */ cth = cos(angle) ; sth = sin(angle) ; tau = cf1 + (cf2 + cf4*cth) * cth + (cf3 + cf5*cth) * sth ; for(i=0;i<n;i++) { d[i] = cth*d[i] + sth*s[i] ; gd[i] = cth*gd[i] + sth*w[i] ; s[i] = gc[i] + gd[i] ; } if(fabs(tau)<=fabs(taubeg)*1.1) break ; } free(hcol,w,s,gd,gc) ; return alpha ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------------------------------------------------- */ static double bigden(int n,int npt,double *xopt,double **xp,double **b, double **z,int idz,int kopt,int knew, double *d,double *w, double *vlag) { /* N is the number of variables. * NPT is the number of interpolation equations. * XOPT is the best interpolation point so far. * XPT contains the coordinates of the current interpolation points. * BMAT provides the last N columns of H. * ZMAT and IDZ give a factorization of the first NPT by NPT submatrix of H. * NDIMis the first dimension of BMAT and has the value NPT+N. * KOPT is the index of the optimal interpolation point. * KNEW is the index of the interpolation point that is going to be moved. * D will be set to the step from XOPT to the new point, and on entry it * should be the D that was calculated by the last call of BIGLAG. The length of the initial D provides a trust region bound on the final D. * W will be set to Wcheck for the final choice of D. * VLAG will be set to Theta*Wcheck+e_b for the final choice of D. * D is calculated in a way that should provide a denominator with a large modulus in the updating formula when the KNEW-th interpolation point is shifted to the new position XOPT+D. * the RETURN value beta will be found in the updating formula when the KNEW-th interpolation point is moved to its new position. */ int i,j,k,isave,iterc,jc,nw,ksav,ndim=npt+n ; double dd,ds,ss,den[9],par[9],tau,sum,diff,temp,step,beta,alpha,angle ; double denex[9],tempa,tempb,tempc,ssden,dtest,xoptd,xopts,denold,denmax ; double densav,dstemp,sumold,sstemp,xoptsq ; double **prod=matrix(ndim,5),**wvec=matrix(ndim,5),*s=vector(n) ; /* Store the first NPT elements of the KNEW-th column of H in W(N+1) * to W(N+NPT). */ alpha = genpqw(n,npt,w+n,z,knew,idz) ; /* The initial search direction D is taken from the last call of * BIGBDLAG, and the initial S is set below, usually to the direction * from X_OPT to X_KNEW, but a different direction to an * interpolation point may be chosen, in order to prevent S from * being nearly parallel to D. */ for(dd=ds=ss=i=0;i<n;i++) { dd += d[i]*d[i] ; s[i] = xp[i][knew] - xopt[i] ; ds += d[i] * s[i] ; ss += s[i] * s[i] ; } if(ds*ds>dd*.99*ss) { ksav = knew ; dtest = ds * ds / ss ; for(k=0;k<npt;k++) if(k!=kopt) { for(dstemp=sstemp=i=0;i<n;i++) { diff = xp[i][k] - xopt[i] ; dstemp += d[i] * diff ; sstemp += diff*diff ; } if(dstemp*dstemp/sstemp<dtest) { ksav = k ; dtest = dstemp * dstemp / sstemp ; ds = dstemp ; ss = sstemp ; } } for(i=0;i<n;i++) s[i] = xp[i][ksav] - xopt[i] ; } ssden = dd*ss - ds*ds ; densav = 0 ; /* Begin the iteration by overwriting S with a vector that has the * required length and direction. */ /* ------------------------------------------------------------------------ */ for(iterc=1;;iterc++) { temp = 1.0 / sqrt(ssden) ; for(xoptsq=xoptd=xopts=i=0;i<n;i++) { s[i] = temp * (dd*s[i]-ds*d[i]) ; xoptd += xopt[i] * d[i] ; xopts += xopt[i] * s[i] ; xoptsq += xopt[i] * xopt[i] ; } /* Set the coefficients of the first 2 terms of BETA. */ tempa = 0.5 * xoptd * xoptd ; tempb = 0.5 * xopts * xopts ; den[0] = dd * (xoptsq + 0.5*dd) + tempa + tempb ; den[1] = 2 * xoptd * dd ; den[2] = 2 * xopts * dd ; den[3] = tempa - tempb ; den[4] = xoptd * xopts ; for(i=4;i<8;i++) den[i+1] = 0 ; /* Put the coefficients of Wcheck in WVEC. */ for(k=0;k<npt;k++) { for(tempa=tempb=tempc=i=0;i<n;i++) { tempa += xp[i][k] * d[i]; tempb += xp[i][k] * s[i]; tempc += xp[i][k] * xopt[i]; } wvec[k][0] = 0.25 * (tempa*tempa + tempb*tempb) ; wvec[k][1] = tempa * tempc ; wvec[k][2] = tempb * tempc ; wvec[k][3] = 0.25 * (tempa*tempa - tempb*tempb) ; wvec[k][4] = 0.5 * tempa * tempb ; } for(i=0;i<n;i++) { wvec[i+npt][0] = 0 ; wvec[i+npt][1] = d[i] ; wvec[i+npt][2] = s[i] ; wvec[i+npt][3] = 0 ; wvec[i+npt][4] = 0 ; } /* Put the coefficents of THETA*Wcheck in PROD. */ for(jc=0;jc<5;jc++) { if(jc==2||jc==3) nw = ndim ; else nw = npt ; for(k=0;k<npt;k++) prod[k][jc] = 0 ; for(j=0;j<npt-n-1;j++) { for(sum=k=0;k<npt;k++) sum += z[j][k] * wvec[k][jc] ; if(j<idz) sum = -sum ; for(k=0;k<npt;k++) prod[k][jc] += sum * z[j][k] ; } if(nw==ndim) for(k=0;k<npt;k++) { for(sum=j=0;j<n;j++) sum += b[j][k] * wvec[npt+j][jc] ; prod[k][jc] += sum ; } for(j=0;j<n;j++) { for(sum=i=0;i<nw;i++) sum += b[j][i] * wvec[i][jc] ; prod[npt+j][jc] = sum ; } } /* Include in DEN the part of BETA that depends on THETA. */ for(k=0;k<ndim;k++) { for(sum=i=0;i<5;i++) sum += ( par[i] = 0.5 * prod[k][i] * wvec[k][i] ) ; den[0] -= par[0] + sum ; tempa = prod[k][0] * wvec[k][1] + prod[k][1] * wvec[k][0] ; tempb = prod[k][1] * wvec[k][3] + prod[k][3] * wvec[k][1] ; tempc = prod[k][2] * wvec[k][4] + prod[k][4] * wvec[k][2] ; den[1] -= tempa + 0.5 * (tempb+tempc) ; den[5] -= 0.5 * (tempb-tempc) ; tempa = prod[k][0] * wvec[k][2] + prod[k][2] * wvec[k][0] ; tempb = prod[k][1] * wvec[k][4] + prod[k][4] * wvec[k][1] ; tempc = prod[k][2] * wvec[k][3] + prod[k][3] * wvec[k][2] ; den[2] -= tempa + 0.5 * (tempb-tempc) ; den[6] -= 0.5 * (tempb+tempc) ; tempa = prod[k][0] * wvec[k][3] + prod[k][3] * wvec[k][0] ; den[3] -= tempa + par[1] - par[2] ; tempa = prod[k][0] * wvec[k][4] + prod[k][4] * wvec[k][0] ; tempb = prod[k][1] * wvec[k][2] + prod[k][2] * wvec[k][1] ; den[4] -= tempa + 0.5 * tempb ; den[7] -= par[3] - par[4] ; tempa = prod[k][3] * wvec[k][4] + prod[k][4] * wvec[k][3] ; den[8] -= 0.5 * tempa ; } /* Extend DEN so that it holds all the coefficients of DENOM. */ for(sum=i=0;i<5;i++) { tempa = prod[knew][i] ; sum += ( par[i] = 0.5 * tempa * tempa ) ; } denex[0] = alpha*den[0] + par[0] + sum ; tempa = 2 * prod[knew][0] * prod[knew][1] ; tempb = prod[knew][1] * prod[knew][3] ; tempc = prod[knew][2] * prod[knew][4] ; denex[1] = alpha*den[1] + tempa + tempb + tempc ; denex[5] = alpha*den[5] + tempb - tempc ; tempa = 2 * prod[knew][0] * prod[knew][2] ; tempb = prod[knew][1] * prod[knew][4] ; tempc = prod[knew][2] * prod[knew][3] ; denex[2] = alpha*den[2] + tempa + tempb - tempc ; denex[6] = alpha*den[6] + tempb + tempc ; tempa = 2 * prod[knew][0] * prod[knew][3] ; denex[3] = alpha*den[3] + tempa + par[1] - par[2] ; tempa = 2 * prod[knew][0] * prod[knew][4] ; denex[4] = alpha*den[4] + tempa + prod[knew][1]*prod[knew][2] ; denex[7] = alpha*den[7] + par[3] - par[4] ; denex[8] = alpha*den[8] + prod[knew][3]*prod[knew][4] ; /* Seek the value of the angle that maximizes the modulus of DENOM. */ sum = denex[0] + denex[1] + denex[3] + denex[5] + denex[7]; denold = denmax = sum ; temp = 2 * pi / 50 ; par[0] = 1 ; for(isave=0,i=1;i<50;i++) { angle = i * temp ; par[1] = cos(angle) ; par[2] = sin(angle) ; for(j=4;j<9;j+=2) { par[j-1] = par[1]*par[j-3] - par[2]*par[j-2] ; par[j] = par[1]*par[j-2] + par[2]*par[j-3] ; } sumold = sum ; for(sum=j=0;j<9;j++) sum += denex[j]*par[j] ; if(fabs(sum)>fabs(denmax)) { denmax = sum ; isave = i ; tempa = sumold ; } else if(i==isave+1) tempb = sum ; } if(isave==0) tempa = sum ; else if(isave==49) tempb = denold ; step = 0 ; if(tempa!=tempb) { tempa -= denmax ; tempb -= denmax ; step = 0.5 * (tempa-tempb) / (tempa+tempb) ; } /* Calculate the new parameters of the denominator, the new VLAG * vector and the new D. Then test for convergence. */ angle = temp * (isave+step) ; par[1] = cos(angle) ; par[2] = sin(angle) ; for(j=4;j<9;j+=2) { par[j-1] = par[1]*par[j-3] - par[2]*par[j-2] ; par[j] = par[1]*par[j-2] + par[2]*par[j-3] ; } for(denmax=beta=j=0;j<9;j++) { beta += den[j]*par[j] ; denmax += denex[j]*par[j] ; } for(k=0;k<ndim;k++) for(vlag[k]=j=0;j<5;j++) vlag[k] += prod[k][j] * par[j] ; tau = vlag[knew] ; for(dd=tempa=tempb=i=0;i<n;i++) { d[i] = par[1]*d[i] + par[2]*s[i] ; w[i] = xopt[i] + d[i] ; dd += d[i] * d[i] ; tempa += d[i] * w[i] ; tempb += w[i] * w[i] ; } if(iterc>=n) break ; if(iterc>1) densav = fmax(densav, denold) ; if(fabs(denmax)<=fabs(densav)*1.1) break ; densav = denmax ; /* Set S to 0.5 the gradient of the denominator with respect to * D. Then branch for the next iteration. */ for(i=0;i<n;i++) { temp = tempa*xopt[i] + tempb*d[i] - vlag[npt+i] ; s[i] = tau*b[i][knew] + alpha*temp ; } for(k=0;k<npt;k++) { for(sum=j=0;j<n;j++) sum += xp[j][k] * w[j] ; temp = (tau*w[n+k]-alpha*vlag[k]) * sum ; for(i=0;i<n;i++) s[i] += temp * xp[i][k] ; } for(ss=ds=i=0;i<n;i++) { ss += s[i]*s[i] ; ds += d[i]*s[i] ; } ssden = dd*ss - ds*ds ; if(ssden<dd*1e-8*ss) break ; } /* Set the vector W before the RETURN from the subroutine. */ for(k=0;k<ndim;k++) for(w[k]=j=0;j<5;j++) w[k] += wvec[k][j] * par[j] ; vlag[kopt] += 1 ; freematrix(prod,wvec) ; free(s) ; return beta ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------------------------------------------------- */ /* The following instructions set the vector HD to the vector D multiplied by the second derivative matrix of Q. */ static void sethd(double *hd,int n,double **xp,double *d,double *hq,double *pq,int npt) { int i,j,k ; double temp ; for(i=0;i<n;i++) hd[i] = 0 ; updategopt(n,hd,hq,d) ; updategopt2(n,npt,hd,d,pq,xp) ; } /* -------------------------------------------------------------------------- */ static double trsapp(int n, int npt, double * xopt, double **xp, double * gq, double * hq, double * pq,double delsq, double * step) { /* The arguments NPT, XOPT, XPT, GQ, HQ and PQ have their usual meanings, * in order to define the current quadratic model Q. * DELTA is the trust region radius, and has to be positive. STEP * will be set to the calculated trial step. CRVMIN will be returned as the * least curvature of H aint the conjugate directions that occur, * except that it is set to 0 ifSTEP goes all the way to the trust * region boundary. The calculation of STEP begins with the * truncated conjugate gradient method. If the boundary of the trust * region is reached, then further changes to STEP may be made, each * one being in the 2D space spanned by the current STEP and the * corresponding gradient of Q. Thus STEP should provide a * substantial reduction to Q within the trust region. */ int i,j,iterc,isave,itermax ; double dd,cf,dg,gg,ds,sg,ss,dhd,dhs,cth,sgk,shs,sth,qadd,qbeg,qred,qmin ; double temp,qsav,qnew,ggbeg,alpha,angle,reduc,ggsav,tempa,tempb,bstep ; double ratio,angtest,crvmin ; double *d=vector(n),*g=vector(n),*hd=vector(n),*hs=vector(n) ; itermax = n ; for(i=0;i<n;i++) d[i] = xopt[i] ; sethd(hd,n,xp,d,hq,pq,npt) ; /* Prepare for the first line search. */ for(qred=dd=i=0;i<n;i++) { step[i] = hs[i] = 0 ; g[i] = gq[i] + hd[i] ; d[i] = -g[i] ; dd += d[i]*d[i] ; } if(dd==0) { free(d,g,hd,hs) ; return 0 ; } ggbeg = gg = dd ; /* ------------------------------------------------------------------------ */
/*page*/ /* -- Calculate the step to the trust region boundary and the product HD -- */ for(ds=ss=0,iterc=1;ss<delsq;iterc++) { temp = delsq - ss; bstep = temp / (ds + sqrt(ds * ds + dd * temp)); sethd(hd,n,xp,d,hq,pq,npt) ; for(dhd=j=0;j<n;j++) dhd += d[j] * hd[j] ; /* Update CRVMIN and set the step-length ALPHA. */ alpha = bstep ; if(dhd>0) { if(iterc==1) crvmin = dhd/dd ; else crvmin = fmin(crvmin,dhd/dd) ; alpha = fmin(alpha,gg/dhd) ; } qred += ( qadd = alpha * (gg - 0.5*alpha*dhd) ) ; /* Update STEP and HS. */ for(ggsav=gg,gg=i=0;i<n;i++) { step[i] += alpha * d[i] ; hs[i] += alpha * hd[i] ; temp = g[i] + hs[i] ; gg += temp * temp ; } /* Begin another conjugate direction iteration if required. */ if(alpha>=bstep) break ; ds = 0 ; if(qadd>qred*.01&&gg>ggbeg*1e-4&&iterc<itermax) { temp = gg / ggsav ; for(dd=ss=i=0;i<n;i++) { d[i] = temp*d[i] - g[i] - hs[i] ; dd += d[i]*d[i] ; ds += d[i]*step[i] ; ss += step[i]*step[i] ; } } if(ds<=0) { free(d,g,hd,hs) ; return crvmin ; } } /* ------------------------------------------------------------------------ */
/*page*/ /* ------------ Test whether an alternative iteration is required --------- */ for(iterc++;gg>ggbeg*1e-4;iterc++) { for(sg=shs=i=0;i<n;i++) { sg += step[i]*g[i] ; shs += step[i]*hs[i] ; } sgk = sg + shs ; angtest = sgk / sqrt(gg*delsq) ; if(angtest<=-.99) break ; /* Begin the alternative iteration by calculating D and HD and some * scalar products. */ temp = sqrt(delsq*gg - sgk*sgk) ; tempa = delsq / temp ; tempb = sgk / temp ; for(i=0;i<n;i++) d[i] = tempa*(g[i]+hs[i]) - tempb*step[i] ; sethd(hd,n,xp,d,hq,pq,npt) ; for(dg=dhd=dhs=i=0;i<n;i++) { dg += d[i]*g[i] ; dhd += hd[i]*d[i] ; dhs += hd[i]*step[i] ; } /* Seek the value of the angle that minimizes Q. */ cf = 0.5 * (shs-dhd) ; qbeg = sg + cf ; qsav = qmin = qbeg ; temp = 2 * pi / 50 ; for(isave=0,i=1;i<50;i++) { angle = i * temp ; cth = cos(angle) ; sth = sin(angle) ; qnew = (sg+cf*cth)*cth + (dg+dhs*cth)*sth ; if(qnew<qmin) { qmin = qnew ; isave = i ; tempa = qsav ; } else if(i==isave+1) tempb = qnew ; qsav = qnew ; } if(isave==0) tempa = qnew ; else if(isave==49) tempb = qbeg ; angle = 0 ; if(tempa!=tempb) { tempa -= qmin ; tempb -= qmin ; angle = 0.5 * (tempa-tempb) / (tempa+tempb) ; } angle = temp * (isave+angle) ; /* Calculate the new STEP and HS. Then test for convergence. */ cth = cos(angle) ; sth = sin(angle) ; reduc = qbeg - (sg + cf * cth) * cth - (dg + dhs * cth) * sth ; for(gg=i=0;i<n;i++) { step[i] = cth*step[i] + sth*d[i] ; hs[i] = cth*hs[i] + sth*hd[i] ; temp = g[i] + hs[i] ; gg += temp*temp ; } qred += reduc ; ratio = reduc / qred ; if(iterc>=itermax||ratio<=0.01) break ; } free(d,g,hd,hs) ; return 0 ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------------------------------------------------- */ static double setrhodelta(double *rho,double rhoend,double *delta) { double ratio=rho[0]/rhoend ; delta[0] = rho[0] / 2 ; if(ratio<=16) rho[0] = rhoend ; else if(ratio<=250) rho[0] = sqrt(ratio) * rhoend ; else rho[0] *= 0.1 ; delta[0] = fmax(delta[0],rho[0]) ; return ratio ; } static double setknewdistsq (int n,int npt,double delta,int *knew,double *xopt,double **xp) { int j,k ; double sum,temp,distsq=4*delta*delta ; for(k=0;k<npt;k++) { for(sum=j=0;j<n;j++) { temp = xp[j][k] - xopt[j] ; sum += temp*temp ; } if(sum>distsq) { knew[0] = k ; distsq = sum ; } } return distsq ; } /* -------------------------------------------------------------------------- */ double uoamin(double (*func)(double*),double *x,int n, double rhobeg,double rhoend,int maxfun) { /* This subroutine seeks the least value of a function of many * variables, by a trust region method that forms quadratic models * by interpolation. There can be some freedom in the interpolation * conditions, which is taken up by minimizing the Frobenius norm of * the change to the second derivative of the quadratic model, * beginning with a zero matrix. The arguments of the subroutine are * as follows. */ /* N must be set to the number of variables and must be at least * two. NPT is the number of interpolation conditions. Its value * must be in the interval [N+2,(N+1)(N+2)/2]. Initial values of the * variables must be set in X(1),X(2),...,X(N). They will be changed * to the values that give the least calculated F. xinc and tol * must be set to the initial and final values of a trust region * radius, so both must be positive with tol<=xinc. Typically * xinc should be about one tenth of the greatest expected change * to a variable, and tol should indicate the accuracy that is * required in the final values of the variables. MAXFUN must be set * to an upper bound on the number of calls of CALFUN. */ /* SUBROUTINE func(x) must be provided by the user. */ /* XBASE will hold a shift of origin that should reduce the contributions * from rounding errors to values of the model and Lagrange functions. * XOPT will be set to the displacement from XBASE of the vector of variables that provides the least calculated F so far. * XNEW will be set to the displacement from XBASE of the vector of variables for the current calculation of F. */ /* ------------------------------------------------------------------------ */
/*page*/ /* ------------------------------------------------------------------------ */ /* XPT will contain the interpolation point coordinates relative to XBASE. * FVAL will hold the values of F at the interpolation points. * GQ will hold the gradient of the quadratic model at XBASE. * HQ will hold the explicit second derivatives of the quadratic model. * PQ will contain the parameters of the implicit second derivatives of the quadratic model. * BMAT will hold the last N columns of H. * ZMAT will hold the factorization of the leading NPT by NPT submatrix * of H, this factorization being ZMAT times Diag(DZ) times ZMAT^T, * where the elements of DZ are plus or minus 1.0, asspecified by IDZ. * NDIM is the first dimension of BMAT and has the value NPT+N. * D is reserved for trial steps from XOPT. * VLAG will contain the values of the Lagrange functions at a new point X. * They are part of a product that requires VLAG to be of length NDIM. */ int i,j,k,ih,nf,nh,nfm,idz,ipt,jpt,nfmm,knew,kopt,nptm,ksave,nfsav,itemp ; int ktemp,itest,npt=2*n+1 ; double f,dsq,rho,sum,fbeg,diff,beta,gisq,temp,suma,sumb,fopt,gqsq ; double xipt,xjpt,diffa,diffb,diffc,alpha,delta,recip,reciq,fsave ; double dnorm,ratio,dstep,vquad,detrat,crvmin,distsq,xoptsq ; double *xbase=vector(n),*xopt=vector(n),*xnew=vector(n),*fval=vector(npt) ; double *gq=vector(n),*hq=vector((n*(n+1))/2),*pq=vector(npt),*d=vector(n) ; double *vlag=vector(npt+n),*w=vector(2*npt),*pqw=vector(npt) ; double **z=matrix(npt-(n+1),npt),**b=matrix(n,npt+n),**xp=matrix(n,npt) ; nh = (n*(n+1)) / 2 ; nptm = npt - (n+1) ; if(maxfun<1) maxfun = 1 ; for(j=0;j<n;j++) xbase[j] = x[j] ; /* Begin the initialization procedure. NF becomes 1 more than the * number of function values so far. The coordinates of the * displacement of the next initial interpolation point from XBASE * are set in XPT(NF,.). */ recip = 1 / (rhobeg*rhobeg) ; reciq = sqrt(0.5) * recip ; /* ------------------------------------------------------------------------ */
/*page*/ /* ------------------------------------------------------------------------ */ for(nf=0;nf<npt;nf++) { nfm = nf-1 ; nfmm = nfm-n ; if(nfm<2*n) // always happens when npt = 2*n+1 { if(nfm>=0&&nfm<n) xp[nfm][nf] = rhobeg ; else if(nfm>=n) xp[nfmm][nf] = -rhobeg ; } else // the case which never happens { itemp = nfmm / n ; jpt = nfm - itemp * n - n ; ipt = jpt + itemp ; if(ipt>=n) { ipt -= n ; swap(ipt,jpt) ; } xipt = rhobeg ; if(fval[ipt+n+1] < fval[ipt+1]) xipt = -xipt ; xjpt = rhobeg ; if(fval[jpt+n+1] < fval[jpt+1]) xjpt = -xjpt ; xp[ipt][nf] = xipt ; xp[jpt][nf] = xjpt ; } /* Calculate the next value of F. The least function value so far and its * index are required. */ for(j=0;j<n;j++) x[j] = xp[j][nf] + xbase[j]; fval[nf] = f = func(x) ; if(nf==0) { fbeg = fopt = f ; kopt = 0 ; } else if(f<fopt) { fopt = f ; kopt = nf ; } /* Set the non0 initial elements of BMAT and the quadratic model * in the cases when NF is at most 2*N+1. */ if(nfm<2*n) { if(nfm>=0&&nfm<n) { gq[nfm] = (f-fbeg) / rhobeg ; if(npt<=nf+n) { b[nfm][0] = -1/rhobeg ; b[nfm][nf] = 1/rhobeg ; b[nfm][npt+nfm-1] = -(rhobeg*rhobeg)/2 ; } } else if(nfm>=n) { b[nfmm][nf-n] = 1/(2*rhobeg) ; b[nfmm][nf] = -1/(2*rhobeg) ; z[nfmm][0] = -(reciq+reciq) ; z[nfmm][nf] = z[nfmm][nf-n] = reciq ; ih = (nfmm * (nfmm+3)) / 2 ; temp = (fbeg-f) / rhobeg ; hq[ih] = (gq[nfmm]-temp) / rhobeg; gq[nfmm] = .5 * (gq[nfmm] + temp); } } /* Set the off-diagonal second derivatives of the Lagrange * functions and the initial quadratic model. */ else { ih = (ipt*(ipt+1))/2 + jpt ; if(xipt<0) ipt += n ; if(xjpt<0) jpt += n ; z[nfmm][0] = z[nfmm][nf] = recip ; z[nfmm][ipt] = z[nfmm][jpt] = -recip ; hq[ih] = (fbeg - fval[ipt+1] - fval[jpt+1] + f) / (xipt*xjpt) ; } } /* ------------------------------------------------------------------------ */
/*page*/ /* - Begin the iterative procedure, because the initial model is complete - */ diffa = diffb = itest = idz = 0 ; for(i=0;i<n;i++) xopt[i] = xp[i][kopt] ; /* Generate the next trust region step and test its length. Set KNEW * to -1 ifthe purpose of the next F will be to improve the * model. */ /* ------------------------------------------------------------------------ */ for(knew=-1,delta=rho=rhobeg,nfsav=nf;;) { if(knew<0) for(;;) { crvmin = trsapp(n,npt,xopt,xp,gq,hq,pq,delta*delta,d) ; for(dsq=i=0;i<n;i++) dsq += d[i]*d[i] ; dnorm = fmin(delta,sqrt(dsq)) ; if(dnorm>=rho/2) break ; // knew must be -1 delta /= 10 ; ratio = -1 ; if(delta<=rho*1.5) delta = rho ; temp = crvmin * .125 * rho * rho ; if(nf<=nfsav+2||temp<=diffa||temp<=diffb||temp<=diffc) { distsq = setknewdistsq(n,npt,delta,&knew,xopt,xp) ; /* If KNEW is positive, then set DSTEP, and branch back for the next * iteration, which will generate a "model step". */ if(knew>=0) { dstep = fmax(rho,fmin(sqrt(distsq)/10,delta/2)) ; dsq = dstep*dstep ; break ; } else if(delta>rho||dnorm>rho) continue ; } /* Return from the calculation, after another Newton-Raphson step, * if it is too short to have been tried before. */ if(rho<=rhoend) { for(i=0;i<n;i++) { xnew[i] = xopt[i] + d[i] ; x[i] = xbase[i] + xnew[i] ; } f = func(x) ; knew = -2 ; break ; } /* The calculations with the current value of RHO are complete. Pick * the next values of RHO and DELTA. */ ratio = setrhodelta(&rho,rhoend,&delta) ; nfsav = nf ; } if(knew==-2) { cond = 0 ; break ; } /* ---------------------------------------------------------------------- */
/*page*/ /* ---------------------------------------------------------------------- */ /* Shift XBASE if XOPT may be too far from XBASE. First make the * changes to BMAT that do not depend on ZMAT. */ for(xoptsq=i=0;i<n;i++) xoptsq += xopt[i] * xopt[i] ; if(dsq<=xoptsq/1000) { updategopt(n,gq,hq,xopt) ; updategopt2(n,npt,gq,xopt,pq,xp) ; shiftxbase(n,npt,xp,xopt,vlag,b,z,pq,hq,idz) ; for(j=0;j<n;j++) { xbase[j] += xopt[j] ; xopt[j] = 0 ; } xoptsq = 0 ; } /* Pick the model step ifKNEW is positive. A different choice of D * may be made later, ifthe choice of D by BIGLAG causes * substantial cancellation in DENOM. */ if(knew>=0) alpha = biglag(n,npt,xopt,xp,b,z,idz,knew,dstep,d) ; /* Calculate VLAG and BETA for the current choice of D. The first * NPT components of W_check will be held in W. */ for(k=0;k<npt;k++) { for(vlag[k]=suma=sumb=j=0;j<n;j++) { suma += xp[j][k] * d[j] ; sumb += xp[j][k] * xopt[j] ; vlag[k] += b[j][k] * d[j] ; } w[k] = suma * (suma/2+sumb) ; } /* ---------------------------------------------------------------------- */
/*page*/ /* ---------------------------------------------------------------------- */ beta = calcvlagbeta(n,npt,vlag,z,b,w,xopt,d,beta,dsq,xoptsq,idz) ; vlag[kopt] += 1 ; /* If KNEW is positive and if the cancellation in DENOM is unacceptable, * then BIGDEN calculates an alternative model step. */ if(knew>=0&&fabs(1 + alpha*beta / (vlag[knew]*vlag[knew]))<=0.8) beta = bigden(n,npt,xopt,xp,b,z,idz,kopt,knew,d,w,vlag) ; /* Calculate the next value of the objective function. */ if(nf+1>=maxfun) { cond = 2 ; break ; } for(i=0;i<n;i++) { xnew[i] = xopt[i] + d[i] ; x[i] = xbase[i] + xnew[i] ; } nf += 1 ; f = func(x) ; /* Use the quadratic model to predict the change in F due to the * step D, and set DIFF to the error of this prediction. */ for(vquad=ih=j=0;j<n;j++) { vquad += d[j] * gq[j] ; for(i=0;i<=j;ih++,i++) { temp = d[i]*xnew[j] + d[j]*xopt[i] ; if(i==j) temp /= 2 ; vquad += temp * hq[ih] ; } } for(k=0;k<npt;k++) vquad += pq[k] * w[k] ; diff = f - fopt - vquad ; diffc = diffb ; diffb = diffa ; diffa = fabs(diff) ; if(dnorm>rho) nfsav = nf ; /* Update FOPT and XOPT ifthe new F is the least value of the * objective function so far. The branch when KNEW is positive * occurs ifD is not a trust region step. */ fsave = fopt ; if(f<fopt) { fopt = f ; for(i=0;i<n;i++) xopt[i] = xnew[i] ; } ksave = knew ; if(knew<0) { /* Pick the next value of DELTA after a trust region step. */ if(vquad>=0) { cond = 1 ; break ; } ratio = (f-fsave) / vquad ; if(ratio<=0.1) delta = dnorm/2 ; else if(ratio<=.7) delta = fmax(delta/2,dnorm) ; else delta = fmax(delta/2,2*dnorm) ; if(delta<=rho*1.5) delta = rho ; /* Set KNEW to the index of the next interpolation point to be * deleted. */ temp = fmax(delta/10,rho) ; if(f>=fsave) { ktemp = kopt ; detrat = 1 ; } else { ktemp = -1 ; detrat = 0 ; } knew = chooseknew(n,npt,beta,z,vlag,xp,xopt,detrat,temp*temp,ktemp,idz) ; } if(knew>=0) { /* Update BMAT, ZMAT and IDZ, so that the KNEW-th interpolation * point can be moved. Begin the updating of the quadratic model, * starting with the explicit second derivative term. */ idz = update(n,npt,b,z,idz,vlag,beta,knew) ; /* Update the other second derivative parameters, and then the * gradient vector of the model. Also include the new interpolation * point. */ updatehq(n,npt,hq,pq[knew],xp,knew) ; pq[knew] = 0 ; genpqw(n,npt,pqw,z,knew,idz) ; for(k=0;k<npt;k++) pq[k] += diff * pqw[k] ; fval[knew] = f ; for(gqsq=i=0;i<n;i++) { gq[i] += diff * b[i][knew]; gqsq += gq[i] * gq[i]; xp[i][knew] = xnew[i]; } /* If a trust region step makes a small change to the objective * function, then calculate the gradient of the least Frobenius norm * interpolant at XBASE, and store it in W, using VLAG for a vector * of right hand sides. */ if(ksave==-1&&delta==rho&&fabs(ratio)>.01) itest = 0 ; else if(ksave==-1&&delta==rho) { for(k=0;k<npt;k++) vlag[k] = fval[k] - fval[kopt] ; for(gisq=i=0;i<n;i++) { for(w[i]=k=0;k<npt;k++) w[i] += b[i][k] * vlag[k] ; gisq += w[i]*w[i] ; } itest += 1 ; if(gqsq<gisq*100) itest = 0 ; if(itest>=3) { for(i=0;i<n;i++) gq[i] = w[i] ; for(ih=0;ih<nh;ih++) hq[ih] = 0 ; for(j=0;j<nptm;j++) { for(w[j]=k=0;k<npt;k++) w[j] += vlag[k] * z[j][k] ; if(j<idz) w[j] = -w[j] ; } for(k=0;k<npt;k++) for(pq[k]=j=0;j<nptm;j++) pq[k] += z[j][k] * w[j]; itest = 0 ; } } if(f<fsave) kopt = knew ; knew = -1 ; /* If a trust region step has provided a sufficient decrease in F, * then branch for another trust region calculation. The case * KSAVE>0 occurs when the new function value was calculated by a * model step. */ if(f<=fsave+vquad/10||ksave>=0) continue ; /* Alternatively, find out ifthe interpolation points are close * enough to the best point so far. */ } distsq = setknewdistsq(n,npt,delta,&knew,xopt,xp) ; /* If KNEW is positive, then set DSTEP, and branch back for the next * iteration, which will generate a "model step". */ if(knew>=0) { dstep = fmax(rho,fmin(sqrt(distsq)/10,delta/2)) ; dsq = dstep*dstep ; } else if(ratio<=0&&delta<=rho&&dnorm<=rho) /* The calculations with the current value of RHO are complete. Pick * the next values of RHO and DELTA. */ { if(rho<=rhoend) { cond = 0 ; break ; } ratio = setrhodelta(&rho,rhoend,&delta) ; nfsav = nf ; } } /* Return from the calculation, after another Newton-Raphson step, * if it is too short to have been tried before. */ if(fopt<=f) { for(i=0;i<n;i++) x[i] = xbase[i] + xopt[i] ; f = fopt ; } free(xbase,xopt,xnew,w,fval,gq) ; free(hq,pq,d,vlag,pqw) ; freematrix(z,b,xp) ; return f ; } /* -------------------------------------------------------------------------- */ static double(*f)(double*) ; static double myfunc(double *x) { return -f(x) ; } double uoamax(double (*func)(double*),double *x,int n, double xinc,double tol,int max_iter) { f = func ; return -uoamin(myfunc,x,n,xinc,tol,max_iter) ; }

Archived from optim.html

#include"memory.h" #include <math.h> #define max(a,b) ((a)>(b)?(a):(b)) #define min(a,b) ((a)<(b)?(a):(b)) void updategopt(int n,double *gopt,double *hq,double *step) { int i,j,ih ; for(ih=j=0;j<n;j++) for(i=0;i<=j;i++,ih++) { if(i<j) gopt[j] += hq[ih] * step[i] ; gopt[i] += hq[ih] * step[j] ; } } void updategopt2(int n,int npt,double *gopt,double *xopt,double *pq,double **xp) { int i,k ; double sum,temp ; for(k=0;k<npt;k++) { for(sum=i=0;i<n;i++) sum += xp[i][k] * xopt[i]; temp = pq[k] * sum ; for(i=0;i<n;i++) gopt[i] += temp * xp[i][k] ; } } /* -------------------------------------------------------------------------- */ int update(int n,int npt,double **b,double **z,int idz,double *vlag, double beta,int knew) { /* The arrays BMAT and ZMAT with IDZ are updated, in order to shift * the interpolation point that has index KNEW. On entry, VLAG * contains the components of the vector Theta*Wcheck+e_b of the * updating formula (6.11), and BETA holds the value of the * parameter that has this name. */ int nptm=npt-n-1,i,j,ja,jb,jl,jp,iflag ; double tau,temp,scala,scalb,alpha,denom,tempa,tempb,tausq,sqrtdn ; double *w=vector(npt+n) ; /* Apply the rotations that put zeros in the KNEW-th row of ZMAT. */ for(jl=0,j=1;j<npt-n-1;j++) { if(j==idz) jl = j ; else if(z[j][knew]!=0) { tempa = z[jl][knew]; tempb = z[j][knew]; temp = sqrt(tempa*tempa+tempb*tempb) ; tempa /= temp ; tempb /= temp ; for(i=0;i<npt;i++) { temp = tempa*z[jl][i] + tempb*z[j][i] ; z[j][i] = tempa*z[j][i] - tempb*z[jl][i] ; z[jl][i] = temp ; } z[j][knew] = 0; } } /* Put the first NPT components of the KNEW-th column of HLAG into * W, and calculate the parameters of the updating formula. */ if(idz<1) tempa = z[0][knew] ; else tempa = -z[0][knew] ; for(i=0;i<npt;i++) w[i] = tempa * z[0][i] ; if(jl>0) for(tempb=z[jl][knew],i=0;i<npt;i++) w[i] += tempb * z[jl][i] ; alpha = w[knew] ; tau = vlag[knew] ; tausq = tau * tau ; denom = alpha*beta + tausq ; vlag[knew] -= 1 ; /* Complete the updating of ZMAT when there is only 1.0 nonzero * element in the KNEW-th row of the new matrix ZMAT, but, ifIFLAG * is set to 1.0, then the first column of ZMAT will be exchanged * with another 1.0 later. */ iflag = 0 ; sqrtdn = sqrt(fabs(denom)) ; /* ------------------------------------------------------------------------ */ / /* ------------------------------------------------------------------------ */ if(jl==0) { tempa = tau / sqrtdn ; tempb = z[0][knew] / sqrtdn ; for(i=0;i<npt;i++) z[0][i] = tempa*z[0][i] - tempb*vlag[i] ; if(denom<0) { if(idz==0) idz = 1 ; else iflag = 1 ; } } else { /* Complete the updating of ZMAT in the alternative case. */ if(beta>=0) ja = jl ; else ja = 0 ; jb = jl - ja ; temp = z[jb][knew] / denom ; tempa = temp * beta ; tempb = temp * tau ; temp = z[ja][knew] ; scala = 1 / sqrt(fabs(beta)*temp*temp+tausq) ; scalb = scala * sqrtdn ; for(i=0;i<npt;i++) { z[ja][i] = scala * (tau * z[ja][i] - temp * vlag[i]) ; z[jb][i] = scalb * (z[jb][i] - tempa * w[i] - tempb * vlag[i]) ; } if(denom<=0) { if(beta<0) idz += 1 ; else iflag = 1 ; } } /* IDZ is reduced in the following case, and usually the first * column of ZMAT is exchanged with a later 1.0. */ if(iflag==1) for(idz-=1,i=0;i<npt;i++) swap(z[0][i],z[idz][i]) ; /* Finally, update the matrix BMAT. */ for(j=0;j<n;j++) { jp = npt + j ; w[jp] = b[j][knew] ; tempa = (alpha*vlag[jp] - tau*w[jp]) / denom ; tempb = -(beta*w[jp] + tau*vlag[jp]) / denom ; for(i=0;i<=jp;i++) { b[j][i] += tempa*vlag[i] + tempb*w[i] ; if(i>=npt) b[i-npt][jp] = b[j][i]; } } free(w) ; return idz ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------------------------------------------------- */ void shiftxbase(int n,int npt,double **xp,double *xopt,double *vlag,double **b, double **z,double *pq,double *hq,int idz) { int i,j,k,ih ; double tempq,temp,sum,xoptsq,sumz,*w=vector(2*npt) ; for(xoptsq=i=0;i<n;i++) xoptsq += xopt[i] * xopt[i] ; tempq = xoptsq/4 ; for(k=0;k<npt;k++) { for(sum=i=0;i<n;i++) sum += xp[i][k] * xopt[i]; sum -= xoptsq/2 ; w[npt+k] = sum ; for(i=0;i<n;i++) { xp[i][k] -= xopt[i]/2 ; vlag[i] = b[i][k]; w[i] = sum*xp[i][k] + tempq*xopt[i] ; for(j=0;j<=i;j++) b[j][npt+i] += vlag[i]*w[j] + w[i]*vlag[j] ; } } /* Then the revisions of BMAT that depend on ZMAT are calculated. */ for(k=0;k<npt-n-1;k++) { for(sumz=i=0;i<npt;i++) { sumz += z[k][i] ; w[i] = w[npt+i] * z[k][i] ; } for(j=0;j<n;j++) { sum = tempq * sumz * xopt[j]; for(i=0;i<npt;i++) sum += w[i] * xp[j][i]; vlag[j] = sum; if(k<idz) sum = -sum; for(i=0;i<npt;i++) b[j][i] += sum * z[k][i]; } for(i=0;i<n;i++) { if(k>=idz) temp = vlag[i] ; else temp = -vlag[i] ; for(j=0;j<=i;j++) b[j][npt+i] += temp * vlag[j] ; } } /* The following instructions complete the shift of XBASE, * including the changes to the parameters of the quadratic * model. */ for(ih=j=0;j<n;j++) { for(w[j]=k=0;k<npt;k++) { w[j] += pq[k] * xp[j][k] ; xp[j][k] -= xopt[j]/2 ; } for(i=0;i<=j;ih++,i++) { hq[ih] += w[i]*xopt[j] + xopt[i]*w[j] ; b[j][npt+i] = b[i][npt+j] ; } } free(w) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------------------------------------------------- */ //C Calculate VLAG and BETA for the current choice of STEP. The first NPT //C elements of VLAG are set to the values of the Lagrange functions at //C XPT(KOPT,.)+STEP(.). The first NPT components of W_check are held //C in W, where W_check is defined in a paper on the updating method. double calcvlagbeta(int n,int npt,double *vlag,double **z,double **b,double *w, double *xopt,double *d,double beta,double dsq,double xsq, int idz) { int i,j,k ; double sum,dx,bsum ; for(beta=k=0;k<npt-n-1;k++) { for(sum=i=0;i<npt;i++) sum += z[k][i] * w[i] ; if(k<idz) { beta += sum*sum ; sum = -sum ; } else beta -= sum*sum ; for(i=0;i<npt;i++) vlag[i] += sum * z[k][i]; } for(bsum=dx=j=0;j<n;j++) { for(sum=i=0;i<npt;i++) sum += w[i] * b[j][i]; bsum += sum * d[j] ; for(k=0;k<n;k++) sum += b[k][npt+j] * d[k]; vlag[npt+j] = sum ; bsum += sum * d[j] ; dx += d[j] * xopt[j] ; } // more numerical instability here? return dx*dx + dsq*((xsq+dx) + dx + dsq/2) + beta - bsum ; } /* -------------------------------------------------------------------------- */ int chooseknew(int n,int npt,double beta,double **z,double *vlag,double **xp, double *xopt,double detrat,double rhosq,int ktemp,int idz) { int i,j,k,knew ; double temp,qtemp,distsq,hdiag,q ; for(knew=-1,k=0;k<npt;k++) { for(hdiag=j=0;j<npt-n-1;j++) if(j<idz) hdiag -= z[j][k]*z[j][k] ; else hdiag += z[j][k]*z[j][k] ; temp = fabs(beta*hdiag+vlag[k]*vlag[k]) ; for(distsq=j=0;j<n;j++) { qtemp = xp[j][k] - xopt[j] ; distsq += qtemp*qtemp ; } if(distsq>rhosq) { if(rhosq==0) q = temp * distsq * distsq ; else { qtemp = distsq / rhosq ; q = temp * (qtemp*qtemp*qtemp) ; } } if(q>detrat&&k!=ktemp) { detrat = q ; knew = k ; } } return knew ; } /* -------------------------------------------------------------------------- */ double genpqw(int n,int npt,double *pqw,double **z,int knew,int idz) { int i,j,k ; double temp ; for(k=0;k<npt;k++) pqw[k] = 0 ; for(j=0;j<npt-n-1;j++) { if(j<idz) temp = -z[j][knew] ; else temp = z[j][knew] ; if(temp!=0) for(k=0;k<npt;k++) pqw[k] += temp * z[j][k] ; } return pqw[knew] ; } void updatehq(int n,int npt,double *hq,double pqknew,double **xpt,int knew) { int i,j,ih ; double temp ; for(ih=i=0;i<n;i++) { temp = pqknew * xpt[i][knew] ; for(j=0;j<=i;j++,ih++) hq[ih] += temp * xpt[j][knew] ; } }

Archived from optim.html

/* This is NEWUOA for unconstrained minimization. The codes were written by Powell in Fortran and then translated to C with f2c. I further modified the code to make it independent of libf2c and f2c.h. Please refer to "The NEWUOA software for unconstrained optimization without derivatives", which is available at www.damtp.cam.ac.uk, for more information. */ /* The original fortran codes are distributed without restrictions. The C++ codes are distributed under MIT license. */ /* The MIT License Copyright (c) 2004, by M.J.D. Powell <mjdp@cam.ac.uk> 2008, by Attractive Chaos <attractivechaos@aol.co.uk> 2015, by Colin Champion <colin.champion@masterlyinactivity.com> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "memory.h" #include <math.h> #define pi 3.141592653589793 static int cond = -1 ; int uoacond() { return cond ; } int update(int,int,double **,double **,int,double *,double,int) ; void shiftxbase(int n,int npt,double **xp,double *xopt,double *vlag,double **b, double **z,double *pq,double *gq,double *hq,int idz) ; /* -------------------------------------------------------------------------- */ static double biglag(int n,int npt,double *xopt,double **xp,double **b, double **z,int idz,int knew,double delta,double *d) { /* N is the number of variables. NPT is the number of interpolation * equations. XOPT is the best interpolation point so far. XPT * contains the coordinates of the current interpolation * points. BMAT provides the last N columns of H. ZMAT and IDZ give * a factorization of the first NPT by NPT submatrix of H. NDIM is * the first dimension of BMAT and has the value NPT+N. KNEW is the * index of the interpolation point that is going to be moved. DELTA * is the current trust region bound. D will be set to the step from * XOPT to the new point. ALPHA will be set to the KNEW-th diagonal * element of the H matrix. HCOL, GC, GD, S and W will be used for * working space. */ /* The step D is calculated in a way that attempts to maximize the * modulus of LFUNC(XOPT+D), subject to the bound ||D|| <= DELTA, * where LFUNC is the KNEW-th Lagrange function. */ int i,j,k,iterc,isave ; double sp,ss,cf1,cf2,cf3,cf4,cf5,dhd,cth,tau,sth,sum,temp,step,angle,scale ; double denom,delsq,tempa,tempb,taubeg,tauold,taumax,dd,gg,alpha ; double *hcol=vector(npt),*w=vector(n),*s=vector(n),*gd=vector(n) ; double *gc=vector(n) ; delsq = delta * delta ; /* Set the first NPT components of HCOL to the leading elements of * the KNEW-th column of H. */ for(k=0;k<npt;k++) hcol[k] = 0 ; for(j=0;j<npt-n-1;j++) { temp = z[j][knew] ; if(j<idz) temp = -temp ; for(k=0;k<npt;k++) hcol[k] += temp * z[j][k] ; } alpha = hcol[knew] ; /* Set the unscaled initial direction D. Form the gradient of LFUNC * atXOPT, and multiply D by the second derivative matrix of LFUNC. */ for(dd=i=0;i<n;i++) { d[i] = xp[i][knew] - xopt[i] ; gc[i] = b[i][knew] ; dd += d[i]*d[i] ; } for(k=0;k<npt;k++) { for(temp=sum=j=0;j<n;j++) { temp += xp[j][k] * xopt[j] ; sum += xp[j][k] * d[j] ; } temp *= hcol[k] ; sum *= hcol[k] ; for(i=0;i<n;i++) { gc[i] += temp * xp[i][k] ; gd[i] += sum * xp[i][k] ; } } /* Scale D and GD, with a sign change if required. Set S to another * vector in the initial two dimensional subspace. */ for(gg=sp=dhd=i=0;i<n;i++) { gg += gc[i] * gc[i] ; sp += d[i] * gc[i] ; dhd += d[i] * gd[i] ; } scale = delta / sqrt(dd) ; if(sp*dhd<0) scale = -scale ; if(sp*sp>dd*.99*gg) temp = 1 ; else temp = 0 ; tau = scale * (fabs(sp)+0.5*scale*fabs(dhd)) ; if(gg*delsq<tau*.01*tau) temp = 1 ; for(i=0;i<n;i++) { d[i] *= scale ; gd[i] *= scale ; s[i] = gc[i] + temp*gd[i] ; } /* Begin the iteration by overwriting S with a vector that has the * required length and direction, except that termination occurs if * the given D and S are nearly parallel. */ for(iterc=0;iterc<n;iterc++) { for(dd=sp=ss=i=0;i<n;i++) { dd += d[i] * d[i] ; sp += d[i] * s[i] ; ss += s[i] * s[i] ; } temp = dd*ss - sp*sp ; if(temp<=dd*1e-8*ss) break ; denom = sqrt(temp); for(i=0;i<n;i++) { s[i] = (dd*s[i]-sp*d[i]) / denom ; w[i] = 0 ; } /* Calculate the coefficients of the objective function on the * circle, beginning with the multiplication of S by the second * derivative matrix. */ for(k=0;k<npt;k++) { for(sum=j=0;j<n;j++) sum += xp[j][k] * s[j] ; sum *= hcol[k] ; for(j=0;j<n;j++) w[j] += sum * xp[j][k] ; } for(cf1=cf2=cf3=cf4=cf5=i=0;i<n;i++) { cf1 += s[i] * w[i]; cf2 += d[i] * gc[i]; cf3 += s[i] * gc[i]; cf4 += d[i] * gd[i]; cf5 += s[i] * gd[i]; } cf1 /= 2 ; cf4 = cf4/2 - cf1 ; /* Seek the value of the angle that maximizes the modulus of TAU. */ taubeg = cf1 + cf2 + cf4 ; taumax = tauold = taubeg; temp = 2 * pi / 50.0 ; for(isave=0,i=1;i<50;tauold=tau,i++) { angle = (double) i*temp; cth = cos(angle) ; sth = sin(angle) ; tau = cf1 + (cf2 + cf4*cth) * cth + (cf3 + cf5*cth) * sth ; if(fabs(tau)>fabs(taumax)) { taumax = tau ; isave = i ; tempa = tauold ; } else if(i==isave+1) tempb = tau ; } if(isave==0) tempa = tau ; else if(isave==49) tempb = taubeg ; step = 0 ; if(tempa!=tempb) { tempa -= taumax ; tempb -= taumax ; step = 0.5 * (tempa-tempb) / (tempa+tempb) ; } angle = temp * (isave+step) ; /* Calculate the new D and GD. Then test for convergence. */ cth = cos(angle) ; sth = sin(angle) ; tau = cf1 + (cf2 + cf4*cth) * cth + (cf3 + cf5*cth) * sth ; for(i=0;i<n;i++) { d[i] = cth*d[i] + sth*s[i] ; gd[i] = cth*gd[i] + sth*w[i] ; s[i] = gc[i] + gd[i] ; } if(fabs(tau)<=fabs(taubeg)*1.1) break ; } free(hcol,w,s,gd,gc) ; return alpha ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------------------------------------------------- */ static double bigden(int n,int npt,double *xopt,double **xp,double **b, double **z,int idz,int kopt,int knew, double *d,double *w, double *vlag) { /* N is the number of variables. * NPT is the number of interpolation equations. * XOPT is the best interpolation point so far. * XPT contains the coordinates of the current interpolation points. * BMAT provides the last N columns of H. * ZMAT and IDZ give a factorization of the first NPT by NPT submatrix of H. * NDIMis the first dimension of BMAT and has the value NPT+N. * KOPT is the index of the optimal interpolation point. * KNEW is the index of the interpolation point that is going to be moved. * D will be set to the step from XOPT to the new point, and on entry it * should be the D that was calculated by the last call of BIGLAG. The length of the initial D provides a trust region bound on the final D. * W will be set to Wcheck for the final choice of D. * VLAG will be set to Theta*Wcheck+e_b for the final choice of D. * D is calculated in a way that should provide a denominator with a large modulus in the updating formula when the KNEW-th interpolation point is shifted to the new position XOPT+D. * the RETURN value beta will be found in the updating formula when the KNEW-th interpolation point is moved to its new position. */ int i,j,k,isave,iterc,jc,nw,ksav,ndim=npt+n ; double dd,ds,ss,den[9],par[9],tau,sum,diff,temp,step,beta,alpha,angle ; double denex[9],tempa,tempb,tempc,ssden,dtest,xoptd,xopts,denold,denmax ; double densav,dstemp,sumold,sstemp,xoptsq ; double **prod=matrix(ndim,5),**wvec=matrix(ndim,5),*s=vector(n) ; /* Store the first NPT elements of the KNEW-th column of H in W(N+1) * to W(N+NPT). */ for(k=0;k<npt;k++) w[n+k] = 0 ; for(j=0;j<npt-n-1;j++) { temp = z[j][knew] ; if(j<idz) temp = -temp ; for(k=0;k<npt;k++) w[n+k] += temp * z[j][k] ; } alpha = w[n+knew] ; /* The initial search direction D is taken from the last call of * BIGBDLAG, and the initial S is set below, usually to the direction * from X_OPT to X_KNEW, but a different direction to an * interpolation point may be chosen, in order to prevent S from * being nearly parallel to D. */ for(dd=ds=ss=xoptsq=i=0;i<n;i++) { dd += d[i]*d[i] ; s[i] = xp[i][knew] - xopt[i] ; ds += d[i] * s[i] ; ss += s[i] * s[i] ; xoptsq += xopt[i] * xopt[i]; } if(ds*ds>dd*.99*ss) { ksav = knew ; dtest = ds * ds / ss ; for(k=0;k<npt;k++) if(k!=kopt) { for(dstemp=sstemp=i=0;i<n;i++) { diff = xp[i][k] - xopt[i] ; dstemp += d[i] * diff ; sstemp += diff*diff ; } if(dstemp*dstemp/sstemp<dtest) { ksav = k ; dtest = dstemp * dstemp / sstemp ; ds = dstemp ; ss = sstemp ; } } for(i=0;i<n;i++) s[i] = xp[i][ksav] - xopt[i] ; } ssden = dd*ss - ds*ds ; densav = 0 ; /* Begin the iteration by overwriting S with a vector that has the * required length and direction. */ /* ------------------------------------------------------------------------ */ for(iterc=1;;iterc++) { temp = 1.0 / sqrt(ssden) ; for(xoptsq=xoptd=xopts=i=0;i<n;i++) { s[i] = temp * (dd*s[i]-ds*d[i]) ; xoptd += xopt[i] * d[i] ; xopts += xopt[i] * s[i] ; xoptsq += xopt[i] * xopt[i] ; } /* Set the coefficients of the first 2 terms of BETA. */ tempa = 0.5 * xoptd * xoptd ; tempb = 0.5 * xopts * xopts ; den[0] = dd * (xoptsq + 0.5*dd) + tempa + tempb ; den[1] = 2 * xoptd * dd ; den[2] = 2 * xopts * dd ; den[3] = tempa - tempb ; den[4] = xoptd * xopts ; for(i=4;i<8;i++) den[i+1] = 0 ; /* Put the coefficients of Wcheck in WVEC. */ for(k=0;k<npt;k++) { for(tempa=tempb=tempc=i=0;i<n;i++) { tempa += xp[i][k] * d[i]; tempb += xp[i][k] * s[i]; tempc += xp[i][k] * xopt[i]; } wvec[k][0] = 0.25 * (tempa*tempa + tempb*tempb) ; wvec[k][1] = tempa * tempc ; wvec[k][2] = tempb * tempc ; wvec[k][3] = 0.25 * (tempa*tempa - tempb*tempb) ; wvec[k][4] = 0.5 * tempa * tempb ; } for(i=0;i<n;i++) { wvec[i+npt][0] = 0 ; wvec[i+npt][1] = d[i] ; wvec[i+npt][2] = s[i] ; wvec[i+npt][3] = 0 ; wvec[i+npt][4] = 0 ; } /* Put the coefficents of THETA*Wcheck in PROD. */ for(jc=0;jc<5;jc++) { if(jc==2||jc==3) nw = ndim ; else nw = npt ; for(k=0;k<npt;k++) prod[k][jc] = 0 ; for(j=0;j<npt-n-1;j++) { for(sum=k=0;k<npt;k++) sum += z[j][k] * wvec[k][jc] ; if(j<idz) sum = -sum ; for(k=0;k<npt;k++) prod[k][jc] += sum * z[j][k] ; } if(nw==ndim) for(k=0;k<npt;k++) { for(sum=j=0;j<n;j++) sum += b[j][k] * wvec[npt+j][jc] ; prod[k][jc] += sum ; } for(j=0;j<n;j++) { for(sum=i=0;i<nw;i++) sum += b[j][i] * wvec[i][jc] ; prod[npt+j][jc] = sum ; } } /* Include in DEN the part of BETA that depends on THETA. */ for(k=0;k<ndim;k++) { for(sum=i=0;i<5;i++) sum += ( par[i] = 0.5 * prod[k][i] * wvec[k][i] ) ; den[0] -= par[0] + sum ; tempa = prod[k][0] * wvec[k][1] + prod[k][1] * wvec[k][0] ; tempb = prod[k][1] * wvec[k][3] + prod[k][3] * wvec[k][1] ; tempc = prod[k][2] * wvec[k][4] + prod[k][4] * wvec[k][2] ; den[1] -= tempa + 0.5 * (tempb+tempc) ; den[5] -= 0.5 * (tempb-tempc) ; tempa = prod[k][0] * wvec[k][2] + prod[k][2] * wvec[k][0] ; tempb = prod[k][1] * wvec[k][4] + prod[k][4] * wvec[k][1] ; tempc = prod[k][2] * wvec[k][3] + prod[k][3] * wvec[k][2] ; den[2] -= tempa + 0.5 * (tempb-tempc) ; den[6] -= 0.5 * (tempb+tempc) ; tempa = prod[k][0] * wvec[k][3] + prod[k][3] * wvec[k][0] ; den[3] -= tempa + par[1] - par[2] ; tempa = prod[k][0] * wvec[k][4] + prod[k][4] * wvec[k][0] ; tempb = prod[k][1] * wvec[k][2] + prod[k][2] * wvec[k][1] ; den[4] -= tempa + 0.5 * tempb ; den[7] -= par[3] - par[4] ; tempa = prod[k][3] * wvec[k][4] + prod[k][4] * wvec[k][3] ; den[8] -= 0.5 * tempa ; } /* Extend DEN so that it holds all the coefficients of DENOM. */ for(sum=i=0;i<5;i++) { tempa = prod[knew][i] ; sum += ( par[i] = 0.5 * tempa * tempa ) ; } denex[0] = alpha*den[0] + par[0] + sum ; tempa = 2 * prod[knew][0] * prod[knew][1] ; tempb = prod[knew][1] * prod[knew][3] ; tempc = prod[knew][2] * prod[knew][4] ; denex[1] = alpha*den[1] + tempa + tempb + tempc ; denex[5] = alpha*den[5] + tempb - tempc ; tempa = 2 * prod[knew][0] * prod[knew][2] ; tempb = prod[knew][1] * prod[knew][4] ; tempc = prod[knew][2] * prod[knew][3] ; denex[2] = alpha*den[2] + tempa + tempb - tempc ; denex[6] = alpha*den[6] + tempb + tempc ; tempa = 2 * prod[knew][0] * prod[knew][3] ; denex[3] = alpha*den[3] + tempa + par[1] - par[2] ; tempa = 2 * prod[knew][0] * prod[knew][4] ; denex[4] = alpha*den[4] + tempa + prod[knew][1]*prod[knew][2] ; denex[7] = alpha*den[7] + par[3] - par[4] ; denex[8] = alpha*den[8] + prod[knew][3]*prod[knew][4] ; /* Seek the value of the angle that maximizes the modulus of DENOM. */ sum = denex[0] + denex[1] + denex[3] + denex[5] + denex[7]; denold = denmax = sum ; temp = 2 * pi / 50 ; par[0] = 1 ; for(isave=0,i=1;i<50;i++) { angle = i * temp ; par[1] = cos(angle) ; par[2] = sin(angle) ; for(j=4;j<9;j+=2) { par[j-1] = par[1]*par[j-3] - par[2]*par[j-2] ; par[j] = par[1]*par[j-2] + par[2]*par[j-3] ; } sumold = sum ; for(sum=j=0;j<9;j++) sum += denex[j]*par[j] ; if(fabs(sum)>fabs(denmax)) { denmax = sum ; isave = i ; tempa = sumold ; } else if(i==isave+1) tempb = sum ; } if(isave==0) tempa = sum ; else if(isave==49) tempb = denold ; step = 0 ; if(tempa!=tempb) { tempa -= denmax ; tempb -= denmax ; step = 0.5 * (tempa-tempb) / (tempa+tempb) ; } /* Calculate the new parameters of the denominator, the new VLAG * vector and the new D. Then test for convergence. */ angle = temp * (isave+step) ; par[1] = cos(angle) ; par[2] = sin(angle) ; for(j=4;j<9;j+=2) { par[j-1] = par[1]*par[j-3] - par[2]*par[j-2] ; par[j] = par[1]*par[j-2] + par[2]*par[j-3] ; } for(denmax=beta=j=0;j<9;j++) { beta += den[j]*par[j] ; denmax += denex[j]*par[j] ; } for(k=0;k<ndim;k++) for(vlag[k]=j=0;j<5;j++) vlag[k] += prod[k][j] * par[j] ; tau = vlag[knew] ; for(dd=tempa=tempb=i=0;i<n;i++) { d[i] = par[1]*d[i] + par[2]*s[i] ; w[i] = xopt[i] + d[i] ; dd += d[i] * d[i] ; tempa += d[i] * w[i] ; tempb += w[i] * w[i] ; } if(iterc>=n) break ; if(iterc>1) densav = fmax(densav, denold) ; if(fabs(denmax)<=fabs(densav)*1.1) break ; densav = denmax ; /* Set S to 0.5 the gradient of the denominator with respect to * D. Then branch for the next iteration. */ for(i=0;i<n;i++) { temp = tempa*xopt[i] + tempb*d[i] - vlag[npt+i] ; s[i] = tau*b[i][knew] + alpha*temp ; } for(k=0;k<npt;k++) { for(sum=j=0;j<n;j++) sum += xp[j][k] * w[j] ; temp = (tau*w[n+k]-alpha*vlag[k]) * sum ; for(i=0;i<n;i++) s[i] += temp * xp[i][k] ; } for(ss=ds=i=0;i<n;i++) { ss += s[i]*s[i] ; ds += d[i]*s[i] ; } ssden = dd*ss - ds*ds ; if(ssden<dd*1e-8*ss) break ; } /* Set the vector W before the RETURN from the subroutine. */ for(k=0;k<ndim;k++) for(w[k]=j=0;j<5;j++) w[k] += wvec[k][j] * par[j] ; vlag[kopt] += 1 ; freematrix(prod,wvec) ; free(s) ; return beta ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------------------------------------------------- */ /* The following instructions set the vector HD to the vector D multiplied by the second derivative matrix of Q. */ static void sethd(double *hd,int n,double **xp,double *d,double *hq,double *pq,int npt) { int i,j,k,ih ; double temp ; for(i=0;i<n;i++) hd[i] = 0 ; for(k=0;k<npt;k++) { for(temp=j=0;j<n;j++) temp += xp[j][k] * d[j] ; temp *= pq[k] ; for(i=0;i<n;i++) hd[i] += temp * xp[i][k] ; } for(ih=j=0;j<n;j++) for(i=0;i<=j;ih++,i++) { if(i<j) hd[j] += hq[ih] * d[i] ; hd[i] += hq[ih]*d[j] ; } } /* -------------------------------------------------------------------------- */ static double trsapp(int n, int npt, double * xopt, double **xp, double * gq, double * hq, double * pq,double delsq, double * step) { /* The arguments NPT, XOPT, XPT, GQ, HQ and PQ have their usual meanings, * in order to define the current quadratic model Q. * DELTA is the trust region radius, and has to be positive. STEP * will be set to the calculated trial step. CRVMIN will be returned as the * least curvature of H aint the conjugate directions that occur, * except that it is set to 0 ifSTEP goes all the way to the trust * region boundary. The calculation of STEP begins with the * truncated conjugate gradient method. If the boundary of the trust * region is reached, then further changes to STEP may be made, each * one being in the 2D space spanned by the current STEP and the * corresponding gradient of Q. Thus STEP should provide a * substantial reduction to Q within the trust region. */ int i,j,iterc,isave,itermax ; double dd,cf,dg,gg,ds,sg,ss,dhd,dhs,cth,sgk,shs,sth,qadd,qbeg,qred,qmin ; double temp,qsav,qnew,ggbeg,alpha,angle,reduc,ggsav,tempa,tempb,bstep ; double ratio,angtest,crvmin ; double *d=vector(n),*g=vector(n),*hd=vector(n),*hs=vector(n) ; itermax = n ; for(i=0;i<n;i++) d[i] = xopt[i] ; sethd(hd,n,xp,d,hq,pq,npt) ; /* Prepare for the first line search. */ for(qred=dd=i=0;i<n;i++) { step[i] = hs[i] = 0 ; g[i] = gq[i] + hd[i] ; d[i] = -g[i] ; dd += d[i]*d[i] ; } if(dd==0) { free(d,g,hd,hs) ; return 0 ; } ggbeg = gg = dd ; /* ------------------------------------------------------------------------ */
/*page*/ /* -- Calculate the step to the trust region boundary and the product HD -- */ for(ds=ss=0,iterc=1;ss<delsq;iterc++) { temp = delsq - ss; bstep = temp / (ds + sqrt(ds * ds + dd * temp)); sethd(hd,n,xp,d,hq,pq,npt) ; for(dhd=j=0;j<n;j++) dhd += d[j] * hd[j] ; /* Update CRVMIN and set the step-length ATRLPHA. */ alpha = bstep ; if(dhd>0) { if(iterc==1) crvmin = dhd/dd ; else crvmin = fmin(crvmin,dhd/dd) ; alpha = fmin(alpha,gg/dhd) ; } qred += ( qadd = alpha * (gg - 0.5*alpha*dhd) ) ; /* Update STEP and HS. */ for(ggsav=gg,gg=i=0;i<n;i++) { step[i] += alpha * d[i] ; hs[i] += alpha * hd[i] ; temp = g[i] + hs[i] ; gg += temp * temp ; } /* Begin another conjugate direction iteration if required. */ if(alpha>=bstep) break ; ds = 0 ; if(qadd>qred*.01&&gg>ggbeg*1e-4&&iterc<itermax) { temp = gg / ggsav ; for(dd=ss=i=0;i<n;i++) { d[i] = temp*d[i] - g[i] - hs[i] ; dd += d[i]*d[i] ; ds += d[i]*step[i] ; ss += step[i]*step[i] ; } } if(ds<=0) { free(d,g,hd,hs) ; return crvmin ; } } /* ------------------------------------------------------------------------ */
/*page*/ /* ------------ Test whether an alternative iteration is required --------- */ for(iterc++;gg>ggbeg*1e-4;iterc++) { for(sg=shs=i=0;i<n;i++) { sg += step[i]*g[i] ; shs += step[i]*hs[i] ; } sgk = sg + shs ; angtest = sgk / sqrt(gg*delsq) ; if(angtest<=-.99) break ; /* Begin the alternative iteration by calculating D and HD and some * scalar products. */ temp = sqrt(delsq*gg - sgk*sgk) ; tempa = delsq / temp ; tempb = sgk / temp ; for(i=0;i<n;i++) d[i] = tempa*(g[i]+hs[i]) - tempb*step[i] ; sethd(hd,n,xp,d,hq,pq,npt) ; for(dg=dhd=dhs=i=0;i<n;i++) { dg += d[i]*g[i] ; dhd += hd[i]*d[i] ; dhs += hd[i]*step[i] ; } /* Seek the value of the angle that minimizes Q. */ cf = 0.5 * (shs-dhd) ; qbeg = sg + cf ; qsav = qmin = qbeg ; temp = 2 * pi / 50 ; for(isave=0,i=1;i<50;i++) { angle = i * temp ; cth = cos(angle) ; sth = sin(angle) ; qnew = (sg+cf*cth)*cth + (dg+dhs*cth)*sth ; if(qnew<qmin) { qmin = qnew ; isave = i ; tempa = qsav ; } else if(i==isave+1) tempb = qnew ; qsav = qnew ; } if(isave==0) tempa = qnew ; else if(isave==49) tempb = qbeg ; angle = 0 ; if(tempa!=tempb) { tempa -= qmin ; tempb -= qmin ; angle = 0.5 * (tempa-tempb) / (tempa+tempb) ; } angle = temp * (isave+angle) ; /* Calculate the new STEP and HS. Then test for convergence. */ cth = cos(angle) ; sth = sin(angle) ; reduc = qbeg - (sg + cf * cth) * cth - (dg + dhs * cth) * sth ; for(gg=i=0;i<n;i++) { step[i] = cth*step[i] + sth*d[i] ; hs[i] = cth*hs[i] + sth*hd[i] ; temp = g[i] + hs[i] ; gg += temp*temp ; } qred += reduc ; ratio = reduc / qred ; if(iterc>=itermax||ratio<=0.01) break ; } free(d,g,hd,hs) ; return 0 ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------------------------------------------------- */ static double setrhodelta(double *rho,double rhoend,double *delta) { double ratio=rho[0]/rhoend ; delta[0] = rho[0] / 2 ; if(ratio<=16) rho[0] = rhoend ; else if(ratio<=250) rho[0] = sqrt(ratio) * rhoend ; else rho[0] = 0.1 * rho[0] ; // !! ** !! delta[0] = fmax(delta[0],rho[0]) ; return ratio ; } static double setknewdistsq (int n,int npt,double delta,int *knew,double *xopt,double **xp) { int j,k ; double sum,temp,distsq=4*delta*delta ; for(k=0;k<npt;k++) { for(sum=j=0;j<n;j++) { temp = xp[j][k] - xopt[j] ; sum += temp*temp ; } if(sum>distsq) { knew[0] = k ; distsq = sum ; } } return distsq ; } /* -------------------------------------------------------------------------- */ double uoamin(double (*func)(double*),double *x,int n, double rhobeg,double rhoend,int maxfun) { /* This subroutine seeks the least value of a function of many * variables, by a trust region method that forms quadratic models * by interpolation. There can be some freedom in the interpolation * conditions, which is taken up by minimizing the Frobenius norm of * the change to the second derivative of the quadratic model, * beginning with a zero matrix. The arguments of the subroutine are * as follows. */ /* N must be set to the number of variables and must be at least * two. NPT is the number of interpolation conditions. Its value * must be in the interval [N+2,(N+1)(N+2)/2]. Initial values of the * variables must be set in X(1),X(2),...,X(N). They will be changed * to the values that give the least calculated F. xinc and tol * must be set to the initial and final values of a trust region * radius, so both must be positive with tol<=xinc. Typically * xinc should be about one tenth of the greatest expected change * to a variable, and tol should indicate the accuracy that is * required in the final values of the variables. MAXFUN must be set * to an upper bound on the number of calls of CALFUN. */ /* SUBROUTINE func(x) must be provided by the user. */ /* XBASE will hold a shift of origin that should reduce the contributions * from rounding errors to values of the model and Lagrange functions. * XOPT will be set to the displacement from XBASE of the vector of variables that provides the least calculated F so far. * XNEW will be set to the displacement from XBASE of the vector of variables for the current calculation of F. */ /* ------------------------------------------------------------------------ */
/*page*/ /* ------------------------------------------------------------------------ */ /* XPT will contain the interpolation point coordinates relative to XBASE. * FVAL will hold the values of F at the interpolation points. * GQ will hold the gradient of the quadratic model at XBASE. * HQ will hold the explicit second derivatives of the quadratic model. * PQ will contain the parameters of the implicit second derivatives of the quadratic model. * BMAT will hold the last N columns of H. * ZMAT will hold the factorization of the leading NPT by NPT submatrix * of H, this factorization being ZMAT times Diag(DZ) times ZMAT^T, * where the elements of DZ are plus or minus 1.0, asspecified by IDZ. * NDIM is the first dimension of BMAT and has the value NPT+N. * D is reserved for trial steps from XOPT. * VLAG will contain the values of the Lagrange functions at a new point X. * They are part of a product that requires VLAG to be of length NDIM. */ int i,j,k,ih,nf,nh,nfm,idz,ipt,jpt,nfmm,knew,kopt,nptm,ksave,nfsav,itemp ; int ktemp,itest,npt=2*n+1 ; double f,dx,dsq,rho,sum,fbeg,diff,beta,gisq,temp,suma,sumb,fopt,bsum,gqsq ; double xipt,xjpt,diffa,diffb,diffc,hdiag,alpha,delta,recip,reciq,fsave ; double dnorm,ratio,dstep,vquad,rhosq,detrat,crvmin,distsq,xoptsq,qtemp ; double *xbase=vector(n),*xopt=vector(n),*xnew=vector(n),*fval=vector(npt) ; double *gq=vector(n),*hq=vector((n*(n+1))/2),*pq=vector(npt),*d=vector(n) ; double *vlag=vector(npt+n),*w=vector(2*npt) ; double **z=matrix(npt-(n+1),npt),**b=matrix(n,npt+n),**xp=matrix(n,npt) ; nh = (n*(n+1)) / 2 ; nptm = npt - (n+1) ; if(maxfun<1) maxfun = 1 ; for(j=0;j<n;j++) xbase[j] = x[j] ; /* Begin the initialization procedure. NF becomes 1 more than the * number of function values so far. The coordinates of the * displacement of the next initial interpolation point from XBASE * are set in XPT(NF,.). */ rhosq = rhobeg * rhobeg ; recip = 1 / rhosq ; reciq = sqrt(0.5) / rhosq ; /* ------------------------------------------------------------------------ */
/*page*/ /* ------------------------------------------------------------------------ */ for(nf=0;nf<npt;nf++) { nfm = nf-1 ; nfmm = nfm-n ; if(nfm<2*n) // always happens when npt = 2*n+1 { if(nfm>=0&&nfm<n) xp[nfm][nf] = rhobeg ; else if(nfm>=n) xp[nfmm][nf] = -rhobeg ; } else // the case which never happens { itemp = nfmm / n ; jpt = nfm - itemp * n - n ; ipt = jpt + itemp ; if(ipt>=n) { ipt -= n ; swap(ipt,jpt) ; } xipt = rhobeg ; if(fval[ipt+n+1] < fval[ipt+1]) xipt = -xipt ; xjpt = rhobeg ; if(fval[jpt+n+1] < fval[jpt+1]) xjpt = -xjpt ; xp[ipt][nf] = xipt ; xp[jpt][nf] = xjpt ; } /* Calculate the next value of F. The least function value so far and its * index are required. */ for(j=0;j<n;j++) x[j] = xp[j][nf] + xbase[j]; fval[nf] = f = func(x) ; if(nf==0) { fbeg = fopt = f ; kopt = 0 ; } else if(f<fopt) { fopt = f ; kopt = nf ; } /* Set the non0 initial elements of BMAT and the quadratic model * in the cases when NF is at most 2*N+1. */ if(nfm<2*n) { if(nfm>=0&&nfm<n) { gq[nfm] = (f-fbeg) / rhobeg ; if(npt<=nf+n) { b[nfm][0] = -1/rhobeg ; b[nfm][nf] = 1/rhobeg ; b[nfm][npt+nfm-1] = -rhosq/2 ; } } else if(nfm>=n) { b[nfmm][nf-n] = 1/(2*rhobeg) ; b[nfmm][nf] = -1/(2*rhobeg) ; z[nfmm][0] = -(reciq+reciq) ; z[nfmm][nf] = z[nfmm][nf-n] = reciq ; ih = (nfmm * (nfmm+3)) / 2 ; temp = (fbeg-f) / rhobeg ; hq[ih] = (gq[nfmm]-temp) / rhobeg; gq[nfmm] = .5 * (gq[nfmm] + temp); } } /* Set the off-diagonal second derivatives of the Lagrange * functions and the initial quadratic model. */ else { ih = (ipt*(ipt+1))/2 + jpt ; if(xipt<0) ipt += n ; if(xjpt<0) jpt += n ; z[nfmm][0] = z[nfmm][nf] = recip ; z[nfmm][ipt] = z[nfmm][jpt] = -recip ; hq[ih] = (fbeg - fval[ipt+1] - fval[jpt+1] + f) / (xipt*xjpt) ; } } /* ------------------------------------------------------------------------ */
/*page*/ /* - Begin the iterative procedure, because the initial model is complete - */ diffa = diffb = itest = idz = 0 ; for(i=0;i<n;i++) xopt[i] = xp[i][kopt] ; /* Generate the next trust region step and test its length. Set KNEW * to -1 ifthe purpose of the next F will be to improve the * model. */ /* ------------------------------------------------------------------------ */ for(knew=-1,delta=rho=rhobeg,nfsav=nf;;) { if(knew<0) for(;;) { crvmin = trsapp(n,npt,xopt,xp,gq,hq,pq,delta*delta,d) ; for(dsq=i=0;i<n;i++) dsq += d[i]*d[i] ; dnorm = fmin(delta,sqrt(dsq)) ; if(dnorm>=rho/2) break ; // knew must be -1 delta /= 10 ; ratio = -1 ; if(delta<=rho*1.5) delta = rho ; temp = crvmin * .125 * rho * rho ; if(nf<=nfsav+2||temp<=diffa||temp<=diffb||temp<=diffc) { distsq = setknewdistsq(n,npt,delta,&knew,xopt,xp) ; /* If KNEW is positive, then set DSTEP, and branch back for the next * iteration, which will generate a "model step". */ if(knew>=0) { dstep = fmax(rho,fmin(sqrt(distsq)/10,delta/2)) ; dsq = dstep*dstep ; break ; } else if(delta>rho||dnorm>rho) continue ; } /* Return from the calculation, after another Newton-Raphson step, * if it is too short to have been tried before. */ if(rho<=rhoend) { for(i=0;i<n;i++) { xnew[i] = xopt[i] + d[i] ; x[i] = xbase[i] + xnew[i] ; } f = func(x) ; knew = -2 ; break ; } /* The calculations with the current value of RHO are complete. Pick * the next values of RHO and DELTA. */ ratio = setrhodelta(&rho,rhoend,&delta) ; nfsav = nf ; } if(knew==-2) { cond = 0 ; break ; } /* ---------------------------------------------------------------------- */
/*page*/ /* ---------------------------------------------------------------------- */ /* Shift XBASE if XOPT may be too far from XBASE. First make the * changes to BMAT that do not depend on ZMAT. */ for(xoptsq=i=0;i<n;i++) xoptsq += xopt[i]*xopt[i] ; if(dsq<=xoptsq/1000) { shiftxbase(n,npt,xp,xopt,vlag,b,z,pq,gq,hq,idz) ; for(j=0;j<n;j++) { xbase[j] += xopt[j] ; xopt[j] = 0 ; } } /* Pick the model step ifKNEW is positive. A different choice of D * may be made later, ifthe choice of D by BIGLAG causes * substantial cancellation in DENOM. */ if(knew>=0) alpha = biglag(n,npt,xopt,xp,b,z,idz,knew,dstep,d) ; /* Calculate VLAG and BETA for the current choice of D. The first * NPT components of W_check will be held in W. */ for(k=0;k<npt;k++) { for(vlag[k]=suma=sumb=j=0;j<n;j++) { suma += xp[j][k] * d[j] ; sumb += xp[j][k] * xopt[j] ; vlag[k] += b[j][k] * d[j] ; } w[k] = suma * (suma/2+sumb) ; } /* ---------------------------------------------------------------------- */
/*page*/ /* ---------------------------------------------------------------------- */ for(beta=k=0;k<nptm;k++) { for(sum=i=0;i<npt;i++) sum += z[k][i] * w[i] ; if(k<idz) { beta += sum*sum ; sum = -sum ; } else beta -= sum*sum ; for(i=0;i<npt;i++) vlag[i] += sum * z[k][i]; } for(xoptsq=bsum=dx=j=0;j<n;j++) { for(sum=i=0;i<npt;i++) sum += w[i] * b[j][i]; bsum += sum * d[j] ; for(k=0;k<n;k++) sum += b[k][npt+j] * d[k]; vlag[npt+j] = sum ; bsum += sum * d[j] ; dx += d[j] * xopt[j] ; xoptsq += xopt[j] * xopt[j] ; } // more numerical instability here? beta = dx*dx + dsq*((xoptsq+dx) + dx + dsq/2) + beta - bsum ; vlag[kopt] += 1 ; /* If KNEW is positive and if the cancellation in DENOM is unacceptable, * then BIGDEN calculates an alternative model step. */ if(knew>=0&&fabs(1 + alpha*beta / (vlag[knew]*vlag[knew]))<=0.8) beta = bigden(n,npt,xopt,xp,b,z,idz,kopt,knew,d,w,vlag) ; /* Calculate the next value of the objective function. */ if(nf+1>=maxfun) { cond = 2 ; break ; } for(i=0;i<n;i++) { xnew[i] = xopt[i] + d[i] ; x[i] = xbase[i] + xnew[i] ; } nf += 1 ; if(nf>=maxfun) { cond = 2 ; break ; } f = func(x) ; /* Use the quadratic model to predict the change in F due to the * step D, and set DIFF to the error of this prediction. */ for(vquad=ih=j=0;j<n;j++) { vquad += d[j] * gq[j] ; for(i=0;i<=j;ih++,i++) { temp = d[i]*xnew[j] + d[j]*xopt[i] ; if(i==j) temp /= 2 ; vquad += temp * hq[ih] ; } } for(k=0;k<npt;k++) vquad += pq[k] * w[k] ; diff = f - fopt - vquad ; diffc = diffb ; diffb = diffa ; diffa = fabs(diff) ; if(dnorm>rho) nfsav = nf ; /* Update FOPT and XOPT ifthe new F is the least value of the * objective function so far. The branch when KNEW is positive * occurs ifD is not a trust region step. */ fsave = fopt ; if(f<fopt) { fopt = f ; for(i=0;i<n;i++) xopt[i] = xnew[i] ; } ksave = knew ; /* ---------------------------------------------------------------------- */
/*page*/ /* ---------------------------------------------------------------------- */ if(knew<0) { /* Pick the next value of DELTA after a trust region step. */ if(vquad>=0) { cond = 1 ; break ; } ratio = (f-fsave) / vquad ; if(ratio<=0.1) delta = dnorm/2 ; else if(ratio<=.7) delta = fmax(delta/2,dnorm) ; else delta = fmax(delta/2,2*dnorm) ; if(delta<=rho*1.5) delta = rho; /* Set KNEW to the index of the next interpolation point to be * deleted. */ temp = fmax(delta/10, rho); rhosq = temp * temp ; if(f>=fsave) { ktemp = kopt ; detrat = 1 ; } else { ktemp = -1 ; detrat = 0 ; } for(k=0;k<npt;k++) { for(hdiag=j=0;j<nptm;j++) if(j<idz) hdiag -= z[j][k]*z[j][k] ; else hdiag += z[j][k]*z[j][k] ; temp = fabs(beta*hdiag + vlag[k]*vlag[k]); for(distsq=j=0;j<n;j++) { qtemp = xp[j][k] - xopt[j] ; distsq += qtemp*qtemp ; } if(distsq>rhosq) { qtemp = distsq / rhosq ; temp *= qtemp * qtemp * qtemp ; } if(temp>detrat&&k!=ktemp) { detrat = temp ; knew = k ; } } } if(knew>=0) { /* Update BMAT, ZMAT and IDZ, so that the KNEW-th interpolation * point can be moved. Begin the updating of the quadratic model, * starting with the explicit second derivative term. */ idz = update(n,npt,b,z,idz,vlag,beta,knew) ; fval[knew] = f ; for(ih=i=0;i<n;i++) { temp = pq[knew] * xp[i][knew] ; for(j=0;j<=i;ih++,j++) hq[ih] += temp * xp[j][knew] ; } pq[knew] = 0 ; /* Update the other second derivative parameters, and then the * gradient vector of the model. Also include the new interpolation * point. */ for(j=0;j<nptm;j++) { temp = diff * z[j][knew] ; if(j<idz) temp = -temp ; for(k=0;k<npt;k++) pq[k] += temp * z[j][k] ; } for(gqsq=i=0;i<n;i++) { gq[i] += diff * b[i][knew]; gqsq += gq[i] * gq[i]; xp[i][knew] = xnew[i]; } /* If a trust region step makes a small change to the objective * function, then calculate the gradient of the least Frobenius norm * interpolant at XBASE, and store it in W, using VLAG for a vector * of right hand sides. */ if(ksave==-1&&delta==rho&&fabs(ratio)>.01) itest = 0 ; else if(ksave==-1&&delta==rho) { for(k=0;k<npt;k++) vlag[k] = fval[k] - fval[kopt] ; for(gisq=i=0;i<n;i++) { for(w[i]=k=0;k<npt;k++) w[i] += b[i][k] * vlag[k] ; gisq += w[i]*w[i] ; } itest += 1 ; if(gqsq<gisq*100) itest = 0 ; if(itest>=3) { for(i=0;i<n;i++) gq[i] = w[i] ; for(ih=0;ih<nh;ih++) hq[ih] = 0 ; for(j=0;j<nptm;j++) { for(w[j]=k=0;k<npt;k++) w[j] += vlag[k] * z[j][k] ; if(j<idz) w[j] = -w[j] ; } for(k=0;k<npt;k++) for(pq[k]=j=0;j<nptm;j++) pq[k] += z[j][k] * w[j]; itest = 0 ; } } if(f<fsave) kopt = knew ; knew = -1 ; /* If a trust region step has provided a sufficient decrease in F, * then branch for another trust region calculation. The case * KSAVE>0 occurs when the new function value was calculated by a * model step. */ if(f<=fsave+vquad/10||ksave>=0) continue ; /* Alternatively, find out ifthe interpolation points are close * enough to the best point so far. */ } distsq = setknewdistsq(n,npt,delta,&knew,xopt,xp) ; /* If KNEW is positive, then set DSTEP, and branch back for the next * iteration, which will generate a "model step". */ if(knew>=0) { dstep = fmax(rho,fmin(sqrt(distsq)/10,delta/2)) ; dsq = dstep*dstep ; } else if(ratio<=0&&delta<=rho&&dnorm<=rho) /* The calculations with the current value of RHO are complete. Pick * the next values of RHO and DELTA. */ { if(rho<=rhoend) { cond = 0 ; break ; } ratio = setrhodelta(&rho,rhoend,&delta) ; nfsav = nf ; } } /* Return from the calculation, after another Newton-Raphson step, * if it is too short to have been tried before. */ if(fopt<=f) { for(i=0;i<n;i++) x[i] = xbase[i] + xopt[i] ; f = fopt ; } free(xbase,xopt,xnew,w,fval,gq) ; free(hq,pq,d,vlag) ; freematrix(z,b,xp) ; return f ; } /* -------------------------------------------------------------------------- */ static double(*f)(double*) ; static double myfunc(double *x) { return -f(x) ; } double uoamax(double (*func)(double*),double *x,int n, double xinc,double tol,int max_iter) { f = func ; return -uoamin(myfunc,x,n,xinc,tol,max_iter) ; }

Archived from optim.html

#include"memory.h" #include <math.h> #define max(a,b) ((a)>(b)?(a):(b)) #define min(a,b) ((a)<(b)?(a):(b)) int update(int n,int npt,double **b,double **z,int idz,double *vlag, double beta,int knew) { /* The arrays BMAT and ZMAT with IDZ are updated, in order to shift * the interpolation point that has index KNEW. On entry, VLAG * contains the components of the vector Theta*Wcheck+e_b of the * updating formula (6.11), and BETA holds the value of the * parameter that has this name. */ int nptm=npt-n-1,i,j,ja,jb,jl,jp,iflag ; double tau,temp,scala,scalb,alpha,denom,tempa,tempb,tausq,sqrtdn ; double *w=vector(npt+n) ; /* Apply the rotations that put zeros in the KNEW-th row of ZMAT. */ for(jl=0,j=1;j<npt-n-1;j++) { if(j==idz) jl = j ; else if(z[j][knew]!=0) { tempa = z[jl][knew]; tempb = z[j][knew]; temp = sqrt(tempa*tempa+tempb*tempb) ; tempa /= temp ; tempb /= temp ; for(i=0;i<npt;i++) { temp = tempa*z[jl][i] + tempb*z[j][i] ; z[j][i] = tempa*z[j][i] - tempb*z[jl][i] ; z[jl][i] = temp ; } z[j][knew] = 0; } } /* Put the first NPT components of the KNEW-th column of HLAG into * W, and calculate the parameters of the updating formula. */ if(idz<1) tempa = z[0][knew] ; else tempa = -z[0][knew] ; for(i=0;i<npt;i++) w[i] = tempa * z[0][i] ; if(jl>0) for(tempb=z[jl][knew],i=0;i<npt;i++) w[i] += tempb * z[jl][i] ; alpha = w[knew] ; tau = vlag[knew] ; tausq = tau * tau ; denom = alpha*beta + tausq ; vlag[knew] -= 1 ; /* Complete the updating of ZMAT when there is only 1.0 nonzero * element in the KNEW-th row of the new matrix ZMAT, but, ifIFLAG * is set to 1.0, then the first column of ZMAT will be exchanged * with another 1.0 later. */ iflag = 0 ; sqrtdn = sqrt(fabs(denom)) ; /* ------------------------------------------------------------------------ */ / /* ------------------------------------------------------------------------ */ if(jl==0) { tempa = tau / sqrtdn ; tempb = z[0][knew] / sqrtdn ; for(i=0;i<npt;i++) z[0][i] = tempa*z[0][i] - tempb*vlag[i] ; if(denom<0) { if(idz==0) idz = 1 ; else iflag = 1 ; } } else { /* Complete the updating of ZMAT in the alternative case. */ if(beta>=0) ja = jl ; else ja = 0 ; jb = jl - ja ; temp = z[jb][knew] / denom ; tempa = temp * beta ; tempb = temp * tau ; temp = z[ja][knew] ; scala = 1 / sqrt(fabs(beta)*temp*temp+tausq) ; scalb = scala * sqrtdn ; for(i=0;i<npt;i++) { z[ja][i] = scala * (tau * z[ja][i] - temp * vlag[i]) ; z[jb][i] = scalb * (z[jb][i] - tempa * w[i] - tempb * vlag[i]) ; } if(denom<=0) { if(beta<0) idz += 1 ; else iflag = 1 ; } } /* IDZ is reduced in the following case, and usually the first * column of ZMAT is exchanged with a later 1.0. */ if(iflag==1) for(idz-=1,i=0;i<npt;i++) swap(z[0][i],z[idz][i]) ; /* Finally, update the matrix BMAT. */ for(j=0;j<n;j++) { jp = npt + j ; w[jp] = b[j][knew] ; tempa = (alpha*vlag[jp] - tau*w[jp]) / denom ; tempb = -(beta*w[jp] + tau*vlag[jp]) / denom ; for(i=0;i<=jp;i++) { b[j][i] = b[j][i] + tempa*vlag[i] + tempb*w[i] ; // !! ** !! if(i>=npt) b[i-npt][jp] = b[j][i]; } } free(w) ; return idz ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------------------------------------------------- */ void shiftxbase(int n,int npt,double **xp,double *xopt,double *vlag,double **b, double **z,double *pq,double *gq,double *hq,int idz) { int i,j,k,ih ; double tempq,temp,sum,xoptsq,sumz,*w=vector(2*npt) ; for(xoptsq=i=0;i<n;i++) xoptsq += xopt[i] * xopt[i] ; tempq = xoptsq/4 ; for(k=0;k<npt;k++) { for(sum=i=0;i<n;i++) sum += xp[i][k] * xopt[i]; temp = pq[k] * sum ; sum -= xoptsq/2 ; w[npt+k] = sum ; for(i=0;i<n;i++) { if(gq) gq[i] += temp * xp[i][k] ; xp[i][k] -= xopt[i]/2 ; vlag[i] = b[i][k]; w[i] = sum*xp[i][k] + tempq*xopt[i] ; for(j=0;j<=i;j++) // !! ** !! b[j][npt+i] = b[j][npt+i] + vlag[i]*w[j] + w[i]*vlag[j] ; } } /* Then the revisions of BMAT that depend on ZMAT are calculated. */ for(k=0;k<npt-n-1;k++) { for(sumz=i=0;i<npt;i++) { sumz += z[k][i] ; w[i] = w[npt+i] * z[k][i] ; } for(j=0;j<n;j++) { sum = tempq * sumz * xopt[j]; for(i=0;i<npt;i++) sum += w[i] * xp[j][i]; vlag[j] = sum; if(k<idz) sum = -sum; for(i=0;i<npt;i++) b[j][i] += sum * z[k][i]; } for(i=0;i<n;i++) { if(k>=idz) temp = vlag[i] ; else temp = -vlag[i] ; for(j=0;j<=i;j++) b[j][npt+i] += temp * vlag[j] ; } } /* The following instructions complete the shift of XBASE, * including the changes to the parameters of the quadratic * model. */ for(ih=j=0;j<n;j++) { for(w[j]=k=0;k<npt;k++) { w[j] += pq[k] * xp[j][k] ; xp[j][k] -= xopt[j]/2 ; } for(i=0;i<=j;ih++,i++) { if(gq) { if(i<j) gq[j] += hq[ih] * xopt[i] ; gq[i] += hq[ih] * xopt[j] ; } hq[ih] = hq[ih] + w[i]*xopt[j] + xopt[i]*w[j] ; b[j][npt+i] = b[i][npt+j] ; } } free(w) ; }

Archived from optim.html

/*
  This is NEWUOA for unconstrained minimization. The codes were written
  by Powell in Fortran and then translated to C with f2c. I further
  modified the code to make it independent of libf2c and f2c.h. Please
  refer to "The NEWUOA software for unconstrained optimization without
  derivatives", which is available at www.damtp.cam.ac.uk, for more
  information.
 */
/*
  The original fortran codes are distributed without restrictions. The
  C++ codes are distributed under MIT license.
 */
/* The MIT License

   Copyright (c) 2004, by M.J.D. Powell <mjdp@cam.ac.uk>
                 2008, by Attractive Chaos <attractivechaos@aol.co.uk>
                 2015, by Colin Champion <colin.champion@masterlyinactivity.com>

   Permission is hereby granted, free of charge, to any person obtaining
   a copy of this software and associated documentation files (the
   "Software"), to deal in the Software without restriction, including
   without limitation the rights to use, copy, modify, merge, publish,
   distribute, sublicense, and/or sell copies of the Software, and to
   permit persons to whom the Software is furnished to do so, subject to
   the following conditions:

   The above copyright notice and this permission notice shall be
   included in all copies or substantial portions of the Software.

   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
   MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
   NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
   BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
   ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
   CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
   SOFTWARE.
*/

#include "memory.h"
#include <math.h>
#define pi 3.141592653589793
static int cond = -1 ; 
int uoacond() { return cond ; } 

/* -------------------------------------------------------------------------- */

static double biglag(int n,int npt,double *xopt,double **xp,double **b,
                     double **z,int idz,int knew,double delta,double *d)
{ /* N is the number of variables. NPT is the number of interpolation
   * equations. XOPT is the best interpolation point so far. XPT
   * contains the coordinates of the current interpolation
   * points. BMAT provides the last N columns of H.  ZMAT and IDZ give
   * a factorization of the first NPT by NPT submatrix of H. NDIM is
   * the first dimension of BMAT and has the value NPT+N.  KNEW is the
   * index of the interpolation point that is going to be moved. DELTA
   * is the current trust region bound. D will be set to the step from
   * XOPT to the new point. ALPHA will be set to the KNEW-th diagonal
   * element of the H matrix. HCOL, GC, GD, S and W will be used for
   * working space. */

  /* The step D is calculated in a way that attempts to maximize the
   * modulus of LFUNC(XOPT+D), subject to the bound ||D|| <= DELTA,
   * where LFUNC is the KNEW-th Lagrange function. */

  int i,j,k,iterc,isave ;
  double sp,ss,cf1,cf2,cf3,cf4,cf5,dhd,cth,tau,sth,sum,temp,step,angle,scale ;
  double denom,delsq,tempa,tempb,taubeg,tauold,taumax,dd,gg,alpha ;
  double *hcol=vector(npt),*w=vector(n),*s=vector(n),*gd=vector(n) ; 
  double *gc=vector(n) ; 

  delsq = delta * delta ;

  /* Set the first NPT components of HCOL to the leading elements of
   * the KNEW-th column of H. */
  for(k=0;k<npt;k++) hcol[k] = 0 ;
  for(j=0;j<npt-n-1;j++) 
  { temp = z[knew][j] ;
    if(j<idz) temp = -temp ;
    for(k=0;k<npt;k++) hcol[k] += temp * z[k][j] ;
  }
  alpha = hcol[knew] ;

  /* Set the unscaled initial direction D. Form the gradient of LFUNC
   * atXOPT, and multiply D by the second derivative matrix of LFUNC. */
  for(dd=i=0;i<n;i++) 
  { d[i] = xp[knew][i] - xopt[i] ; gc[i] = b[knew][i] ; dd += d[i]*d[i] ; }

  for(k=0;k<npt;k++) 
  { for(temp=sum=j=0;j<n;j++) 
    { temp += xp[k][j] * xopt[j] ; sum += xp[k][j] * d[j] ; }
    temp *= hcol[k] ;
    sum *= hcol[k] ;
    for(i=0;i<n;i++) 
    { gc[i] += temp * xp[k][i] ; gd[i] += sum * xp[k][i] ; }
  }

  /* Scale D and GD, with a sign change if required. Set S to another
   * vector in the initial two dimensional subspace. */
  for(gg=sp=dhd=i=0;i<n;i++) 
  { gg += gc[i] * gc[i] ; sp += d[i] * gc[i] ; dhd += d[i] * gd[i] ; }

  scale = delta / sqrt(dd) ;
  if(sp*dhd<0) scale = -scale ;
  if(sp*sp>dd*.99*gg) temp = 1 ; else temp = 0 ; 
  tau = scale * (fabs(sp)+0.5*scale*fabs(dhd)) ;
  if(gg*delsq<tau*.01*tau) temp = 1 ;

  for(i=0;i<n;i++) 
  { d[i] *= scale ; gd[i] *= scale ; s[i] = gc[i] + temp*gd[i] ; }

  /* Begin the iteration by overwriting S with a vector that has the
   * required length and direction, except that termination occurs if
   * the given D and S are nearly parallel. */
  for(iterc=0;iterc<n;iterc++) 
  { for(dd=sp=ss=i=0;i<n;i++) 
    { dd += d[i] * d[i] ; sp += d[i] * s[i] ; ss += s[i] * s[i] ; }
    temp = dd*ss - sp*sp ;
    if(temp<=dd*1e-8*ss) break ;
    denom = sqrt(temp);
    for(i=0;i<n;i++) { s[i] = (dd*s[i]-sp*d[i]) / denom ; w[i] = 0 ; }

    /* Calculate the coefficients of the objective function on the
     * circle, beginning with the multiplication of S by the second
     * derivative matrix. */
    for(k=0;k<npt;k++) 
    { for(sum=j=0;j<n;j++) sum += xp[k][j] * s[j] ;
      sum *= hcol[k] ; 
      for(j=0;j<n;j++) w[j] += sum * xp[k][j] ;
    }

    for(cf1=cf2=cf3=cf4=cf5=i=0;i<n;i++) 
    { cf1 += s[i] * w[i];
      cf2 += d[i] * gc[i];
      cf3 += s[i] * gc[i];
      cf4 += d[i] * gd[i];
      cf5 += s[i] * gd[i];
    }

    cf1 /= 2 ;
    cf4 = cf4/2 - cf1 ;
    /* Seek the value of the angle that maximizes the modulus of TAU. */
    taubeg = cf1 + cf2 + cf4 ;
    taumax = tauold = taubeg;
    temp = 2 * pi / 50.0 ;

    for(isave=0,i=1;i<50;tauold=tau,i++) 
    { angle = (double) i*temp;
      cth = cos(angle) ;
      sth = sin(angle) ;
      tau = cf1 + (cf2 + cf4*cth) * cth + (cf3 + cf5*cth) * sth ;
      if(fabs(tau)>fabs(taumax)) 
      { taumax = tau ; isave = i ; tempa = tauold ; } 
      else if(i==isave+1) tempb = tau ;
    }

    if(isave==0) tempa = tau ; else if(isave==49) tempb = taubeg ;
    step = 0 ;
    if(tempa!=tempb) 
    { tempa -= taumax ;
      tempb -= taumax ;
      step = 0.5 * (tempa-tempb) / (tempa+tempb) ;
    }
    angle = temp * (isave+step) ;
    /* Calculate the new D and GD. Then test for convergence. */
    cth = cos(angle) ;
    sth = sin(angle) ;
    tau = cf1 + (cf2 + cf4*cth) * cth + (cf3 + cf5*cth) * sth ;

    for(i=0;i<n;i++) 
    { d[i] = cth*d[i] + sth*s[i] ;
      gd[i] = cth*gd[i] + sth*w[i] ;
      s[i] = gc[i] + gd[i] ;
    }
    if(fabs(tau)<=fabs(taubeg)*1.1) break ;
  }
  free(hcol,w,s,gd,gc) ; 
  return alpha ; 
}
/* -------------------------------------------------------------------------- */
/*page*/
/* -------------------------------------------------------------------------- */

static double bigden(int n,int npt,double *xopt,double **xp,double **b, 
                     double **z,int idz,int kopt,int knew, double *d,double *w,
                     double *vlag)
{ /* N is the number of variables.
   * NPT is the number of interpolation equations.
   * XOPT is the best interpolation point so far.
   * XPT contains the coordinates of the current interpolation points.
   * BMAT provides the last N columns of H.
   * ZMAT and IDZ give a factorization of the first NPT by NPT
     submatrix of H.
   * NDIMis the first dimension of BMAT and has the value NPT+N.
   * KOPT is the index of the optimal interpolation point.
   * KNEW is the index of the interpolation point that is going to be
     moved.
   * D will be set to the step from XOPT to the new point, and on entry it
   * should be the D that was calculated by the last call of BIGLAG. 
     The length of the initial D provides a trust region bound on the final D.
   * W will be set to Wcheck for the final choice of D.
   * VLAG will be set to Theta*Wcheck+e_b for the final choice of D.
   * D is calculated in a way that should provide a denominator with a
     large modulus in the updating formula when the KNEW-th
     interpolation point is shifted to the new position XOPT+D. 
   * the RETURN value beta will be found in the updating formula when
     the KNEW-th interpolation point is moved to its new position.
*/
  int i,j,k,isave,iterc,jc,nw,ksav,ndim=npt+n ;
  double dd,ds,ss,den[9],par[9],tau,sum,diff,temp,step,beta,alpha,angle ;
  double denex[9],tempa,tempb,tempc,ssden,dtest,xoptd,xopts,denold,denmax ; 
  double densav,dstemp,sumold,sstemp,xoptsq ;
  double **prod=matrix(ndim,5),**wvec=matrix(ndim,5),*s=vector(n) ;

  /* Store the first NPT elements of the KNEW-th column of H in W(N+1)
   * to W(N+NPT). */
  for(k=0;k<npt;k++) w[n+k] = 0 ; 
  for(j=0;j<npt-n-1;j++) 
  { temp = z[knew][j] ;
    if(j<idz) temp = -temp ;
    for(k=0;k<npt;k++) w[n+k] += temp * z[k][j] ;
  }
  alpha = w[n+knew] ;
  /* The initial search direction D is taken from the last call of
   * BIGBDLAG, and the initial S is set below, usually to the direction
   * from X_OPT to X_KNEW, but a different direction to an
   * interpolation point may be chosen, in order to prevent S from
   * being nearly parallel to D. */
  for(dd=ds=ss=xoptsq=i=0;i<n;i++)
  { dd += d[i]*d[i] ; 
    s[i] = xp[knew][i] - xopt[i] ;
    ds += d[i] * s[i] ;
    ss += s[i] * s[i] ;
    xoptsq += xopt[i] * xopt[i];
  }

  if(ds*ds>dd*.99*ss) 
  { ksav = knew ;
    dtest = ds * ds / ss ;
    for(k=0;k<npt;k++) if(k!=kopt)
    { for(dstemp=sstemp=i=0;i<n;i++) 
      { diff = xp[k][i] - xopt[i] ;
        dstemp += d[i] * diff ; 
        sstemp += diff*diff ; 
      }
      if(dstemp*dstemp/sstemp<dtest) 
      { ksav = k ;
        dtest = dstemp * dstemp / sstemp ;
        ds = dstemp ;
        ss = sstemp ;
      }
    }
    for(i=0;i<n;i++) s[i] = xp[ksav][i] - xopt[i] ;
  }

  ssden = dd*ss - ds*ds ;
  densav = 0 ;
  /* Begin the iteration by overwriting S with a vector that has the
   * required length and direction. */

  /* ------------------------------------------------------------------------ */

  for(iterc=1;;iterc++)
  { temp = 1.0 / sqrt(ssden) ;
    for(xoptd=xopts=i=0;i<n;i++) 
    { s[i] = temp * (dd*s[i]-ds*d[i]) ;
      xoptd += xopt[i] * d[i] ;
      xopts += xopt[i] * s[i] ;
    }
    /* Set the coefficients of the first 2 terms of BETA. */
    tempa = 0.5 * xoptd * xoptd ;
    tempb = 0.5 * xopts * xopts ;
    den[0] = dd * (xoptsq + 0.5*dd) + tempa + tempb ;
    den[1] = 2 * xoptd * dd ;
    den[2] = 2 * xopts * dd ;
    den[3] = tempa - tempb ;
    den[4] = xoptd * xopts ;
    for(i=4;i<8;i++) den[i+1] = 0 ; 

    /* Put the coefficients of Wcheck in WVEC. */
    for(k=0;k<npt;k++)
    { for(tempa=tempb=tempc=i=0;i<n;i++)
      { tempa += xp[k][i] * d[i];
        tempb += xp[k][i] * s[i];
        tempc += xp[k][i] * xopt[i];
      }
      wvec[k][0] = 0.25 * (tempa*tempa + tempb*tempb) ;
      wvec[k][1] = tempa * tempc ;
      wvec[k][2] = tempb * tempc ;
      wvec[k][3] = 0.25 * (tempa*tempa - tempb*tempb) ;
      wvec[k][4] = 0.5 * tempa * tempb ;
    }
    for(i=0;i<n;i++)
    { wvec[i+npt][0] = 0 ;
      wvec[i+npt][1] = d[i] ;
      wvec[i+npt][2] = s[i] ;
      wvec[i+npt][3] = 0 ;
      wvec[i+npt][4] = 0 ;
    }

    /* Put the coefficents of THETA*Wcheck in PROD. */
    for(jc=0;jc<5;jc++)
    { if(jc==2||jc==3) nw = ndim ; else nw = npt ; 
      for(k=0;k<npt;k++) prod[k][jc] = 0 ;
      for(j=0;j<npt-n-1;j++) 
      { for(sum=k=0;k<npt;k++) sum += z[k][j] * wvec[k][jc] ;
        if(j<idz) sum = -sum ;
        for(k=0;k<npt;k++) prod[k][jc] += sum * z[k][j] ;
      }
      if(nw==ndim) for(k=0;k<npt;k++) 
      { for(sum=j=0;j<n;j++) sum += b[k][j] * wvec[npt+j][jc] ;
        prod[k][jc] += sum ;
      }
      for(j=0;j<n;j++) 
      { for(sum=i=0;i<nw;i++) sum += b[i][j] * wvec[i][jc] ;
        prod[npt+j][jc] = sum ;
      }
    }

    /* Include in DEN the part of BETA that depends on THETA. */
    for(k=0;k<ndim;k++)
    { for(sum=i=0;i<5;i++) sum += ( par[i] = 0.5 * prod[k][i] * wvec[k][i] ) ;
      den[0] -= par[0] + sum ;

      tempa = prod[k][0] * wvec[k][1] + prod[k][1] * wvec[k][0] ;
      tempb = prod[k][1] * wvec[k][3] + prod[k][3] * wvec[k][1] ;
      tempc = prod[k][2] * wvec[k][4] + prod[k][4] * wvec[k][2] ;
      den[1] -= tempa + 0.5 * (tempb+tempc) ;
      den[5] -= 0.5 * (tempb-tempc) ;

      tempa = prod[k][0] * wvec[k][2] + prod[k][2] * wvec[k][0] ;
      tempb = prod[k][1] * wvec[k][4] + prod[k][4] * wvec[k][1] ;
      tempc = prod[k][2] * wvec[k][3] + prod[k][3] * wvec[k][2] ;
      den[2] -= tempa + 0.5 * (tempb-tempc) ;
      den[6] -= 0.5 * (tempb+tempc) ;

      tempa = prod[k][0] * wvec[k][3] + prod[k][3] * wvec[k][0] ;
      den[3] -= tempa + par[1] - par[2] ;

      tempa = prod[k][0] * wvec[k][4] + prod[k][4] * wvec[k][0] ;
      tempb = prod[k][1] * wvec[k][2] + prod[k][2] * wvec[k][1] ;
      den[4] -= tempa + 0.5 * tempb ;
      den[7] -= par[3] - par[4] ;

      tempa = prod[k][3] * wvec[k][4] + prod[k][4] * wvec[k][3] ;
      den[8] -= 0.5 * tempa ;
    }

    /* Extend DEN so that it holds all the coefficients of DENOM. */
    for(sum=i=0;i<5;i++) 
    { tempa = prod[knew][i] ; sum += ( par[i] = 0.5 * tempa * tempa ) ; }
    denex[0] = alpha*den[0] + par[0] + sum ;

    tempa = 2 * prod[knew][0] * prod[knew][1] ;
    tempb = prod[knew][1] * prod[knew][3] ;
    tempc = prod[knew][2] * prod[knew][4] ;
    denex[1] = alpha*den[1] + tempa + tempb + tempc ;
    denex[5] = alpha*den[5] + tempb - tempc ;

    tempa = 2 * prod[knew][0] * prod[knew][2] ;
    tempb = prod[knew][1] * prod[knew][4] ;
    tempc = prod[knew][2] * prod[knew][3] ;
    denex[2] = alpha*den[2] + tempa + tempb - tempc ;
    denex[6] = alpha*den[6] + tempb + tempc ;

    tempa = 2 * prod[knew][0] * prod[knew][3] ;
    denex[3] = alpha*den[3] + tempa + par[1] - par[2] ;

    tempa = 2 * prod[knew][0] * prod[knew][4] ;
    denex[4] = alpha*den[4] + tempa + prod[knew][1]*prod[knew][2] ;
    denex[7] = alpha*den[7] + par[3] - par[4] ;
    denex[8] = alpha*den[8] + prod[knew][3]*prod[knew][4] ;

    /* Seek the value of the angle that maximizes the modulus of DENOM. */
    sum = denex[0] + denex[1] + denex[3] + denex[5] + denex[7];
    denold = denmax = sum ;
    temp = 2 * pi / 50 ;
    par[0] = 1 ;
    for(isave=0,i=1;i<50;i++)
    { angle = i * temp ;
      par[1] = cos(angle) ;
      par[2] = sin(angle) ;
      for(j=4;j<9;j+=2)
      { par[j-1] = par[1]*par[j-3] - par[2]*par[j-2] ;
        par[j] = par[1]*par[j-2] + par[2]*par[j-3] ;
      }
      sumold = sum ;
      for(sum=j=0;j<9;j++) sum += denex[j]*par[j] ;
      if(fabs(sum)>fabs(denmax)) { denmax = sum ; isave = i ; tempa = sumold ; }
      else if(i==isave+1) tempb = sum ;
    }

    if(isave==0) tempa = sum ; else if(isave==49) tempb = denold ;
    step = 0 ;
    if(tempa!=tempb) 
    { tempa -= denmax ;
      tempb -= denmax ;
      step = 0.5 * (tempa-tempb) / (tempa+tempb) ;
    }

    /* Calculate the new parameters of the denominator, the new VLAG
     * vector and the new D. Then test for convergence. */
    angle = temp * (isave+step) ;
    par[1] = cos(angle) ;
    par[2] = sin(angle) ;
    for(j=4;j<9;j+=2) 
    { par[j-1] = par[1]*par[j-3] - par[2]*par[j-2] ;
      par[j] = par[1]*par[j-2] + par[2]*par[j-3] ;
    }
    for(denmax=beta=j=0;j<9;j++)
    { beta += den[j]*par[j] ; denmax += denex[j]*par[j] ; } 

    for(k=0;k<ndim;k++) for(vlag[k]=j=0;j<5;j++)
      vlag[k] += prod[k][j] * par[j] ;

    tau = vlag[knew] ;
    for(dd=tempa=tempb=i=0;i<n;i++)
    { d[i] = par[1]*d[i] + par[2]*s[i] ;
      w[i] = xopt[i] + d[i] ;
      dd += d[i] * d[i] ;
      tempa += d[i] * w[i] ;
      tempb += w[i] * w[i] ;
    }

    if(iterc>=n) break ;
    if(iterc>1) densav = fmax(densav, denold) ;
    if(fabs(denmax)<=fabs(densav)*1.1) break ;
    densav = denmax ;

    /* Set S to 0.5 the gradient of the denominator with respect to
     * D. Then branch for the next iteration. */
    for(i=0;i<n;i++)
    { temp = tempa*xopt[i] + tempb*d[i] - vlag[npt+i] ;
      s[i] = tau*b[knew][i] + alpha*temp ;
    }

    for(k=0;k<npt;k++)
    { for(sum=j=0;j<n;j++) sum += xp[k][j] * w[j] ;
      temp = (tau*w[n+k]-alpha*vlag[k]) * sum ;
      for(i=0;i<n;i++) s[i] += temp * xp[k][i] ;
    }

    for(ss=ds=i=0;i<n;i++) { ss += s[i]*s[i] ; ds += d[i]*s[i] ; }
    ssden = dd*ss - ds*ds ;
    if(ssden<dd*1e-8*ss) break ;
  }

  /* Set the vector W before the RETURN from the subroutine. */
  for(k=0;k<ndim;k++) for(w[k]=j=0;j<5;j++) w[k] +=  wvec[k][j] * par[j] ;
  vlag[kopt] += 1 ;
  freematrix(prod,wvec) ; free(s) ; 
  return beta ;
}
/* -------------------------------------------------------------------------- */
/*page*/
/* -------------------------------------------------------------------------- */

/* The following instructions set the vector HD to the vector D multiplied 
   by the second derivative matrix of Q.  */

static void 
  sethd(double *hd,int n,double **xp,double *d,double *hq,double *pq,int npt)
{ int i,j,k,ih ; 
  double temp ; 

  for(i=0;i<n;i++) hd[i] = 0 ; 
  for(k=0;k<npt;k++) 
  { for(temp=j=0;j<n;j++) temp += xp[k][j] * d[j] ;
    temp *= pq[k] ; 
    for(i=0;i<n;i++) hd[i] += temp * xp[k][i] ;
  }
  for(ih=j=0;j<n;j++) for(i=0;i<=j;ih++,i++) 
  { if(i<j) hd[j] += hq[ih] * d[i] ; hd[i] += hq[ih]*d[j] ; }
}
/* -------------------------------------------------------------------------- */

static double trsapp(int n, int npt, double * xopt, double **xp, double * gq,  
                     double * hq, double * pq,double delsq, double * step)
{ /* The arguments NPT, XOPT, XPT, GQ, HQ and PQ have their usual meanings, 
   * in order to define the current quadratic model Q.
   * DELTA is the trust region radius, and has to be positive. STEP
   * will be set to the calculated trial step. CRVMIN will be returned as the
   * least curvature of H aint the conjugate directions that occur,
   * except that it is set to 0 ifSTEP goes all the way to the trust
   * region boundary. The calculation of STEP begins with the
   * truncated conjugate gradient method. If the boundary of the trust
   * region is reached, then further changes to STEP may be made, each
   * one being in the 2D space spanned by the current STEP and the
   * corresponding gradient of Q. Thus STEP should provide a
   * substantial reduction to Q within the trust region. */

  int i,j,iterc,isave,itermax ;
  double dd,cf,dg,gg,ds,sg,ss,dhd,dhs,cth,sgk,shs,sth,qadd,qbeg,qred,qmin ;
  double temp,qsav,qnew,ggbeg,alpha,angle,reduc,ggsav,tempa,tempb,bstep ;
  double ratio,angtest,crvmin ;
  double *d=vector(n),*g=vector(n),*hd=vector(n),*hs=vector(n) ; 

  itermax = n ;
  for(i=0;i<n;i++) d[i] = xopt[i] ; 
  sethd(hd,n,xp,d,hq,pq,npt) ;
  /* Prepare for the first line search. */
  for(qred=dd=i=0;i<n;i++)
  { step[i] = hs[i] = 0 ; 
    g[i] = gq[i] + hd[i] ;
    d[i] = -g[i] ; 
    dd += d[i]*d[i] ;
  }
  if(dd==0) { free(d,g,hd,hs) ; return 0 ; }
  ggbeg = gg = dd ;

  /* ------------------------------------------------------------------------ */
  /*page*/
  /* -- Calculate the step to the trust region boundary and the product HD -- */

  for(ds=ss=0,iterc=1;ss<delsq;iterc++)
  { temp = delsq - ss;
    bstep = temp / (ds + sqrt(ds * ds + dd * temp));
    sethd(hd,n,xp,d,hq,pq,npt) ;

    for(dhd=j=0;j<n;j++) dhd += d[j] * hd[j] ;
    /* Update CRVMIN and set the step-length ATRLPHA. */
    alpha = bstep ;
    if(dhd>0) 
    { if(iterc==1) crvmin = dhd/dd ; else crvmin = fmin(crvmin,dhd/dd) ;
      alpha = fmin(alpha,gg/dhd) ;
    }
    qred += ( qadd = alpha * (gg - 0.5*alpha*dhd) ) ;

    /* Update STEP and HS. */
    for(ggsav=gg,gg=i=0;i<n;i++)
    { step[i] += alpha * d[i] ; 
      hs[i] += alpha * hd[i] ; 
      temp = g[i] + hs[i] ; 
      gg += temp * temp ; 
    }

    /* Begin another conjugate direction iteration if required. */
    if(alpha>=bstep) break ; 
    ds = 0 ; 
    if(qadd>qred*.01&&gg>ggbeg*1e-4&&iterc<itermax) 
    { temp = gg / ggsav ;
      for(dd=ss=i=0;i<n;i++)
      { d[i] = temp*d[i] - g[i] - hs[i] ; 
        dd += d[i]*d[i] ; 
        ds += d[i]*step[i] ; 
        ss += step[i]*step[i] ;
      }
    }
    if(ds<=0) { free(d,g,hd,hs) ; return crvmin ; }
  }
  /* ------------------------------------------------------------------------ */
  /*page*/
  /* ------------ Test whether an alternative iteration is required --------- */

  for(iterc++;gg>ggbeg*1e-4;iterc++)
  { for(sg=shs=i=0;i<n;i++) { sg += step[i]*g[i] ; shs += step[i]*hs[i] ; } 
    sgk = sg + shs ;
    angtest = sgk / sqrt(gg*delsq) ;
    if(angtest<=-.99) break ; 
    /* Begin the alternative iteration by calculating D and HD and some
     * scalar products. */
    temp = sqrt(delsq*gg - sgk*sgk) ;
    tempa = delsq / temp ;
    tempb = sgk / temp ;
    for(i=0;i<n;i++) d[i] = tempa*(g[i]+hs[i]) - tempb*step[i] ; 
    sethd(hd,n,xp,d,hq,pq,npt) ;

    for(dg=dhd=dhs=i=0;i<n;i++)
    { dg += d[i]*g[i] ; dhd += hd[i]*d[i] ; dhs += hd[i]*step[i] ; }

    /* Seek the value of the angle that minimizes Q. */
    cf = 0.5 * (shs-dhd) ;
    qbeg = sg + cf ;
    qsav = qmin = qbeg ;
    temp = 2 * pi / 50 ;

    for(isave=0,i=1;i<50;i++)
    { angle = i * temp ;
      cth = cos(angle) ;
      sth = sin(angle) ;
      qnew = (sg+cf*cth)*cth + (dg+dhs*cth)*sth ;
      if(qnew<qmin) { qmin = qnew ; isave = i ; tempa = qsav ; }
      else if(i==isave+1) tempb = qnew ;
      qsav = qnew ;
    }
    if(isave==0) tempa = qnew ; else if(isave==49) tempb = qbeg ;
    angle = 0 ;
    if(tempa!=tempb) 
    { tempa -= qmin ; 
      tempb -= qmin ;
      angle = 0.5 * (tempa-tempb) / (tempa+tempb) ;
    }
    angle = temp * (isave+angle) ;

    /* Calculate the new STEP and HS. Then test for convergence. */
    cth = cos(angle) ;
    sth = sin(angle) ;
    reduc = qbeg - (sg + cf * cth) * cth - (dg + dhs * cth) * sth ;
    for(gg=i=0;i<n;i++)
    { step[i] = cth*step[i] + sth*d[i] ; 
      hs[i] = cth*hs[i] + sth*hd[i] ; 
      temp = g[i] + hs[i] ;
      gg += temp*temp ;
    }

    qred += reduc ;
    ratio = reduc / qred ;
    if(iterc>=itermax||ratio<=0.01) break ; 
  }
  free(d,g,hd,hs) ; 
  return 0 ; 
}
/* -------------------------------------------------------------------------- */
/*page*/
/* -------------------------------------------------------------------------- */

static int update(int n, int npt, double **b, double **z, int idz, 
                  double *vlag,double beta, int knew)
{
    /* The arrays BMAT and ZMAT with IDZ are updated, in order to shift
     * the interpolation point that has index KNEW. On entry, VLAG
     * contains the components of the vector Theta*Wcheck+e_b of the
     * updating formula (6.11), and BETA holds the value of the
     * parameter that has this name. */

  int i,j, ja, jb, jl, jp, iflag;
  double tau,temp,scala,scalb,alpha,denom,tempa,tempb,tausq,*w=vector(npt+n) ;

    /* Apply the rotations that put zeros in the KNEW-th row of ZMAT. */

  for(jl=0,j=1;j<npt-n-1;j++)
  { if(j==idz)  jl = j ; 
    else if(z[knew][j]!=0) 
    { tempa = z[knew][jl];
      tempb = z[knew][j];
      temp = sqrt(tempa*tempa+tempb*tempb) ;
      tempa /= temp ;
      tempb /= temp ;
      for(i=0;i<npt;i++)
      { temp = tempa*z[i][jl] + tempb*z[i][j] ;
        z[i][j] = tempa*z[i][j] - tempb*z[i][jl] ;
        z[i][jl] = temp ;
      }
      z[knew][j] = 0;
    }
  }

  /* Put the first NPT components of the KNEW-th column of HLAG into
   * W, and calculate the parameters of the updating formula. */
  tempa = z[knew][0] ;
  if(idz>=1) tempa = -tempa ;
  for(i=0;i<npt;i++) w[i] = tempa * z[i][0] ; 
  if(jl>0) for(tempb=z[knew][jl],i=0;i<npt;i++) w[i] += tempb * z[i][jl] ; 

  alpha = w[knew] ;
  tau = vlag[knew] ;
  tausq = tau * tau ;
  denom = alpha*beta + tausq ;
  vlag[knew] -= 1 ;
  /* Complete the updating of ZMAT when there is only 1.0 nonzero
   * element in the KNEW-th row of the new matrix ZMAT, but, ifIFLAG
   * is set to 1.0, then the first column of ZMAT will be exchanged
   * with another 1.0 later. */
  iflag = 0 ;

  /* ------------------------------------------------------------------------ */
  /*page*/
  /* ------------------------------------------------------------------------ */

  if(jl==0) 
  { temp = sqrt(fabs(denom)) ;
    tempb = tempa / temp ;
    tempa = tau / temp ;
    for(i=0;i<npt;i++) z[i][0] = tempa*z[i][0] - tempb*vlag[i] ;

    if(idz==0&&temp<0) idz = 1 ;
    if(idz>=1&&temp>=0) iflag = 1 ;
  } 
  else 
  { /* Complete the updating of ZMAT in the alternative case. */
    ja = 0 ;
    if(beta>=0) ja = jl ;
    jb = jl - ja ;
    temp = z[knew][jb] / denom ;
    tempa = temp * beta ;
    tempb = temp * tau ;
    temp = z[knew][ja] ;
    scala = 1.0 / sqrt(fabs(beta)*temp*temp+tausq) ;
    scalb = scala * sqrt((fabs(denom))) ;
    for(i=0;i<npt;i++)
    { z[i][ja] = scala * (tau * z[i][ja] - temp * vlag[i]) ;
      z[i][jb] = scalb * (z[i][jb] - tempa * w[i] - tempb * vlag[i]) ;
    }
    if(denom<=0) { if(beta<0) idz += 1 ; else iflag = 1 ; }
  }

  /* IDZ is reduced in the following case, and usually the first
   * column of ZMAT is exchanged with a later 1.0. */
  if(iflag==1) for(idz-=1,i=0;i<npt;i++) swap(z[i][0],z[i][idz]) ; 

  /* Finally, update the matrix BMAT. */
  for(j=0;j<n;j++)
  { jp = npt + j ; 
    w[jp] = b[knew][j] ;
    tempa = (alpha*vlag[jp] - tau*w[jp]) / denom ;
    tempb = -(beta*w[jp] + tau*vlag[jp]) / denom ;
    for(i=0;i<=jp;i++)
    { b[i][j] = b[i][j] + tempa*vlag[i] + tempb*w[i] ; // !! ** !!
      if(i>=npt) b[jp][i-npt] = b[i][j];
    }
  }
  free(w) ; 
  return idz ;
}
/* -------------------------------------------------------------------------- */
/*page*/
/* -------------------------------------------------------------------------- */

static double setrhodelta(double *rho,double rhoend,double *delta)
{ double ratio=rho[0]/rhoend ; 

  delta[0] = rho[0] / 2 ; 
  if(ratio<=16) rho[0] = rhoend ;
  else if(ratio<=250) rho[0] = sqrt(ratio) * rhoend ;
  else rho[0] = 0.1 * rho[0] ; // !! ** !!

  delta[0] = fmax(delta[0],rho[0]) ;
  return ratio ; 
}
static double setknewdistsq
  (int n,int npt,double delta,int *knew,double *xopt,double **xp) 
{ int j,k ;
  double sum,temp,distsq=4*delta*delta ;
  for(k=0;k<npt;k++)
  { for(sum=j=0;j<n;j++) { temp = xp[k][j] - xopt[j] ; sum += temp*temp ; }
    if(sum>distsq) { knew[0] = k ; distsq = sum ; } 
  }
  return distsq ; 
}
/* -------------------------------------------------------------------------- */

double uoamin(double (*func)(double*),double *x,int n,
              double rhobeg,double rhoend,int maxfun)
{
    /* This subroutine seeks the least value of a function of many
     * variables, by a trust region method that forms quadratic models
     * by interpolation. There can be some freedom in the interpolation
     * conditions, which is taken up by minimizing the Frobenius norm of
     * the change to the second derivative of the quadratic model,
     * beginning with a zero matrix. The arguments of the subroutine are
     * as follows. */

    /* N must be set to the number of variables and must be at least
     * two. NPT is the number of interpolation conditions. Its value
     * must be in the interval [N+2,(N+1)(N+2)/2]. Initial values of the
     * variables must be set in X(1),X(2),...,X(N). They will be changed
     * to the values that give the least calculated F. xinc and tol
     * must be set to the initial and final values of a trust region
     * radius, so both must be positive with tol<=xinc. Typically
     * xinc should be about one tenth of the greatest expected change
     * to a variable, and tol should indicate the accuracy that is
     * required in the final values of the variables. MAXFUN must be set
     * to an upper bound on the number of calls of CALFUN.  */

  /* SUBROUTINE func(x) must be provided by the user. */

  /* XBASE will hold a shift of origin that should reduce the contributions
   * from rounding errors to values of the model and Lagrange functions.
   * XOPT will be set to the displacement from XBASE of the vector of
     variables that provides the least calculated F so far.
   * XNEW will be set to the displacement from XBASE of the vector of
     variables for the current calculation of F. */

  /* ------------------------------------------------------------------------ */
  /*page*/
  /* ------------------------------------------------------------------------ */

  /* XPT will contain the interpolation point coordinates relative to XBASE.
   * FVAL will hold the values of F at the interpolation points.
   * GQ will hold the gradient of the quadratic model at XBASE.
   * HQ will hold the explicit second derivatives of the quadratic model.
   * PQ will contain the parameters of the implicit second derivatives
     of the quadratic model.
   * BMAT will hold the last N columns of H.
   * ZMAT will hold the factorization of the leading NPT by NPT submatrix
   * of H, this factorization being ZMAT times Diag(DZ) times ZMAT^T,
   * where the elements of DZ are plus or minus 1.0, asspecified by IDZ.
   * NDIM is the first dimension of BMAT and has the value NPT+N.
   * D is reserved for trial steps from XOPT.
   * VLAG will contain the values of the Lagrange functions at a new point X.
   * They are part of a product that requires VLAG to be of length NDIM. */

  int i,j,k,ih,nf,nh,nfm,idz,ipt,jpt,nfmm,knew,kopt,nptm,ksave,nfsav,itemp ; 
  int ktemp,itest,npt=2*n+1 ;
  double f,dx,dsq,rho,sum,fbeg,diff,beta,gisq,temp,suma,sumb,fopt,bsum,gqsq ; 
  double xipt,xjpt,sumz,diffa,diffb,diffc,hdiag,alpha,delta,recip,reciq,fsave ;
  double dnorm,ratio,dstep,vquad,tempq,rhosq,detrat,crvmin,distsq,xoptsq,qtemp ;

  double *xbase=vector(n),*xopt=vector(n),*xnew=vector(n),*fval=vector(npt) ; 
  double *gq=vector(n),*hq=vector((n*(n+1))/2),*pq=vector(npt) ;
  double *d=vector(n),*vlag=vector(npt+n),*w=vector(2*npt) ; 
  double **z=matrix(npt,npt-(n+1)),**b=matrix(npt+n,n),**xp=matrix(npt,n) ;

  nh = (n*(n+1)) / 2 ;
  nptm = npt - (n+1) ;
  if(maxfun<1) maxfun = 1 ; 
  for(j=0;j<n;j++) xbase[j] = x[j] ; 

  /* Begin the initialization procedure. NF becomes 1 more than the
   * number of function values so far. The coordinates of the
   * displacement of the next initial interpolation point from XBASE
   * are set in XPT(NF,.). */

  rhosq = rhobeg * rhobeg ;
  recip = 1 / rhosq ;
  reciq = sqrt(0.5) / rhosq ;

  /* ------------------------------------------------------------------------ */
  /*page*/
  /* ------------------------------------------------------------------------ */

  for(nf=0;nf<npt;nf++)
  { nfm = nf-1 ;
    nfmm = nfm-n ;

    if(nfm<2*n) // always happens when npt = 2*n+1
    { if(nfm>=0&&nfm<n) xp[nf][nfm] = rhobeg ;
      else if(nfm>=n) xp[nf][nfmm] = -rhobeg ;
    } 
    else        // the case which never happens
    { itemp = nfmm / n ;
      jpt = nfm - itemp * n - n ;
      ipt = jpt + itemp ;
      if(ipt>=n) { ipt -= n ; swap(ipt,jpt) ; } 
      xipt = rhobeg ;
      if(fval[ipt+n+1] < fval[ipt+1]) xipt = -xipt ;
      xjpt = rhobeg ;
      if(fval[jpt+n+1] < fval[jpt+1]) xjpt = -xjpt ;
      xp[nf][ipt] = xipt ;
      xp[nf][jpt] = xjpt ;
    }
    /* Calculate the next value of F. The least function value so far and its
     * index are required. */
    for(j=0;j<n;j++) x[j] = xp[nf][j] + xbase[j];
    fval[nf] = f = func(x) ; 

    if(nf==0) { fbeg = fopt = f ; kopt = 0 ; }
    else if(f<fopt) { fopt = f ; kopt = nf ; }

    /* Set the non0 initial elements of BMAT and the quadratic model
     * in the cases when NF is at most 2*N+1. */
    if(nfm<2*n) 
    { if(nfm>=0&&nfm<n) 
      { gq[nfm] = (f-fbeg) / rhobeg ;
        if(npt<=nf+n) 
        { b[0][nfm] = -1/rhobeg ; 
          b[nf][nfm] = 1/rhobeg ; 
          b[npt+nfm-1][nfm] = -rhosq/2 ; 
        }
      } 
      else if(nfm>=n) 
      { b[nf-n][nfmm] = 1/(2*rhobeg) ; 
        b[nf][nfmm] = -1/(2*rhobeg) ; 
        z[0][nfmm] = -(reciq+reciq) ;
        z[nf][nfmm] = z[nf-n][nfmm] = reciq ;
        ih = (nfmm * (nfmm+3)) / 2 ;
        temp = (fbeg-f) / rhobeg ;
        hq[ih] = (gq[nfmm]-temp) / rhobeg;
        gq[nfmm] = .5 * (gq[nfmm] + temp);
      }
    }
        /* Set the off-diagonal second derivatives of the Lagrange
         * functions and the initial quadratic model. */
    else 
    { ih = (ipt*(ipt+1))/2 + jpt ;
      if(xipt<0) ipt += n ;
      if(xjpt<0) jpt += n ;
      z[0][nfmm] = z[nf][nfmm] = recip ;
      z[ipt][nfmm] = z[jpt][nfmm] = -recip ;
      hq[ih] = (fbeg - fval[ipt+1] - fval[jpt+1] + f) / (xipt*xjpt) ;
    }
  }
  /* ------------------------------------------------------------------------ */
  /*page*/
  /* - Begin the iterative procedure, because the initial model is complete - */

  diffa = diffb = itest = idz = 0 ;
  for(xoptsq=i=0;i<n;i++) 
  { xopt[i] = xp[kopt][i] ; xoptsq += xopt[i]*xopt[i] ; }
    /* Generate the next trust region step and test its length. Set KNEW
     * to -1 ifthe purpose of the next F will be to improve the
     * model. */

  /* ------------------------------------------------------------------------ */

  for(knew=-1,delta=rho=rhobeg,nfsav=nf;;)
  { if(knew<0) for(;;)
    { crvmin = trsapp(n,npt,xopt,xp,gq,hq,pq,delta*delta,d) ;
      for(dsq=i=0;i<n;i++) dsq += d[i]*d[i] ; 
      dnorm = fmin(delta,sqrt(dsq)) ;
      if(dnorm>=rho/2) break ; // knew must be -1
      delta /= 10 ;
      ratio = -1 ;
      if(delta<=rho*1.5) delta = rho ;
      temp = crvmin * .125 * rho * rho ;
      if(nf<=nfsav+2||temp<=diffa||temp<=diffb||temp<=diffc) 
      { distsq = setknewdistsq(n,npt,delta,&knew,xopt,xp) ;
        /* If KNEW is positive, then set DSTEP, and branch back for the next
         * iteration, which will generate a "model step". */
        if(knew>=0) 
        { dstep = fmax(rho,fmin(sqrt(distsq)/10,delta/2)) ; 
          dsq = dstep*dstep ; 
          break ;
        }
        else if(delta>rho||dnorm>rho) continue ;
      }
      /* Return from the calculation, after another Newton-Raphson step,
       * if it is too short to have been tried before. */
      if(rho<=rhoend) 
      { for(i=0;i<n;i++) 
        { xnew[i] = xopt[i] + d[i] ; x[i] = xbase[i] + xnew[i] ; }
        f = func(x) ;
        knew = -2 ; 
        break ;
      }
      /* The calculations with the current value of RHO are complete. Pick
       * the next values of RHO and DELTA. */
      ratio = setrhodelta(&rho,rhoend,&delta) ; 
      nfsav = nf ; 
    }
    if(knew==-2) { cond = 0 ; break ; }

    /* ---------------------------------------------------------------------- */
    /*page*/
    /* ---------------------------------------------------------------------- */

    /* Shift XBASE if XOPT may be too far from XBASE. First make the
     * changes to BMAT that do not depend on ZMAT. */
    if(dsq<=xoptsq/1000) 
    { tempq = xoptsq/4 ;
      for(k=0;k<npt;k++)
      { for(sum=i=0;i<n;i++) sum += xp[k][i] * xopt[i];
        temp = pq[k] * sum ;
        sum -= xoptsq/2 ;
        w[npt+k] = sum ;
        for(i=0;i<n;i++)
        { gq[i] += temp * xp[k][i] ;
          xp[k][i] -= xopt[i]/2 ;
          vlag[i] = b[k][i];
          w[i] = sum*xp[k][i] + tempq*xopt[i] ;
          for(j=0;j<=i;j++) // !! ** !!
            b[npt+i][j] = b[npt+i][j] + vlag[i]*w[j] + w[i]*vlag[j] ;
        }
      }
      /* Then the revisions of BMAT that depend on ZMAT are calculated. */
      for(k=0;k<nptm;k++)
      { for(sumz=i=0;i<npt;i++)
        { sumz += z[i][k] ; w[i] = w[npt+i] * z[i][k] ; }
        for(j=0;j<n;j++)
        { sum = tempq * sumz * xopt[j];
          for(i=0;i<npt;i++) sum += w[i] * xp[i][j];
          vlag[j] = sum;
          if(k<idz) sum = -sum;
          for(i=0;i<npt;i++) b[i][j] += sum * z[i][k];
        }
        for(i=0;i<n;i++)
        { if(k>=idz) temp = vlag[i] ; else temp = -vlag[i] ; 
          for(j=0;j<=i;j++) b[npt+i][j] += temp * vlag[j] ;
        }
      }
      /* The following instructions complete the shift of XBASE,
       * including the changes to the parameters of the quadratic
       * model. */
      for(ih=j=0;j<n;j++)
      { for(w[j]=k=0;k<npt;k++)
        { w[j] += pq[k] * xp[k][j] ; xp[k][j] -= xopt[j]/2 ; }
        for(i=0;i<=j;ih++,i++)
        { if(i<j) gq[j] += hq[ih] * xopt[i] ;
          gq[i] += hq[ih] * xopt[j] ;
          hq[ih] = hq[ih] + w[i]*xopt[j] + xopt[i]*w[j] ;
          b[npt+i][j] = b[npt+j][i] ;
        }
      }
      for(xoptsq=j=0;j<n;j++) { xbase[j] += xopt[j] ; xopt[j] = 0 ; }
    }

    /* Pick the model step ifKNEW is positive. A different choice of D
     * may be made later, ifthe choice of D by BIGLAG causes
     * substantial cancellation in DENOM. */
    if(knew >= 0) alpha = biglag(n,npt,xopt,xp,b,z,idz,knew,dstep,d) ;

    /* Calculate VLAG and BETA for the current choice of D. The first
     * NPT components of W_check will be held in W. */
    for(k=0;k<npt;k++)
    { for(vlag[k]=suma=sumb=j=0;j<n;j++)
      { suma += xp[k][j] * d[j] ;
        sumb += xp[k][j] * xopt[j] ;
        vlag[k] += b[k][j] * d[j] ;
      }
      w[k] = suma * (suma/2+sumb) ;
    }
    /* ---------------------------------------------------------------------- */
    /*page*/
    /* ---------------------------------------------------------------------- */

    for(beta=k=0;k<nptm;k++)
    { for(sum=i=0;i<npt;i++) sum += z[i][k] * w[i] ;
      if(k<idz) { beta += sum*sum ; sum = -sum ; } else beta -= sum*sum ; 
      for(i=0;i<npt;i++) vlag[i] += sum * z[i][k];
    }
    for(bsum=dx=j=0;j<n;j++)
    { for(sum=i=0;i<npt;i++) sum += w[i] * b[i][j];
      bsum += sum * d[j] ;
      for(k=0;k<n;k++) sum += b[npt+j][k] * d[k];
      vlag[npt+j] = sum ; 
      bsum += sum * d[j] ; 
      dx += d[j] * xopt[j] ; 
    }

    // more numerical instability here? 
    beta = dx*dx + dsq*((xoptsq+dx) + dx + dsq/2) + beta - bsum ;
    vlag[kopt] += 1 ;

    /* If KNEW is positive and if the cancellation in DENOM is unacceptable, 
     * then BIGDEN calculates an alternative model step. */
    if(knew>=0&&fabs(1 + alpha*beta / (vlag[knew]*vlag[knew]))<=0.8) 
      beta = bigden(n,npt,xopt,xp,b,z,idz,kopt,knew,d,w,vlag) ;

    /* Calculate the next value of the objective function. */
    if(nf+1>=maxfun) { cond = 2 ; break ; }
    for(i=0;i<n;i++) { xnew[i] = xopt[i] + d[i] ; x[i] = xbase[i] + xnew[i] ; }
    nf += 1 ; 
    if(nf>=maxfun) { cond = 2 ; break ; }
    f = func(x) ;

    /* Use the quadratic model to predict the change in F due to the
     * step D, and set DIFF to the error of this prediction. */
    for(vquad=ih=j=0;j<n;j++)
    { vquad += d[j] * gq[j] ; 
      for(i=0;i<=j;ih++,i++)
      { temp = d[i]*xnew[j] + d[j]*xopt[i] ;
        if(i==j) temp /= 2 ;
        vquad += temp * hq[ih] ;
      }
    }

    for(k=0;k<npt;k++) vquad += pq[k] * w[k] ;
    diff = f - fopt - vquad ;
    diffc = diffb ;
    diffb = diffa ;
    diffa = fabs(diff) ;
    if(dnorm>rho) nfsav = nf ;
    /* Update FOPT and XOPT ifthe new F is the least value of the
     * objective function so far. The branch when KNEW is positive
     * occurs ifD is not a trust region step. */
    fsave = fopt ;
    if(f<fopt) 
    { fopt = f ;
      for(xoptsq=i=0;i<n;i++)
      { xopt[i] = xnew[i] ; xoptsq += xopt[i] * xopt[i] ; }
    }
    ksave = knew ;

    /* ---------------------------------------------------------------------- */
    /*page*/
    /* ---------------------------------------------------------------------- */

    if(knew<0) 
    { /* Pick the next value of DELTA after a trust region step. */
      if(vquad>=0) { cond = 1 ; break ; }
      ratio = (f-fsave) / vquad ;
      if(ratio<=0.1) delta = dnorm/2 ;
      else if(ratio<=.7) delta = fmax(delta/2,dnorm) ;
      else delta = fmax(delta/2,2*dnorm) ;

      if(delta<=rho*1.5) delta = rho;
      /* Set KNEW to the index of the next interpolation point to be
       * deleted. */
      temp = fmax(delta/10, rho);
      rhosq = temp * temp ;
      if(f>=fsave) { ktemp = kopt ; detrat = 1 ; }
      else { ktemp = -1 ; detrat = 0 ; } 

      for(k=0;k<npt;k++)
      { for(hdiag=j=0;j<nptm;j++) 
          if(j<idz) hdiag -= z[k][j]*z[k][j] ; else hdiag += z[k][j]*z[k][j] ; 
        temp = fabs(beta*hdiag + vlag[k]*vlag[k]);
        for(distsq=j=0;j<n;j++) 
        { qtemp = xp[k][j] - xopt[j] ; distsq += qtemp*qtemp ; }
        if(distsq>rhosq) 
        { qtemp = distsq / rhosq ; temp *= qtemp * qtemp * qtemp ; }
        if(temp>detrat&&k!=ktemp) { detrat = temp ; knew = k ; } 
      }
    }
    if(knew>=0)
    { /* Update BMAT, ZMAT and IDZ, so that the KNEW-th interpolation
      * point can be moved. Begin the updating of the quadratic model,
      * starting with the explicit second derivative term. */
      idz = update(n,npt,b,z,idz,vlag,beta,knew) ;
      fval[knew] = f ;

      for(ih=i=0;i<n;i++)
      { temp = pq[knew] * xp[knew][i] ;
        for(j=0;j<=i;ih++,j++) hq[ih] += temp * xp[knew][j] ;
      }

      pq[knew] = 0 ;
      /* Update the other second derivative parameters, and then the
       * gradient vector of the model. Also include the new interpolation
       * point. */
      for(j=0;j<nptm;j++)
      { temp = diff * z[knew][j] ;
        if(j<idz) temp = -temp ; 
        for(k=0;k<npt;k++) pq[k] += temp * z[k][j] ;
      }

      for(gqsq=i=0;i<n;i++)
      { gq[i] += diff * b[knew][i];
        gqsq += gq[i] * gq[i];
        xp[knew][i] = xnew[i];
      }

      /* If a trust region step makes a small change to the objective
       * function, then calculate the gradient of the least Frobenius norm
       * interpolant at XBASE, and store it in W, using VLAG for a vector
       * of right hand sides. */
      if(ksave==-1&&delta==rho&&fabs(ratio)>.01) itest = 0 ;
      else if(ksave==-1&&delta==rho) 
      { for(k=0;k<npt;k++) vlag[k] = fval[k] - fval[kopt] ; 
        for(gisq=i=0;i<n;i++)
        { for(w[i]=k=0;k<npt;k++) w[i] += b[k][i] * vlag[k] ;
          gisq += w[i]*w[i] ; 
        }
        itest += 1 ; 
        if(gqsq<gisq*100) itest = 0 ;
        if(itest>=3)
        { for(i=0;i<n;i++) gq[i] = w[i] ; 
          for(ih=0;ih<nh;ih++) hq[ih] = 0 ; 
          for(j=0;j<nptm;j++) 
          { for(w[j]=k=0;k<npt;k++) w[j] += vlag[k] * z[k][j] ;
            if(j<idz) w[j] = -w[j] ; 
          }
          for(k=0;k<npt;k++) 
            for(pq[k]=j=0;j<nptm;j++) pq[k] += z[k][j] * w[j];
          itest = 0 ; 
        }
      }

      if(f<fsave) kopt = knew ;
      knew = -1 ; 
      /* If a trust region step has provided a sufficient decrease in F,
       * then branch for another trust region calculation. The case
       * KSAVE>0 occurs when the new function value was calculated by a
       * model step. */
      if(f<=fsave+vquad/10||ksave>=0) continue ;
      /* Alternatively, find out ifthe interpolation points are close
       * enough to the best point so far. */
    }
    distsq = setknewdistsq(n,npt,delta,&knew,xopt,xp) ;
      /* If KNEW is positive, then set DSTEP, and branch back for the next
       * iteration, which will generate a "model step". */
    if(knew>=0) 
    { dstep = fmax(rho,fmin(sqrt(distsq)/10,delta/2)) ; dsq = dstep*dstep ; }
    else if(ratio<=0&&delta<=rho&&dnorm<=rho) 
    /* The calculations with the current value of RHO are complete. Pick
     * the next values of RHO and DELTA. */
    { if(rho<=rhoend) { cond = 0 ; break ; }
      ratio = setrhodelta(&rho,rhoend,&delta) ; 
      nfsav = nf ; 
    }
  }
  /* Return from the calculation, after another Newton-Raphson step,
   * if it is too short to have been tried before. */
  if(fopt<=f) { for(i=0;i<n;i++) x[i] = xbase[i] + xopt[i] ; f = fopt ; }
 
  free(xbase,xopt,xnew,w,fval,gq) ; 
  free(hq,pq,d,vlag) ; 
  freematrix(z,b,xp) ; 
  return f ;
}
/* -------------------------------------------------------------------------- */

static double(*f)(double*) ;
static double myfunc(double *x) { return -f(x) ; }

double uoamax(double (*func)(double*),double *x,int n,
                 double xinc,double tol,int max_iter)
{ f = func ; return -uoamin(myfunc,x,n,xinc,tol,max_iter) ; }

Archived from utils.html

#ifndef MEMORY_H
#define MEMORY_H

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>

#ifndef MATRIX_H
#define MATRIX_H

/* --------------------- define insist(...,orscream(...)) ------------------- */

#define insist(a,b) \
((a)?0:(fprintf(stdout,"Fatal error at line %d of %s\n",\
        __LINE__,__FILE__),(b)))

static int orscream(const char *m,...) 
{ va_list vl ; 
  va_start(vl,m) ; 
  vfprintf(stderr,m,vl) ; 
  va_end(vl) ; 
  fprintf(stderr,"\n") ; 
  fflush(0) ; 
  exit(EXIT_FAILURE) ; 
} ;
/* -------------------------- define variadic free() ------------------------ */

static void free(void *a,void *b) { free(a) ; free(b) ; } 
static void free(void *a,void *b,void *c) 
{ free(a) ; free(b) ; free(c) ; } 
static void free(void *a,void *b,void *c,void *d) 
{ free(a) ; free(b) ; free(c) ; free(d) ; } 
static void free(void *a,void *b,void *c,void *d,void *e) 
{ free(a) ; free(b) ; free(c) ; free(d) ; free(e) ; } 
static void free(void *a,void *b,void *c,void *d,void *e,void *f) 
{ free(a) ; free(b) ; free(c) ; free(d) ; free(e) ; free(f) ; } 

/* ------------------------- duplicate matrix.h ---------------------------- */

static void *cjcalloc(size_t a,size_t b)
{ insist((int)a>=0&&(int)b>=0,
         orscream("negative length %d requested.",a*b)) ; 
  void *p=calloc(a,b) ; 
  insist(p,orscream("unable to allocate %d bytes of memory.",b)) ; 
  return p ; 
} 
static void *cjcrealloc(void *a,size_t b)
{ insist((int)b>=0,orscream("negative length %d requested.",b)) ; 
  if(a==0&&b==0) return 0 ; 
  void *p=realloc(a,b) ; 
  if(b) insist(p,orscream("unable to reallocate %x to %d bytes.",a,b)) ;
  return p ; 
} 

/* vector allocators - free by calling 'free' */
// -- vector
static double *vector(int n) { return (double *) cjcalloc(n,sizeof(double)) ; } 
static double *vector(double *a,int n) 
{ return (double *) cjcrealloc(a,n*sizeof(double)) ; } 

// -- ivector
static int *ivector(int n) { return (int *) cjcalloc(n,sizeof(int)) ; }
static int *ivector(int *a,int n) 
{ return (int *) cjcrealloc(a,n*sizeof(int)) ; } 

// -- charvector
static char *charvector(int n) { return (char *) cjcalloc(n,sizeof(char)) ; }
static char *charvector(const char *c) 
{ int i ; 
  char *ret ; 
  for(i=0;c[i];i++) ; 
  ret = charvector(1+i) ;
  for(;i>=0;i--) ret[i] = c[i] ; 
  return ret ; 
} 
static char *charvector(char *a,int n) 
{ return (char *) cjcrealloc(a,n*sizeof(char)) ; } 

// -- strvector
static char **strvector(int n) { return (char **) cjcalloc(n,sizeof(char*)) ; }
static char **strvector(char **a,int n) 
{ return (char **) cjcrealloc(a,n*sizeof(char*)) ; } 

// -- shortvector
static short *shortvector(int n) 
{ return (short *) cjcalloc(n,sizeof(short)) ; } 
static short *shortvector(short *a,int n) 
{ return (short *) cjcrealloc(a,n*sizeof(short)) ; } 

/* 2- and 3- dimensional matrices of doubles */
static double **matrix(int m,int n)
{ double **a = (double **) cjcalloc(m,sizeof(double *)) ;
  a[0] = vector(2+m*n) ; 
  ((int *)a[0])[0] = m ; 
  ((int *)a[0])[1] = n ; 
  a[0] += 2 ; 
  for(int i=1;i<m;i++) a[i] = a[i-1] + n ; 
  return a ; 
}  
static int dimension0(double **a) { return ((int *)(a[0]-2))[0] ; } 
static int dimension1(double **a) { return ((int *)(a[0]-2))[1] ; } 

static double **matrix(double **a0,int m1,int n1)
{ double **a1 = matrix(m1,n1) ;
  if(a0==0) return a1 ; 
  int i,j,m0=dimension0(a0),n0=dimension1(a0) ; 
  if(m1>m0) m1 = m0 ; 
  if(n1>n0) n1 = n0 ; 
  for(i=0;i<m1;i++) for(j=0;j<n1;j++) a1[i][j] = a0[i][j] ; 
  return a1 ; 
}  
static double ***matrix(int m,int n,int l)
{ int i ; 
  double ***a = (double ***) cjcalloc(m,sizeof(double **)) ;
  a[0] = (double **) cjcalloc(3+m*n,sizeof(double *)) ; 
  ((int *)a[0])[0] = m ; 
  ((int *)a[0])[1] = n ; 
  ((int *)a[0])[2] = l ; 
  a[0] += 3 ; 
  for(i=1;i<m;i++) a[i] = a[i-1] + n ; 
  a[0][0] = vector(m*n*l) ; 
  for(i=1;i<m*n;i++) a[0][i] = a[0][i-1] + l ; 
  return a ; 
}  
static int dimension0(double ***a) { return ((int *)(a[0]-3))[0] ; } 
static int dimension1(double ***a) { return ((int *)(a[0]-3))[1] ; } 
static int dimension2(double ***a) { return ((int *)(a[0]-3))[2] ; } 

/* free them */
static void freematrix(double **a) { if(a) free(a[0]-2,a) ; } 
static void freematrix(double **a,double **b)
{ freematrix(a) ; freematrix(b) ; } 
static void freematrix(double **a,double **b,double **c)
{ freematrix(a) ; freematrix(b) ; freematrix(c) ; } 
static void freematrix(double **a,double **b,double **c,double **d)
{ freematrix(a) ; freematrix(b) ; freematrix(c) ; freematrix(d) ; } 

static void freematrix(double **a,double **b,double **c,double **d,
                       double **e)
{ freematrix(a) ; freematrix(b) ; freematrix(c) ; freematrix(d) ; 
  freematrix(e) ;
} 

static void freematrix(double **a,double **b,double **c,double **d,
                       double **e,double **f)
{ freematrix(a) ; freematrix(b) ; freematrix(c) ; freematrix(d) ; 
  freematrix(e) ; freematrix(f) ;
} 
static void freematrix(double ***a) { if(a) free(a[0][0],a[0]-3,a) ; } 

/* 2- and 3- dimensional matrices of ints */
static int **imatrix(int m,int n)
{ int **a = (int **) cjcalloc(m,sizeof(int *)) ;
  a[0] = ivector(2+m*n) ; 
  a[0][0] = m ; 
  a[0][1] = n ; 
  a[0] += 2 ; 
  for(int i=1;i<m;i++) a[i] = a[i-1] + n ; 
  return a ; 
}  
static int dimension0(int **a) { return ((int *)(a[0]-2))[0] ; } 
static int dimension1(int **a) { return ((int *)(a[0]-2))[1] ; } 

static int ***imatrix(int m,int n,int l)
{ int i ; 
  int ***a = (int ***) cjcalloc(m,sizeof(int **)) ;
  a[0] = (int **) cjcalloc(3+m*n,sizeof(int *)) ; 
  ((int *)a[0])[0] = m ; 
  ((int *)a[0])[1] = n ; 
  ((int *)a[0])[2] = l ; 
  a[0] += 3 ; 
  for(i=1;i<m;i++) a[i] = a[i-1] + n ; 
  a[0][0] = ivector(m*n*l) ; 
  for(i=1;i<m*n;i++) a[0][i] = a[0][i-1] + l ; 
  return a ; 
}  
static int dimension0(int ***a) { return ((int *)(a[0]-3))[0] ; } 
static int dimension1(int ***a) { return ((int *)(a[0]-3))[1] ; } 
static int dimension2(int ***a) { return ((int *)(a[0]-3))[2] ; } 

/* free them */
static void freeimatrix(int **a) { if(a) free(a[0]-2,a) ; } 
static void freeimatrix(int ***a) { if(a) free(a[0][0],a[0]-3,a) ; } 

/* swaps */
static void swap(int &a,int &b) { int c=b ; b = a ; a = c ; } 
static void swap(double &a,double &b) { double c=b ; b = a ; a = c ; }
static void swap(char &a,char &b) { char c=b ; b = a ; a = c ; } 
static void swap(int &a,double &b) { int c=(int) b ; b = a ; a = c ; }
static void swap(double &a,int &b) { int c=b ; b = (int) a ; a = c ; }

/* ----------------------------- generic sort ------------------------------- */

#define shellsort(u,n,b,x)                                  \
{ int i,j,incr ; b v ;                                      \
  for(incr=1;1+3*incr<(n);incr=1+3*incr) ;                  \
  for(;incr>0;incr/=3) for(i=incr;i<(n);i++)                \
  { v = u[i] ;                                              \
    for(j=i;j>=incr;j-=incr)                                \
    { if(v.x<u[j-incr].x) u[j] = u[j-incr] ; else break ; } \
    u[j] = v ;                                              \
  }                                                         \
}
#define shellsortdesc(u,n,b,x)                              \
{ int i,j,incr ; b v ;                                      \
  for(incr=1;1+3*incr<(n);incr=1+3*incr) ;                  \
  for(;incr>0;incr/=3) for(i=incr;i<(n);i++)                \
  { v = u[i] ;                                              \
    for(j=i;j>=incr;j-=incr)                                \
    { if(v.x>u[j-incr].x) u[j] = u[j-incr] ; else break ; } \
    u[j] = v ;                                              \
  }                                                         \
}
/* ----------------------------- integer sort ------------------------------- */

static void isort(int *u,int n)
{ int i,j,incr,v ; 

  for(incr=1;1+3*incr<n;incr=1+3*incr) ; 
  for(;incr>0;incr/=3) for(i=incr;i<n;i++)
  { for(v=u[i],j=i;j>=incr;j-=incr) 
      if(v<u[j-incr]) u[j] = u[j-incr] ; else break ; 
    u[j] = v ;
  }
}
/* --- extend matrix.h to complexes if unreal.h already included ---- */

#ifdef UNREAL_H
/* allocate vectors and matrices of complexes */
static complex *cvector(int n) 
{ return (complex *) cjcalloc(n,sizeof(complex)) ; } 
static complex *cvector(complex *a,int n) 
{ return (complex *) cjcrealloc(a,n*sizeof(complex)) ; } 

/* 2- and 3- dimensional matrices of doubles */
static complex **cmatrix(int m,int n)
{ complex **a = (complex **) cjcalloc(m,sizeof(complex *)) ;
  a[0] = cvector(2+m*n) ; 
  ((int *)a[0])[0] = m ; 
  ((int *)a[0])[1] = n ; 
  a[0] += 2 ; 
  for(int i=1;i<m;i++) a[i] = a[i-1] + n ; 
  return a ; 
}  
static int dimension0(complex **a) { return ((int *)(a[0]-2))[0] ; } 
static int dimension1(complex **a) { return ((int *)(a[0]-2))[1] ; } 

static complex ***cmatrix(int m,int n,int l)
{ int i ; 
  complex ***a = (complex ***) cjcalloc(m,sizeof(complex **)) ;
  a[0] = (complex **) cjcalloc(3+m*n,sizeof(complex *)) ; 
  ((int *)a[0])[0] = m ; 
  ((int *)a[0])[1] = n ; 
  ((int *)a[0])[2] = l ; 
  a[0] += 3 ; 
  for(i=1;i<m;i++) a[i] = a[i-1] + n ; 
  a[0][0] = cvector(m*n*l) ; 
  for(i=1;i<m*n;i++) a[0][i] = a[0][i-1] + l ; 
  return a ; 
}  
static int dimension0(complex ***a) { return ((int *)(a[0]-3))[0] ; } 
static int dimension1(complex ***a) { return ((int *)(a[0]-3))[1] ; } 
static int dimension2(complex ***a) { return ((int *)(a[0]-3))[2] ; } 

/* free them */
static void freecmatrix(complex **a) { if(a) free(a[0]-2,a) ; } 
static void freecmatrix(complex ***a) { if(a) free(a[0][0],a[0]-3,a) ; } 

#endif

/* -------------------------- define robust fopens -------------------------- */

#ifndef NO_MEMORY_IO
static int cjcwarning=0 ; 

static FILE *fopenread(char *name)
{ FILE *f ; 
  if(name[0]=='-'&&name[1]=='-'&&name[2]==0) f = stdin ; 
  else f = fopen(name,"r") ; 
  insist(f,(perror(0),
            orscream("Your input file %s could not be found.\n",name))) ;
  return f ; 
} 
static FILE *fopenwrite(char *name)
{ FILE *f ; 
  if(name[0]=='-'&&name[1]=='-'&&name[2]==0) f = stdout ; 
  else f = fopen(name,"w") ; 
  insist(f,(perror(0),orscream("Unable to write to your file %s.\n",name))) ;
  return f ; 
} 
static FILE *fopenappend(char *name)
{ FILE *f ;
  if(cjcwarning==0) { printf("fopenappend deprecated\n") ; cjcwarning = 1 ; } 
  if(name[0]=='-'&&name[1]=='-'&&name[2]==0) f = stdout ;
  else f = fopen(name,"a") ;
  insist(f,(perror(0),orscream("Unable to append to your file %s",name))) ;
  return f ;
}
static FILE *fopenreadb(char *name)
{ FILE *f ; 
  if(name[0]=='-'&&name[1]=='-'&&name[2]==0) f = stdin ; 
  else f = fopen(name,"rb") ; 
  insist(f,(perror(0),
            orscream("Your input file %s could not be found.",name))) ;
  return f ; 
} 
static FILE *fopenwriteb(char *name)
{ FILE *f ; 
  if(name[0]=='-'&&name[1]=='-'&&name[2]==0) f = stdout ; 
  else f = fopen(name,"wb") ; 
  insist(f,(perror(0),orscream("Unable to write to your file %s",name))) ; 
  return f ; 
} 
static FILE *fopenappendb(char *name)
{ FILE *f ;
  if(cjcwarning==0) { printf("fopenappendb deprecated\n") ; cjcwarning = 1 ; } 
  if(name[0]=='-'&&name[1]=='-'&&name[2]==0) f = stdout ;
  else f = fopen(name,"ab") ;
  insist(f,(perror(0),orscream("Unable to append to your file %s",name))) ;
  return f ; 
}
#endif

static char *freadline(FILE *ifl)
{ char *s=0 ; 
  int slen,ns,c,i ; 
  for(slen=ns=0;;)
  { c = fgetc(ifl) ; 
    if(c==EOF||c=='\n') { if(slen>ns+1) s = charvector(s,ns+1) ; return s ; }
    if(ns>=slen-1)
    { slen += 10 + slen/2 ; 
      s = charvector(s,slen) ; 
      for(i=ns;i<slen;i++) s[i] = 0 ; 
    }
    s[ns++] = (char) c ; 
  }
}
static char *readline() { return freadline(stdin) ; } 
#endif
#endif

Added manually

#include <fem.hpp> // Fortran EMulation library of fable module namespace placeholder_please_replace { using namespace fem::major_types; void datan(...) { throw std::runtime_error( "Missing function implementation: datan"); } void dfloat(...) { throw std::runtime_error( "Missing function implementation: dfloat"); } struct common_commonymous { float fmax; common_commonymous() : fmax(fem::float0) {} }; struct common : fem::common, common_commonymous { common( int argc, char const* argv[]) : fem::common(argc, argv) {} }; void calfun( common& cmn, int const& /* n */, arr_cref<float> x, float& f) { x(dimension(star)); float& fmax = cmn.fmax; // float zero = fem::float0; float v12 = fem::float0; float v13 = fem::float0; float v14 = fem::float0; float v23 = fem::float0; float v24 = fem::float0; float v34 = fem::float0; float del1 = fem::float0; float del2 = fem::float0; float del3 = fem::float0; float del4 = fem::float0; float temp = fem::float0; zero = 0.0e0; f = fmax; v12 = x(1) * x(5) - x(4) * x(2); v13 = x(1) * x(8) - x(7) * x(2); v14 = x(1) * x(11) - x(10) * x(2); v23 = x(4) * x(8) - x(7) * x(5); v24 = x(4) * x(11) - x(10) * x(5); v34 = x(7) * x(11) - x(10) * x(8); del1 = v23 * x(12) - v24 * x(9) + v34 * x(6); if (del1 <= zero) { goto statement_10; } del2 = -v34 * x(3) - v13 * x(12) + v14 * x(9); if (del2 <= zero) { goto statement_10; } del3 = -v14 * x(6) + v24 * x(3) + v12 * x(12); if (del3 <= zero) { goto statement_10; } del4 = -v12 * x(9) + v13 * x(6) - v23 * x(3); if (del4 <= zero) { goto statement_10; } temp = fem::pow3((del1 + del2 + del3 + del4)) / (del1 * del2 * del3 * del4); f = fem::dmin1(temp / 6.0e0, fmax); statement_10:; } void getact( int const& n, int const& m, arr_cref<float, 2> amat, arr_cref<float> /* b */, int& nact, arr_ref<int> iact, arr_ref<float, 2> qfac, arr_ref<float> rfac, float const& snorm, arr_ref<float> resnew, arr_ref<float> resact, arr_cref<float> g, arr_ref<float> dw, arr_ref<float> vlam, arr_ref<float> w) { amat(dimension(n, star)); iact(dimension(star)); qfac(dimension(n, star)); rfac(dimension(star)); resnew(dimension(star)); resact(dimension(star)); g(dimension(star)); dw(dimension(star)); vlam(dimension(star)); w(dimension(star)); float one = fem::float0; float tiny = fem::float0; float zero = fem::float0; float tdel = fem::float0; float ddsav = fem::float0; int i = fem::int0; int j = fem::int0; int iflag = fem::int0; int ic = fem::int0; float temp = fem::float0; int idiag = fem::int0; int jw = fem::int0; float dd = fem::float0; float dnorm = fem::float0; int l = fem::int0; float test = fem::float0; float violmx = fem::float0; float sum = fem::float0; float ctol = fem::float0; int k = fem::int0; int nactp = fem::int0; float rdiag = fem::float0; float sprod = fem::float0; float cosv = fem::float0; float sinv = fem::float0; float vmult = fem::float0; int jc = fem::int0; int jcp = fem::int0; float cval = fem::float0; float sval = fem::float0; int jdiag = fem::int0; //C //C N, M, AMAT, B, NACT, IACT, QFAC and RFAC are the same as the terms //C with these names in SUBROUTINE LINCOB. The current values must be //C set on entry. NACT, IACT, QFAC and RFAC are kept up to date when //C GETACT changes the current active set. //C SNORM, RESNEW, RESACT, G and DW are the same as the terms with these //C names in SUBROUTINE TRSTEP. The elements of RESNEW and RESACT are //C also kept up to date. //C VLAM and W are used for working space, the vector VLAM being reserved //C for the Lagrange multipliers of the calculation. Their lengths must //C be at least N. //C The main purpose of GETACT is to pick the current active set. It is //C defined by the property that the projection of -G into the space //C orthogonal to the active constraint normals is as large as possible, //C subject to this projected steepest descent direction moving no closer //C to the boundary of every constraint whose current residual is at most //C 0.2*SNORM. On return, the settings in NACT, IACT, QFAC and RFAC are //C all appropriate to this choice of active set. //C Occasionally this projected direction is zero, and then the final value //C of W(1) is set to zero. Otherwise, the direction itself is returned //C in DW, and W(1) is set to the square of the length of the direction. //C //C Set some constants and a temporary VLAM. //C one = 1.0e0; tiny = 1.0e-60; zero = 0.0e0; tdel = 0.2e0 * snorm; ddsav = zero; FEM_DO_SAFE(i, 1, n) { ddsav += fem::pow2(g(i)); vlam(i) = zero; } ddsav += ddsav; //C //C Set the initial QFAC to the identity matrix in the case NACT=0. //C if (nact == 0) { FEM_DO_SAFE(i, 1, n) { FEM_DO_SAFE(j, 1, n) { qfac(i, j) = zero; } qfac(i, i) = one; } goto statement_100; } //C //C Remove any constraints from the initial active set whose residuals //C exceed TDEL. //C iflag = 1; ic = nact; statement_40: if (resact(ic) > tdel) { goto statement_800; } statement_50: ic = ic - 1; if (ic > 0) { goto statement_40; } //C //C Remove any constraints from the initial active set whose Lagrange //C multipliers are nonnegative, and set the surviving multipliers. //C iflag = 2; statement_60: if (nact == 0) { goto statement_100; } ic = nact; statement_70: temp = zero; FEM_DO_SAFE(i, 1, n) { temp += qfac(i, ic) * g(i); } idiag = (ic * ic + ic) / 2; if (ic < nact) { jw = idiag + ic; FEM_DO_SAFE(j, ic + 1, nact) { temp = temp - rfac(jw) * vlam(j); jw += j; } } if (temp >= zero) { goto statement_800; } vlam(ic) = temp / rfac(idiag); ic = ic - 1; if (ic > 0) { goto statement_70; } //C //C Set the new search direction D. Terminate if the 2-norm of D is zero //C or does not decrease, or if NACT=N holds. The situation NACT=N //C occurs for sufficiently large SNORM if the origin is in the convex //C hull of the constraint gradients. //C statement_100: if (nact == n) { goto statement_290; } FEM_DO_SAFE(j, nact + 1, n) { w(j) = zero; FEM_DO_SAFE(i, 1, n) { w(j) += qfac(i, j) * g(i); } } dd = zero; FEM_DO_SAFE(i, 1, n) { dw(i) = zero; FEM_DO_SAFE(j, nact + 1, n) { dw(i) = dw(i) - w(j) * qfac(i, j); } dd += fem::pow2(dw(i)); } if (dd >= ddsav) { goto statement_290; } if (dd == zero) { goto statement_300; } ddsav = dd; dnorm = fem::dsqrt(dd); //C //C Pick the next integer L or terminate, a positive value of L being //C the index of the most violated constraint. The purpose of CTOL //C below is to estimate whether a positive value of VIOLMX may be //C due to computer rounding errors. //C l = 0; if (m > 0) { test = dnorm / snorm; violmx = zero; FEM_DO_SAFE(j, 1, m) { if (resnew(j) > zero && resnew(j) <= tdel) { sum = zero; FEM_DO_SAFE(i, 1, n) { sum += amat(i, j) * dw(i); } if (sum > test * resnew(j)) { if (sum > violmx) { l = j; violmx = sum; } } } } ctol = zero; temp = 0.01e0 * dnorm; if (violmx > zero && violmx < temp) { if (nact > 0) { FEM_DO_SAFE(k, 1, nact) { j = iact(k); sum = zero; FEM_DO_SAFE(i, 1, n) { sum += dw(i) * amat(i, j); } ctol = fem::dmax1(ctol, fem::dabs(sum)); } } } } w(1) = one; if (l == 0) { goto statement_300; } if (violmx <= 10.0e0 * ctol) { goto statement_300; } //C //C Apply Givens rotations to the last (N-NACT) columns of QFAC so that //C the first (NACT+1) columns of QFAC are the ones required for the //C addition of the L-th constraint, and add the appropriate column //C to RFAC. //C nactp = nact + 1; idiag = (nactp * nactp - nactp) / 2; rdiag = zero; FEM_DOSTEP(j, n, 1, -1) { sprod = zero; FEM_DO_SAFE(i, 1, n) { sprod += qfac(i, j) * amat(i, l); } if (j <= nact) { rfac(idiag + j) = sprod; } else { if (fem::dabs(rdiag) <= 1.0e-20 * fem::dabs(sprod)) { rdiag = sprod; } else { temp = fem::dsqrt(sprod * sprod + rdiag * rdiag); cosv = sprod / temp; sinv = rdiag / temp; rdiag = temp; FEM_DO_SAFE(i, 1, n) { temp = cosv * qfac(i, j) + sinv * qfac(i, j + 1); qfac(i, j + 1) = -sinv * qfac(i, j) + cosv * qfac(i, j + 1); qfac(i, j) = temp; } } } } if (rdiag < zero) { FEM_DO_SAFE(i, 1, n) { qfac(i, nactp) = -qfac(i, nactp); } } rfac(idiag + nactp) = fem::dabs(rdiag); nact = nactp; iact(nact) = l; resact(nact) = resnew(l); vlam(nact) = zero; resnew(l) = zero; //C //C Set the components of the vector VMU in W. //C statement_220: w(nact) = one / fem::pow2(rfac((nact * nact + nact) / 2)); if (nact > 1) { FEM_DOSTEP(i, nact - 1, 1, -1) { idiag = (i * i + i) / 2; jw = idiag + i; sum = zero; FEM_DO_SAFE(j, i + 1, nact) { sum = sum - rfac(jw) * w(j); jw += j; } w(i) = sum / rfac(idiag); } } //C //C Calculate the multiple of VMU to subtract from VLAM, and update VLAM. //C vmult = violmx; ic = 0; j = 1; statement_250: if (j < nact) { if (vlam(j) >= vmult * w(j)) { ic = j; vmult = vlam(j) / w(j); } j++; goto statement_250; } FEM_DO_SAFE(j, 1, nact) { vlam(j) = vlam(j) - vmult * w(j); } if (ic > 0) { vlam(ic) = zero; } violmx = fem::dmax1(violmx - vmult, zero); if (ic == 0) { violmx = zero; } //C //C Reduce the active set if necessary, so that all components of the //C new VLAM are negative, with resetting of the residuals of the //C constraints that become inactive. //C iflag = 3; ic = nact; statement_270: if (vlam(ic) < zero) { goto statement_280; } resnew(iact(ic)) = fem::dmax1(resact(ic), tiny); goto statement_800; statement_280: ic = ic - 1; if (ic > 0) { goto statement_270; } //C //C Calculate the next VMU if VIOLMX is positive. Return if NACT=N holds, //C as then the active constraints imply D=0. Otherwise, go to label //C 100, to calculate the new D and to test for termination. //C if (violmx > zero) { goto statement_220; } if (nact < n) { goto statement_100; } statement_290: dd = zero; statement_300: w(1) = dd; return; //C //C These instructions rearrange the active constraints so that the new //C value of IACT(NACT) is the old value of IACT(IC). A sequence of //C Givens rotations is applied to the current QFAC and RFAC. Then NACT //C is reduced by one. //C statement_800: resnew(iact(ic)) = fem::dmax1(resact(ic), tiny); jc = ic; statement_810: if (jc < nact) { jcp = jc + 1; idiag = jc * jcp / 2; jw = idiag + jcp; temp = fem::dsqrt(fem::pow2(rfac(jw - 1)) + fem::pow2(rfac(jw))); cval = rfac(jw) / temp; sval = rfac(jw - 1) / temp; rfac(jw - 1) = sval * rfac(idiag); rfac(jw) = cval * rfac(idiag); rfac(idiag) = temp; if (jcp < nact) { FEM_DO_SAFE(j, jcp + 1, nact) { temp = sval * rfac(jw + jc) + cval * rfac(jw + jcp); rfac(jw + jcp) = cval * rfac(jw + jc) - sval * rfac(jw + jcp); rfac(jw + jc) = temp; jw += j; } } jdiag = idiag - jc; FEM_DO_SAFE(i, 1, n) { if (i < jc) { temp = rfac(idiag + i); rfac(idiag + i) = rfac(jdiag + i); rfac(jdiag + i) = temp; } temp = sval * qfac(i, jc) + cval * qfac(i, jcp); qfac(i, jcp) = cval * qfac(i, jc) - sval * qfac(i, jcp); qfac(i, jc) = temp; } iact(jc) = iact(jcp); resact(jc) = resact(jcp); vlam(jc) = vlam(jcp); jc = jcp; goto statement_810; } nact = nact - 1; switch (iflag) { case 1: goto statement_50; case 2: goto statement_60; case 3: goto statement_280; default: break; } } void update( int const& n, int const& npt, arr_cref<float, 2> xpt, arr_ref<float, 2> bmat, arr_ref<float, 2> zmat, int& idz, int const& ndim, arr_cref<float> sp, arr_cref<float> step, int const& kopt, int& knew, arr_ref<float> vlag, arr_ref<float> w) { xpt(dimension(npt, star)); bmat(dimension(ndim, star)); zmat(dimension(npt, star)); sp(dimension(star)); step(dimension(star)); vlag(dimension(star)); w(dimension(star)); float half = fem::float0; float one = fem::float0; float zero = fem::float0; int nptm = fem::int0; int k = fem::int0; float sum = fem::float0; int j = fem::int0; float beta = fem::float0; int i = fem::int0; float bsum = fem::float0; float dx = fem::float0; float ssq = fem::float0; int jp = fem::int0; float denmax = fem::float0; float hdiag = fem::float0; float temp = fem::float0; float denabs = fem::float0; float distsq = fem::float0; int jl = fem::int0; float tempa = fem::float0; float tempb = fem::float0; float alpha = fem::float0; float tau = fem::float0; float tausq = fem::float0; float denom = fem::float0; float sqrtdn = fem::float0; int iflag = fem::int0; int ja = fem::int0; int jb = fem::int0; float scala = fem::float0; float scalb = fem::float0; //C //C The arguments N, NPT, XPT, BMAT, ZMAT, IDZ, NDIM ,SP and STEP are //C identical to the corresponding arguments in SUBROUTINE LINCOB. //C KOPT is such that XPT(KOPT,.) is the current trust region centre. //C KNEW on exit is usually positive, and then it is the index of an //C interpolation point to be moved to the position XPT(KOPT,.)+STEP(.). //C It is set on entry either to its final value or to 0. In the latter //C case, the final value of KNEW is chosen to maximize the denominator //C of the matrix updating formula times a weighting factor. //C VLAG and W are used for working space, the first NPT+N elements of //C both of these vectors being required. //C //C The arrays BMAT and ZMAT with IDZ are updated, the new matrices being //C the ones that are suitable after the shift of the KNEW-th point to //C the new position XPT(KOPT,.)+STEP(.). A return with KNEW set to zero //C occurs if the calculation fails due to a zero denominator in the //C updating formula, which should never happen. //C //C Set some constants. //C half = 0.5e0; one = 1.0e0; zero = 0.0e0; nptm = npt - n - 1; //C //C Calculate VLAG and BETA for the current choice of STEP. The first NPT //C elements of VLAG are set to the values of the Lagrange functions at //C XPT(KOPT,.)+STEP(.). The first NPT components of W_check are held //C in W, where W_check is defined in a paper on the updating method. //C FEM_DO_SAFE(k, 1, npt) { w(k) = sp(npt + k) * (half * sp(npt + k) + sp(k)); sum = zero; FEM_DO_SAFE(j, 1, n) { sum += bmat(k, j) * step(j); } vlag(k) = sum; } beta = zero; FEM_DO_SAFE(k, 1, nptm) { sum = zero; FEM_DO_SAFE(i, 1, npt) { sum += zmat(i, k) * w(i); } if (k < idz) { beta += sum * sum; sum = -sum; } else { beta = beta - sum * sum; } FEM_DO_SAFE(i, 1, npt) { vlag(i) += sum * zmat(i, k); } } bsum = zero; dx = zero; ssq = zero; FEM_DO_SAFE(j, 1, n) { sum = zero; FEM_DO_SAFE(i, 1, npt) { sum += w(i) * bmat(i, j); } bsum += sum * step(j); jp = npt + j; FEM_DO_SAFE(k, 1, n) { sum += bmat(jp, k) * step(k); } vlag(jp) = sum; bsum += sum * step(j); dx += step(j) * xpt(kopt, j); ssq += fem::pow2(step(j)); } beta = dx * dx + ssq * (sp(kopt) + dx + dx + half * ssq) + beta - bsum; vlag(kopt) += one; //C //C If KNEW is zero initially, then pick the index of the interpolation //C point to be deleted, by maximizing the absolute value of the //C denominator of the updating formula times a weighting factor. //C if (knew == 0) { denmax = zero; FEM_DO_SAFE(k, 1, npt) { hdiag = zero; FEM_DO_SAFE(j, 1, nptm) { temp = one; if (j < idz) { temp = -one; } hdiag += temp * fem::pow2(zmat(k, j)); } denabs = fem::dabs(beta * hdiag + fem::pow2(vlag(k))); distsq = zero; FEM_DO_SAFE(j, 1, n) { distsq += fem::pow2((xpt(k, j) - xpt(kopt, j))); } temp = denabs * distsq * distsq; if (temp > denmax) { denmax = temp; knew = k; } } } //C //C Apply the rotations that put zeros in the KNEW-th row of ZMAT. //C jl = 1; if (nptm >= 2) { FEM_DO_SAFE(j, 2, nptm) { if (j == idz) { jl = idz; } else if (zmat(knew, j) != zero) { temp = fem::dsqrt(fem::pow2(zmat(knew, jl)) + fem::pow2(zmat(knew, j))); tempa = zmat(knew, jl) / temp; tempb = zmat(knew, j) / temp; FEM_DO_SAFE(i, 1, npt) { temp = tempa * zmat(i, jl) + tempb * zmat(i, j); zmat(i, j) = tempa * zmat(i, j) - tempb * zmat(i, jl); zmat(i, jl) = temp; } zmat(knew, j) = zero; } } } //C //C Put the first NPT components of the KNEW-th column of the Z Z^T matrix //C into W, and calculate the parameters of the updating formula. //C tempa = zmat(knew, 1); if (idz >= 2) { tempa = -tempa; } if (jl > 1) { tempb = zmat(knew, jl); } FEM_DO_SAFE(i, 1, npt) { w(i) = tempa * zmat(i, 1); if (jl > 1) { w(i) += tempb * zmat(i, jl); } } alpha = w(knew); tau = vlag(knew); tausq = tau * tau; denom = alpha * beta + tausq; vlag(knew) = vlag(knew) - one; if (denom == zero) { knew = 0; goto statement_180; } sqrtdn = fem::dsqrt(fem::dabs(denom)); //C //C Complete the updating of ZMAT when there is only one nonzero element //C in the KNEW-th row of the new matrix ZMAT. IFLAG is set to one when //C the value of IDZ is going to be reduced. //C iflag = 0; if (jl == 1) { tempa = tau / sqrtdn; tempb = zmat(knew, 1) / sqrtdn; FEM_DO_SAFE(i, 1, npt) { zmat(i, 1) = tempa * zmat(i, 1) - tempb * vlag(i); } if (denom < zero) { if (idz == 1) { idz = 2; } else { iflag = 1; } } } else { //C //C Complete the updating of ZMAT in the alternative case. //C ja = 1; if (beta >= zero) { ja = jl; } jb = jl + 1 - ja; temp = zmat(knew, jb) / denom; tempa = temp * beta; tempb = temp * tau; temp = zmat(knew, ja); scala = one / fem::dsqrt(fem::dabs(beta) * temp * temp + tausq); scalb = scala * sqrtdn; FEM_DO_SAFE(i, 1, npt) { zmat(i, ja) = scala * (tau * zmat(i, ja) - temp * vlag(i)); zmat(i, jb) = scalb * (zmat(i, jb) - tempa * w(i) - tempb * vlag(i)); } if (denom <= zero) { if (beta < zero) { idz++; } else { iflag = 1; } } } //C //C Reduce IDZ when the diagonal part of the ZMAT times Diag(DZ) times //C ZMAT^T factorization gains another positive element. Then exchange //C the first and IDZ-th columns of ZMAT. //C if (iflag == 1) { idz = idz - 1; FEM_DO_SAFE(i, 1, npt) { temp = zmat(i, 1); zmat(i, 1) = zmat(i, idz); zmat(i, idz) = temp; } } //C //C Finally, update the matrix BMAT. //C FEM_DO_SAFE(j, 1, n) { jp = npt + j; w(jp) = bmat(knew, j); tempa = (alpha * vlag(jp) - tau * w(jp)) / denom; tempb = (-beta * w(jp) - tau * vlag(jp)) / denom; FEM_DO_SAFE(i, 1, jp) { bmat(i, j) += tempa * vlag(i) + tempb * w(i); if (i > npt) { bmat(jp, i - npt) = bmat(i, j); } } } statement_180:; } void prelim( common& cmn, int const& n, int const& npt, int const& m, arr_cref<float, 2> amat, arr_ref<float> b, arr_ref<float> x, float const& rhobeg, int const& iprint, arr_ref<float> xbase, arr_ref<float, 2> xpt, arr_ref<float> fval, arr_ref<float> xsav, arr_ref<float> xopt, arr_ref<float> gopt, int& kopt, arr_ref<float> hq, arr_ref<float> pq, arr_ref<float, 2> bmat, arr_ref<float, 2> zmat, int& idz, int const& ndim, arr_ref<float> sp, arr_ref<float> rescon, arr_ref<float> step, arr_ref<float> pqw, arr_ref<float> w) { amat(dimension(n, star)); b(dimension(star)); x(dimension(star)); xbase(dimension(star)); xpt(dimension(npt, star)); fval(dimension(star)); xsav(dimension(star)); xopt(dimension(star)); gopt(dimension(star)); hq(dimension(star)); pq(dimension(star)); bmat(dimension(ndim, star)); zmat(dimension(npt, star)); sp(dimension(star)); rescon(dimension(star)); step(dimension(star)); pqw(dimension(star)); w(dimension(star)); common_write write(cmn); float half = fem::float0; float one = fem::float0; float zero = fem::float0; int nptm = fem::int0; float rhosq = fem::float0; float recip = fem::float0; float reciq = fem::float0; float test = fem::float0; int kbase = fem::int0; int j = fem::int0; int k = fem::int0; int i = fem::int0; int jp = fem::int0; int itemp = fem::int0; int ipt = fem::int0; int jpt = fem::int0; float temp = fem::float0; int nf = fem::int0; float feas = fem::float0; float bigv = fem::float0; float resid = fem::float0; int jsav = fem::int0; float f = fem::float0; //C //C The arguments N, NPT, M, AMAT, B, X, RHOBEG, IPRINT, XBASE, XPT, FVAL, //C XSAV, XOPT, GOPT, HQ, PQ, BMAT, ZMAT, NDIM, SP and RESCON are the //C same as the corresponding arguments in SUBROUTINE LINCOB. //C KOPT is set to the integer such that XPT(KOPT,.) is the initial trust //C region centre. //C IDZ is going to be set to one, so that every element of Diag(DZ) is //C one in the product ZMAT times Diag(DZ) times ZMAT^T, which is the //C factorization of the leading NPT by NPT submatrix of H. //C STEP, PQW and W are used for working space, the arrays STEP and PQW //C being taken from LINCOB. The length of W must be at least N+NPT. //C //C SUBROUTINE PRELIM provides the elements of XBASE, XPT, BMAT and ZMAT //C for the first iteration, an important feature being that, if any of //C of the columns of XPT is an infeasible point, then the largest of //C the constraint violations there is at least 0.2*RHOBEG. It also sets //C the initial elements of FVAL, XOPT, GOPT, HQ, PQ, SP and RESCON. //C //C Set some constants. //C half = 0.5e0; one = 1.0e0; zero = 0.0e0; nptm = npt - n - 1; rhosq = rhobeg * rhobeg; recip = one / rhosq; reciq = fem::dsqrt(half) / rhosq; test = 0.2e0 * rhobeg; idz = 1; kbase = 1; //C //C Set the initial elements of XPT, BMAT, SP and ZMAT to zero. //C FEM_DO_SAFE(j, 1, n) { xbase(j) = x(j); FEM_DO_SAFE(k, 1, npt) { xpt(k, j) = zero; } FEM_DO_SAFE(i, 1, ndim) { bmat(i, j) = zero; } } FEM_DO_SAFE(k, 1, npt) { sp(k) = zero; FEM_DO_SAFE(j, 1, npt - n - 1) { zmat(k, j) = zero; } } //C //C Set the nonzero coordinates of XPT(K,.), K=1,2,...,min[2*N+1,NPT], //C but they may be altered later to make a constraint violation //C sufficiently large. The initial nonzero elements of BMAT and of //C the first min[N,NPT-N-1] columns of ZMAT are set also. //C FEM_DO_SAFE(j, 1, n) { xpt(j + 1, j) = rhobeg; if (j < npt - n) { jp = n + j + 1; xpt(jp, j) = -rhobeg; bmat(j + 1, j) = half / rhobeg; bmat(jp, j) = -half / rhobeg; zmat(1, j) = -reciq - reciq; zmat(j + 1, j) = reciq; zmat(jp, j) = reciq; } else { bmat(1, j) = -one / rhobeg; bmat(j + 1, j) = one / rhobeg; bmat(npt + j, j) = -half * rhosq; } } //C //C Set the remaining initial nonzero elements of XPT and ZMAT when the //C number of interpolation points exceeds 2*N+1. //C if (npt > 2 * n + 1) { FEM_DO_SAFE(k, n + 1, npt - n - 1) { itemp = (k - 1) / n; ipt = k - itemp * n; jpt = ipt + itemp; if (jpt > n) { jpt = jpt - n; } xpt(n + k + 1, ipt) = rhobeg; xpt(n + k + 1, jpt) = rhobeg; zmat(1, k) = recip; zmat(ipt + 1, k) = -recip; zmat(jpt + 1, k) = -recip; zmat(n + k + 1, k) = recip; } } //C //C Update the constraint right hand sides to allow for the shift XBASE. //C if (m > 0) { FEM_DO_SAFE(j, 1, m) { temp = zero; FEM_DO_SAFE(i, 1, n) { temp += amat(i, j) * xbase(i); } b(j) = b(j) - temp; } } //C //C Go through the initial points, shifting every infeasible point if //C necessary so that its constraint violation is at least 0.2*RHOBEG. //C FEM_DO_SAFE(nf, 1, npt) { feas = one; bigv = zero; j = 0; statement_80: j++; if (j <= m && nf >= 2) { resid = -b(j); FEM_DO_SAFE(i, 1, n) { resid += xpt(nf, i) * amat(i, j); } if (resid <= bigv) { goto statement_80; } bigv = resid; jsav = j; if (resid <= test) { feas = -one; goto statement_80; } feas = zero; } if (feas < zero) { FEM_DO_SAFE(i, 1, n) { step(i) = xpt(nf, i) + (test - bigv) * amat(i, jsav); } FEM_DO_SAFE(k, 1, npt) { sp(npt + k) = zero; FEM_DO_SAFE(j, 1, n) { sp(npt + k) += xpt(k, j) * step(j); } } update(n, npt, xpt, bmat, zmat, idz, ndim, sp, step, kbase, nf, pqw, w); FEM_DO_SAFE(i, 1, n) { xpt(nf, i) = step(i); } } //C //C Calculate the objective function at the current interpolation point, //C and set KOPT to the index of the first trust region centre. //C FEM_DO_SAFE(j, 1, n) { x(j) = xbase(j) + xpt(nf, j); } f = feas; calfun(cmn, n, x, f); if (iprint == 3) { { write_loop wloop(cmn, 6, "(/,4x,'Function number',i6,' F =',1p,d18.10," "' The corresponding X is:',/(2x,5d15.6))"); wloop, nf, f; FEM_DO_SAFE(i, 1, n) { wloop, x(i); } } } if (nf == 1) { kopt = 1; } else if (f < fval(kopt) && feas > zero) { kopt = nf; } fval(nf) = f; } //C //C Set PQ for the first quadratic model. //C FEM_DO_SAFE(j, 1, nptm) { w(j) = zero; FEM_DO_SAFE(k, 1, npt) { w(j) += zmat(k, j) * fval(k); } } FEM_DO_SAFE(k, 1, npt) { pq(k) = zero; FEM_DO_SAFE(j, 1, nptm) { pq(k) += zmat(k, j) * w(j); } } //C //C Set XOPT, SP, GOPT and HQ for the first quadratic model. //C FEM_DO_SAFE(j, 1, n) { xopt(j) = xpt(kopt, j); xsav(j) = xbase(j) + xopt(j); gopt(j) = zero; } FEM_DO_SAFE(k, 1, npt) { sp(k) = zero; FEM_DO_SAFE(j, 1, n) { sp(k) += xpt(k, j) * xopt(j); } temp = pq(k) * sp(k); FEM_DO_SAFE(j, 1, n) { gopt(j) += fval(k) * bmat(k, j) + temp * xpt(k, j); } } FEM_DO_SAFE(i, 1, (n * n + n) / 2) { hq(i) = zero; } //C //C Set the initial elements of RESCON. //C FEM_DO_SAFE(j, 1, m) { temp = b(j); FEM_DO_SAFE(i, 1, n) { temp = temp - xopt(i) * amat(i, j); } temp = fem::dmax1(temp, zero); if (temp >= rhobeg) { temp = -temp; } rescon(j) = temp; } } void qmstep( int const& n, int const& npt, int const& m, arr_cref<float, 2> amat, arr_cref<float> /* b */, arr_cref<float, 2> xpt, arr_cref<float> xopt, int const& nact, arr_cref<int> iact, arr_cref<float> rescon, arr_cref<float, 2> qfac, int const& kopt, int const& knew, float const& del, arr_ref<float> step, arr_ref<float> gl, arr_cref<float> pqw, arr_ref<float> rstat, arr_ref<float> w, int& ifeas) { amat(dimension(n, star)); xpt(dimension(npt, star)); xopt(dimension(star)); iact(dimension(star)); rescon(dimension(star)); qfac(dimension(n, star)); step(dimension(star)); gl(dimension(star)); pqw(dimension(star)); rstat(dimension(star)); w(dimension(star)); float half = fem::float0; float one = fem::float0; float tenth = fem::float0; float zero = fem::float0; float test = fem::float0; int k = fem::int0; float temp = fem::float0; int j = fem::int0; int i = fem::int0; int iflag = fem::int0; float vbig = fem::float0; float ss = fem::float0; float sp = fem::float0; float stp = fem::float0; float vlag = fem::float0; int ksav = fem::int0; float stpsav = fem::float0; float gg = fem::float0; float vgrad = fem::float0; float ghg = fem::float0; float vnew = fem::float0; float ww = fem::float0; float bigv = fem::float0; float ctol = fem::float0; float sum = fem::float0; float resmax = fem::float0; int jsav = fem::int0; //C //C N, NPT, M, AMAT, B, XPT, XOPT, NACT, IACT, RESCON, QFAC, KOPT are the //C same as the terms with these names in SUBROUTINE LINCOB. //C KNEW is the index of the interpolation point that is going to be moved. //C DEL is the current restriction on the length of STEP, which is never //C greater than the current trust region radius DELTA. //C STEP will be set to the required step from XOPT to the new point. //C GL must be set on entry to the gradient of LFUNC at XBASE, where LFUNC //C is the KNEW-th Lagrange function. It is used also for some other //C gradients of LFUNC. //C PQW provides the second derivative parameters of LFUNC. //C RSTAT and W are used for working space. Their lengths must be at least //C M and N, respectively. RSTAT(J) is set to -1.0, 0.0, or 1.0 if the //C J-th constraint is irrelevant, active, or both inactive and relevant, //C respectively. //C IFEAS will be set to 0 or 1 if XOPT+STEP is infeasible or feasible. //C half = 0.5e0; one = 1.0e0; tenth = 0.1e0; zero = 0.0e0; test = 0.2e0 * del; //C //C Replace GL by the gradient of LFUNC at the trust region centre, and //C set the elements of RSTAT. //C FEM_DO_SAFE(k, 1, npt) { temp = zero; FEM_DO_SAFE(j, 1, n) { temp += xpt(k, j) * xopt(j); } temp = pqw(k) * temp; FEM_DO_SAFE(i, 1, n) { gl(i) += temp * xpt(k, i); } } if (m > 0) { FEM_DO_SAFE(j, 1, m) { rstat(j) = one; if (fem::dabs(rescon(j)) >= del) { rstat(j) = -one; } } FEM_DO_SAFE(k, 1, nact) { rstat(iact(k)) = zero; } } //C //C Find the greatest modulus of LFUNC on a line through XOPT and //C another interpolation point within the trust region. //C iflag = 0; vbig = zero; FEM_DO_SAFE(k, 1, npt) { if (k == kopt) { goto statement_60; } ss = zero; sp = zero; FEM_DO_SAFE(i, 1, n) { temp = xpt(k, i) - xopt(i); ss += temp * temp; sp += gl(i) * temp; } stp = -del / fem::dsqrt(ss); if (k == knew) { if (sp * (sp - one) < zero) { stp = -stp; } vlag = fem::dabs(stp * sp) + stp * stp * fem::dabs(sp - one); } else { vlag = fem::dabs(stp * (one - stp) * sp); } if (vlag > vbig) { ksav = k; stpsav = stp; vbig = vlag; } statement_60:; } //C //C Set STEP to the move that gives the greatest modulus calculated above. //C This move may be replaced by a steepest ascent step from XOPT. //C gg = zero; FEM_DO_SAFE(i, 1, n) { gg += fem::pow2(gl(i)); step(i) = stpsav * (xpt(ksav, i) - xopt(i)); } vgrad = del * fem::dsqrt(gg); if (vgrad <= tenth * vbig) { goto statement_220; } //C //C Make the replacement if it provides a larger value of VBIG. //C ghg = zero; FEM_DO_SAFE(k, 1, npt) { temp = zero; FEM_DO_SAFE(j, 1, n) { temp += xpt(k, j) * gl(j); } ghg += pqw(k) * temp * temp; } vnew = vgrad + fem::dabs(half * del * del * ghg / gg); if (vnew > vbig) { vbig = vnew; stp = del / fem::dsqrt(gg); if (ghg < zero) { stp = -stp; } FEM_DO_SAFE(i, 1, n) { step(i) = stp * gl(i); } } if (nact == 0 || nact == n) { goto statement_220; } //C //C Overwrite GL by its projection. Then set VNEW to the greatest //C value of |LFUNC| on the projected gradient from XOPT subject to //C the trust region bound. If VNEW is sufficiently large, then STEP //C may be changed to a move along the projected gradient. //C FEM_DO_SAFE(k, nact + 1, n) { w(k) = zero; FEM_DO_SAFE(i, 1, n) { w(k) += gl(i) * qfac(i, k); } } gg = zero; FEM_DO_SAFE(i, 1, n) { gl(i) = zero; FEM_DO_SAFE(k, nact + 1, n) { gl(i) += qfac(i, k) * w(k); } gg += fem::pow2(gl(i)); } vgrad = del * fem::dsqrt(gg); if (vgrad <= tenth * vbig) { goto statement_220; } ghg = zero; FEM_DO_SAFE(k, 1, npt) { temp = zero; FEM_DO_SAFE(j, 1, n) { temp += xpt(k, j) * gl(j); } ghg += pqw(k) * temp * temp; } vnew = vgrad + fem::dabs(half * del * del * ghg / gg); //C //C Set W to the possible move along the projected gradient. //C stp = del / fem::dsqrt(gg); if (ghg < zero) { stp = -stp; } ww = zero; FEM_DO_SAFE(i, 1, n) { w(i) = stp * gl(i); ww += fem::pow2(w(i)); } //C //C Set STEP to W if W gives a sufficiently large value of the modulus //C of the Lagrange function, and if W either preserves feasibility //C or gives a constraint violation of at least 0.2*DEL. The purpose //C of CTOL below is to provide a check on feasibility that includes //C a tolerance for contributions from computer rounding errors. //C if (vnew / vbig >= 0.2e0) { ifeas = 1; bigv = zero; j = 0; statement_170: j++; if (j <= m) { if (rstat(j) == one) { temp = -rescon(j); FEM_DO_SAFE(i, 1, n) { temp += w(i) * amat(i, j); } bigv = fem::dmax1(bigv, temp); } if (bigv < test) { goto statement_170; } ifeas = 0; } ctol = zero; temp = 0.01e0 * fem::dsqrt(ww); if (bigv > zero && bigv < temp) { FEM_DO_SAFE(k, 1, nact) { j = iact(k); sum = zero; FEM_DO_SAFE(i, 1, n) { sum += w(i) * amat(i, j); } ctol = fem::dmax1(ctol, fem::dabs(sum)); } } if (bigv <= 10.0e0 * ctol || bigv >= test) { FEM_DO_SAFE(i, 1, n) { step(i) = w(i); } goto statement_260; } } //C //C Calculate the greatest constraint violation at XOPT+STEP with STEP at //C its original value. Modify STEP if this violation is unacceptable. //C statement_220: ifeas = 1; bigv = zero; resmax = zero; j = 0; statement_230: j++; if (j <= m) { if (rstat(j) < zero) { goto statement_230; } temp = -rescon(j); FEM_DO_SAFE(i, 1, n) { temp += step(i) * amat(i, j); } resmax = fem::dmax1(resmax, temp); if (temp < test) { if (temp <= bigv) { goto statement_230; } bigv = temp; jsav = j; ifeas = -1; goto statement_230; } ifeas = 0; } if (ifeas == - 1) { FEM_DO_SAFE(i, 1, n) { step(i) += (test - bigv) * amat(i, jsav); } ifeas = 0; } //C //C Return the calculated STEP and the value of IFEAS. //C statement_260:; } void trstep( int const& n, int const& npt, int const& m, arr_cref<float, 2> amat, arr_cref<float> b, arr_cref<float, 2> xpt, arr_cref<float> hq, arr_cref<float> pq, int& nact, arr_ref<int> iact, arr_cref<float> rescon, arr_ref<float, 2> qfac, arr_ref<float> rfac, float& snorm, arr_ref<float> step, arr_ref<float> g, arr_ref<float> resnew, arr_ref<float> resact, arr_ref<float> d, arr_ref<float> dw, arr_ref<float> w) { amat(dimension(n, star)); b(dimension(star)); xpt(dimension(npt, star)); hq(dimension(star)); pq(dimension(star)); iact(dimension(star)); rescon(dimension(star)); qfac(dimension(n, star)); rfac(dimension(star)); step(dimension(star)); g(dimension(star)); resnew(dimension(star)); resact(dimension(star)); d(dimension(star)); dw(dimension(star)); w(dimension(star)); float half = fem::float0; float one = fem::float0; float tiny = fem::float0; float zero = fem::float0; float ctest = fem::float0; float snsq = fem::float0; int j = fem::int0; int k = fem::int0; int i = fem::int0; float ss = fem::float0; float reduct = fem::float0; int ncall = fem::int0; float scale = fem::float0; float resmax = fem::float0; float gamma = fem::float0; int ir = fem::int0; float temp = fem::float0; float rhs = fem::float0; float ds = fem::float0; float dd = fem::float0; float sum = fem::float0; float ad = fem::float0; float adw = fem::float0; int icount = fem::int0; float alpbd = fem::float0; float dg = fem::float0; float alpha = fem::float0; int ih = fem::int0; float dgd = fem::float0; float alpht = fem::float0; float alphm = fem::float0; int jsav = fem::int0; float beta = fem::float0; float wgd = fem::float0; //C //C N, NPT, M, AMAT, B, XPT, HQ, PQ, NACT, IACT, RESCON, QFAC and RFAC //C are the same as the terms with these names in LINCOB. If RESCON(J) //C is negative, then |RESCON(J)| must be no less than the trust region //C radius, so that the J-th constraint can be ignored. //C SNORM is set to the trust region radius DELTA initially. On the //C return, however, it is the length of the calculated STEP, which is //C set to zero if the constraints do not allow a long enough step. //C STEP is the total calculated step so far from the trust region centre, //C its final value being given by the sequence of CG iterations, which //C terminate if the trust region boundary is reached. //C G must be set on entry to the gradient of the quadratic model at the //C trust region centre. It is used as working space, however, and is //C always the gradient of the model at the current STEP, except that //C on return the value of G(1) is set to ONE instead of to ZERO if //C and only if GETACT is called more than once. //C RESNEW, RESACT, D, DW and W are used for working space. A negative //C value of RESNEW(J) indicates that the J-th constraint does not //C restrict the CG steps of the current trust region calculation, a //C zero value of RESNEW(J) indicates that the J-th constraint is active, //C and otherwise RESNEW(J) is set to the greater of TINY and the actual //C residual of the J-th constraint for the current STEP. RESACT holds //C the residuals of the active constraints, which may be positive. //C D is the search direction of each line search. DW is either another //C search direction or the change in gradient along D. The length of W //C must be at least MAX[M,2*N]. //C //C Set some numbers for the conjugate gradient iterations. //C half = 0.5e0; one = 1.0e0; tiny = 1.0e-60; zero = 0.0e0; ctest = 0.01e0; snsq = snorm * snorm; //C //C Set the initial elements of RESNEW, RESACT and STEP. //C if (m > 0) { FEM_DO_SAFE(j, 1, m) { resnew(j) = rescon(j); if (rescon(j) >= snorm) { resnew(j) = -one; } else if (rescon(j) >= zero) { resnew(j) = fem::dmax1(resnew(j), tiny); } } if (nact > 0) { FEM_DO_SAFE(k, 1, nact) { resact(k) = rescon(iact(k)); resnew(iact(k)) = zero; } } } FEM_DO_SAFE(i, 1, n) { step(i) = zero; } ss = zero; reduct = zero; ncall = 0; //C //C GETACT picks the active set for the current STEP. It also sets DW to //C the vector closest to -G that is orthogonal to the normals of the //C active constraints. DW is scaled to have length 0.2*SNORM, as then //C a move of DW from STEP is allowed by the linear constraints. //C statement_40: ncall++; getact(n, m, amat, b, nact, iact, qfac, rfac, snorm, resnew, resact, g, dw, w, w(n + 1)); if (w(n + 1) == zero) { goto statement_320; } scale = 0.2e0 * snorm / fem::dsqrt(w(n + 1)); FEM_DO_SAFE(i, 1, n) { dw(i) = scale * dw(i); } //C //C If the modulus of the residual of an active constraint is substantial, //C then set D to the shortest move from STEP to the boundaries of the //C active constraints. //C resmax = zero; if (nact > 0) { FEM_DO_SAFE(k, 1, nact) { resmax = fem::dmax1(resmax, resact(k)); } } gamma = zero; if (resmax > 1.0e-4 * snorm) { ir = 0; FEM_DO_SAFE(k, 1, nact) { temp = resact(k); if (k >= 2) { FEM_DO_SAFE(i, 1, k - 1) { ir++; temp = temp - rfac(ir) * w(i); } } ir++; w(k) = temp / rfac(ir); } FEM_DO_SAFE(i, 1, n) { d(i) = zero; FEM_DO_SAFE(k, 1, nact) { d(i) += w(k) * qfac(i, k); } } //C //C The vector D that has just been calculated is also the shortest move //C from STEP+DW to the boundaries of the active constraints. Set GAMMA //C to the greatest steplength of this move that satisfies the trust //C region bound. //C rhs = snsq; ds = zero; dd = zero; FEM_DO_SAFE(i, 1, n) { sum = step(i) + dw(i); rhs = rhs - sum * sum; ds += d(i) * sum; dd += fem::pow2(d(i)); } if (rhs > zero) { temp = fem::dsqrt(ds * ds + dd * rhs); if (ds <= zero) { gamma = (temp - ds) / dd; } else { gamma = rhs / (temp + ds); } } //C //C Reduce the steplength GAMMA if necessary so that the move along D //C also satisfies the linear constraints. //C j = 0; statement_110: if (gamma > zero) { j++; if (resnew(j) > zero) { ad = zero; adw = zero; FEM_DO_SAFE(i, 1, n) { ad += amat(i, j) * d(i); adw += amat(i, j) * dw(i); } if (ad > zero) { temp = fem::dmax1((resnew(j) - adw) / ad, zero); gamma = fem::dmin1(gamma, temp); } } if (j < m) { goto statement_110; } } gamma = fem::dmin1(gamma, one); } //C //C Set the next direction for seeking a reduction in the model function //C subject to the trust region bound and the linear constraints. //C if (gamma <= zero) { FEM_DO_SAFE(i, 1, n) { d(i) = dw(i); } icount = nact; } else { FEM_DO_SAFE(i, 1, n) { d(i) = dw(i) + gamma * d(i); } icount = nact - 1; } alpbd = one; //C //C Set ALPHA to the steplength from STEP along D to the trust region //C boundary. Return if the first derivative term of this step is //C sufficiently small or if no further progress is possible. //C statement_150: icount++; rhs = snsq - ss; if (rhs <= zero) { goto statement_320; } dg = zero; ds = zero; dd = zero; FEM_DO_SAFE(i, 1, n) { dg += d(i) * g(i); ds += d(i) * step(i); dd += fem::pow2(d(i)); } if (dg >= zero) { goto statement_320; } temp = fem::dsqrt(rhs * dd + ds * ds); if (ds <= zero) { alpha = (temp - ds) / dd; } else { alpha = rhs / (temp + ds); } if (-alpha * dg <= ctest * reduct) { goto statement_320; } //C //C Set DW to the change in gradient along D. //C ih = 0; FEM_DO_SAFE(j, 1, n) { dw(j) = zero; FEM_DO_SAFE(i, 1, j) { ih++; if (i < j) { dw(j) += hq(ih) * d(i); } dw(i) += hq(ih) * d(j); } } FEM_DO_SAFE(k, 1, npt) { temp = zero; FEM_DO_SAFE(j, 1, n) { temp += xpt(k, j) * d(j); } temp = pq(k) * temp; FEM_DO_SAFE(i, 1, n) { dw(i) += temp * xpt(k, i); } } //C //C Set DGD to the curvature of the model along D. Then reduce ALPHA if //C necessary to the value that minimizes the model. //C dgd = zero; FEM_DO_SAFE(i, 1, n) { dgd += d(i) * dw(i); } alpht = alpha; if (dg + alpha * dgd > zero) { alpha = -dg / dgd; } //C //C Make a further reduction in ALPHA if necessary to preserve feasibility, //C and put some scalar products of D with constraint gradients in W. //C alphm = alpha; jsav = 0; if (m > 0) { FEM_DO_SAFE(j, 1, m) { ad = zero; if (resnew(j) > zero) { FEM_DO_SAFE(i, 1, n) { ad += amat(i, j) * d(i); } if (alpha * ad > resnew(j)) { alpha = resnew(j) / ad; jsav = j; } } w(j) = ad; } } alpha = fem::dmax1(alpha, alpbd); alpha = fem::dmin1(alpha, alphm); if (icount == nact) { alpha = fem::dmin1(alpha, one); } //C //C Update STEP, G, RESNEW, RESACT and REDUCT. //C ss = zero; FEM_DO_SAFE(i, 1, n) { step(i) += alpha * d(i); ss += fem::pow2(step(i)); g(i) += alpha * dw(i); } if (m > 0) { FEM_DO_SAFE(j, 1, m) { if (resnew(j) > zero) { resnew(j) = fem::dmax1(resnew(j) - alpha * w(j), tiny); } } } if (icount == nact && nact > 0) { FEM_DO_SAFE(k, 1, nact) { resact(k) = (one - gamma) * resact(k); } } reduct = reduct - alpha * (dg + half * alpha * dgd); //C //C Test for termination. Branch to label 40 if there is a new active //C constraint and if the distance from STEP to the trust region //C boundary is at least 0.2*SNORM. //C if (alpha == alpht) { goto statement_320; } temp = -alphm * (dg + half * alphm * dgd); if (temp <= ctest * reduct) { goto statement_320; } if (jsav > 0) { if (ss <= 0.64e0 * snsq) { goto statement_40; } goto statement_320; } if (icount == n) { goto statement_320; } //C //C Calculate the next search direction, which is conjugate to the //C previous one except in the case ICOUNT=NACT. //C if (nact > 0) { FEM_DO_SAFE(j, nact + 1, n) { w(j) = zero; FEM_DO_SAFE(i, 1, n) { w(j) += g(i) * qfac(i, j); } } FEM_DO_SAFE(i, 1, n) { temp = zero; FEM_DO_SAFE(j, nact + 1, n) { temp += qfac(i, j) * w(j); } w(n + i) = temp; } } else { FEM_DO_SAFE(i, 1, n) { w(n + i) = g(i); } } if (icount == nact) { beta = zero; } else { wgd = zero; FEM_DO_SAFE(i, 1, n) { wgd += w(n + i) * dw(i); } beta = wgd / dgd; } FEM_DO_SAFE(i, 1, n) { d(i) = -w(n + i) + beta * d(i); } alpbd = zero; goto statement_150; //C //C Return from the subroutine. //C statement_320: snorm = zero; if (reduct > zero) { snorm = fem::dsqrt(ss); } g(1) = zero; if (ncall > 1) { g(1) = one; } } void lincob( common& cmn, int const& n, int const& npt, int const& m, arr_cref<float, 2> amat, arr_ref<float> b, arr_ref<float> x, float const& rhobeg, float const& rhoend, int const& iprint, int const& maxfun, arr_ref<float> xbase, arr_ref<float, 2> xpt, arr_ref<float> fval, arr_ref<float> xsav, arr_ref<float> xopt, arr_ref<float> gopt, arr_ref<float> hq, arr_ref<float> pq, arr_ref<float, 2> bmat, arr_ref<float, 2> zmat, int const& ndim, arr_ref<float> step, arr_ref<float> sp, arr_ref<float> xnew, arr_ref<int> iact, arr_ref<float> rescon, arr_ref<float, 2> qfac, arr_ref<float> rfac, arr_ref<float> pqw, arr_ref<float> w) { amat(dimension(n, star)); b(dimension(star)); x(dimension(star)); xbase(dimension(star)); xpt(dimension(npt, star)); fval(dimension(star)); xsav(dimension(star)); xopt(dimension(star)); gopt(dimension(star)); hq(dimension(star)); pq(dimension(star)); bmat(dimension(ndim, star)); zmat(dimension(npt, star)); step(dimension(star)); sp(dimension(star)); xnew(dimension(star)); iact(dimension(star)); rescon(dimension(star)); qfac(dimension(n, star)); rfac(dimension(star)); pqw(dimension(star)); w(dimension(star)); common_write write(cmn); float half = fem::float0; float one = fem::float0; float tenth = fem::float0; float zero = fem::float0; int np = fem::int0; int nh = fem::int0; int nptm = fem::int0; int kopt = fem::int0; int idz = fem::int0; int nf = fem::int0; float fopt = fem::float0; float rho = fem::float0; float delta = fem::float0; int ifeas = fem::int0; int nact = fem::int0; int itest = fem::int0; int knew = fem::int0; int nvala = fem::int0; int nvalb = fem::int0; float fsave = fem::float0; float xoptsq = fem::float0; int i = fem::int0; float qoptsq = fem::float0; int k = fem::int0; float sum = fem::float0; int ip = fem::int0; int j = fem::int0; float sumz = fem::float0; float temp = fem::float0; int ih = fem::int0; float delsav = fem::float0; int ksave = fem::int0; float snorm = fem::float0; float del = fem::float0; float vquad = fem::float0; float xdiff = fem::float0; float f = fem::float0; float diff = fem::float0; float vqalt = fem::float0; float dffalt = fem::float0; float ratio = fem::float0; float ssq = fem::float0; float distsq = fem::float0; static const char* format_590 = "(4x,'Least value of F =',1p,d23.15,9x,'The corresponding X is:',/(2x," "5d15.6))"; //C //C The arguments N, NPT, M, X, RHOBEG, RHOEND, IPRINT and MAXFUN are //C identical to the corresponding arguments in SUBROUTINE LINCOA. //C AMAT is a matrix whose columns are the constraint gradients, scaled //C so that they have unit length. //C B contains on entry the right hand sides of the constraints, scaled //C as above, but later B is modified for variables relative to XBASE. //C XBASE holds a shift of origin that should reduce the contributions //C from rounding errors to values of the model and Lagrange functions. //C XPT contains the interpolation point coordinates relative to XBASE. //C FVAL holds the values of F at the interpolation points. //C XSAV holds the best feasible vector of variables so far, without any //C shift of origin. //C XOPT is set to XSAV-XBASE, which is the displacement from XBASE of //C the feasible vector of variables that provides the least calculated //C F so far, this vector being the current trust region centre. //C GOPT holds the gradient of the quadratic model at XSAV = XBASE+XOPT. //C HQ holds the explicit second derivatives of the quadratic model. //C PQ contains the parameters of the implicit second derivatives of the //C quadratic model. //C BMAT holds the last N columns of the big inverse matrix H. //C ZMAT holds the factorization of the leading NPT by NPT submatrix //C of H, this factorization being ZMAT times Diag(DZ) times ZMAT^T, //C where the elements of DZ are plus or minus one, as specified by IDZ. //C NDIM is the first dimension of BMAT and has the value NPT+N. //C STEP is employed for trial steps from XOPT. It is also used for working //C space when XBASE is shifted and in PRELIM. //C SP is reserved for the scalar products XOPT^T XPT(K,.), K=1,2,...,NPT, //C followed by STEP^T XPT(K,.), K=1,2,...,NPT. //C XNEW is the displacement from XBASE of the vector of variables for //C the current calculation of F, except that SUBROUTINE TRSTEP uses it //C for working space. //C IACT is an integer array for the indices of the active constraints. //C RESCON holds useful information about the constraint residuals. Every //C nonnegative RESCON(J) is the residual of the J-th constraint at the //C current trust region centre. Otherwise, if RESCON(J) is negative, the //C J-th constraint holds as a strict inequality at the trust region //C centre, its residual being at least |RESCON(J)|; further, the value //C of |RESCON(J)| is at least the current trust region radius DELTA. //C QFAC is the orthogonal part of the QR factorization of the matrix of //C active constraint gradients, these gradients being ordered in //C accordance with IACT. When NACT is less than N, columns are added //C to QFAC to complete an N by N orthogonal matrix, which is important //C for keeping calculated steps sufficiently close to the boundaries //C of the active constraints. //C RFAC is the upper triangular part of this QR factorization, beginning //C with the first diagonal element, followed by the two elements in the //C upper triangular part of the second column and so on. //C PQW is used for working space, mainly for storing second derivative //C coefficients of quadratic functions. Its length is NPT+N. //C The array W is also used for working space. The required number of //C elements, namely MAX[M+3*N,2*M+N,2*NPT], is set in LINCOA. //C //C Set some constants. //C half = 0.5e0; one = 1.0e0; tenth = 0.1e0; zero = 0.0e0; np = n + 1; nh = (n * np) / 2; nptm = npt - np; //C //C Set the elements of XBASE, XPT, FVAL, XSAV, XOPT, GOPT, HQ, PQ, BMAT, //C ZMAT and SP for the first iteration. An important feature is that, //C if the interpolation point XPT(K,.) is not feasible, where K is any //C integer from [1,NPT], then a change is made to XPT(K,.) if necessary //C so that the constraint violation is at least 0.2*RHOBEG. Also KOPT //C is set so that XPT(KOPT,.) is the initial trust region centre. //C prelim(cmn, n, npt, m, amat, b, x, rhobeg, iprint, xbase, xpt, fval, xsav, xopt, gopt, kopt, hq, pq, bmat, zmat, idz, ndim, sp, rescon, step, pqw, w); //C //C Begin the iterative procedure. //C nf = npt; fopt = fval(kopt); rho = rhobeg; delta = rho; ifeas = 0; nact = 0; itest = 3; statement_10: knew = 0; nvala = 0; nvalb = 0; //C //C Shift XBASE if XOPT may be too far from XBASE. First make the changes //C to BMAT that do not depend on ZMAT. //C statement_20: fsave = fopt; xoptsq = zero; FEM_DO_SAFE(i, 1, n) { xoptsq += fem::pow2(xopt(i)); } if (xoptsq >= 1.0e4 * delta * delta) { qoptsq = 0.25e0 * xoptsq; FEM_DO_SAFE(k, 1, npt) { sum = zero; FEM_DO_SAFE(i, 1, n) { sum += xpt(k, i) * xopt(i); } sum = sum - half * xoptsq; w(npt + k) = sum; sp(k) = zero; FEM_DO_SAFE(i, 1, n) { xpt(k, i) = xpt(k, i) - half * xopt(i); step(i) = bmat(k, i); w(i) = sum * xpt(k, i) + qoptsq * xopt(i); ip = npt + i; FEM_DO_SAFE(j, 1, i) { bmat(ip, j) += step(i) * w(j) + w(i) * step(j); } } } //C //C Then the revisions of BMAT that depend on ZMAT are calculated. //C FEM_DO_SAFE(k, 1, nptm) { sumz = zero; FEM_DO_SAFE(i, 1, npt) { sumz += zmat(i, k); w(i) = w(npt + i) * zmat(i, k); } FEM_DO_SAFE(j, 1, n) { sum = qoptsq * sumz * xopt(j); FEM_DO_SAFE(i, 1, npt) { sum += w(i) * xpt(i, j); } step(j) = sum; if (k < idz) { sum = -sum; } FEM_DO_SAFE(i, 1, npt) { bmat(i, j) += sum * zmat(i, k); } } FEM_DO_SAFE(i, 1, n) { ip = i + npt; temp = step(i); if (k < idz) { temp = -temp; } FEM_DO_SAFE(j, 1, i) { bmat(ip, j) += temp * step(j); } } } //C //C Update the right hand sides of the constraints. //C if (m > 0) { FEM_DO_SAFE(j, 1, m) { temp = zero; FEM_DO_SAFE(i, 1, n) { temp += amat(i, j) * xopt(i); } b(j) = b(j) - temp; } } //C //C The following instructions complete the shift of XBASE, including the //C changes to the parameters of the quadratic model. //C ih = 0; FEM_DO_SAFE(j, 1, n) { w(j) = zero; FEM_DO_SAFE(k, 1, npt) { w(j) += pq(k) * xpt(k, j); xpt(k, j) = xpt(k, j) - half * xopt(j); } FEM_DO_SAFE(i, 1, j) { ih++; hq(ih) += w(i) * xopt(j) + xopt(i) * w(j); bmat(npt + i, j) = bmat(npt + j, i); } } FEM_DO_SAFE(j, 1, n) { xbase(j) += xopt(j); xopt(j) = zero; xpt(kopt, j) = zero; } } //C //C In the case KNEW=0, generate the next trust region step by calling //C TRSTEP, where SNORM is the current trust region radius initially. //C The final value of SNORM is the length of the calculated step, //C except that SNORM is zero on return if the projected gradient is //C unsuitable for starting the conjugate gradient iterations. //C delsav = delta; ksave = knew; if (knew == 0) { snorm = delta; FEM_DO_SAFE(i, 1, n) { xnew(i) = gopt(i); } trstep(n, npt, m, amat, b, xpt, hq, pq, nact, iact, rescon, qfac, rfac, snorm, step, xnew, w, w(m + 1), pqw, pqw(np), w(m + np)); //C //C A trust region step is applied whenever its length, namely SNORM, is at //C least HALF*DELTA. It is also applied if its length is at least 0.1999 //C times DELTA and if a line search of TRSTEP has caused a change to the //C active set. Otherwise there is a branch below to label 530 or 560. //C temp = half * delta; if (xnew(1) >= half) { temp = 0.1999e0 * delta; } if (snorm <= temp) { delta = half * delta; if (delta <= 1.4e0 * rho) { delta = rho; } nvala++; nvalb++; temp = snorm / rho; if (delsav > rho) { temp = one; } if (temp >= half) { nvala = zero; } if (temp >= tenth) { nvalb = zero; } if (delsav > rho) { goto statement_530; } if (nvala < 5 && nvalb < 3) { goto statement_530; } if (snorm > zero) { ksave = -1; } goto statement_560; } nvala = zero; nvalb = zero; //C //C Alternatively, KNEW is positive. Then the model step is calculated //C within a trust region of radius DEL, after setting the gradient at //C XBASE and the second derivative parameters of the KNEW-th Lagrange //C function in W(1) to W(N) and in PQW(1) to PQW(NPT), respectively. //C } else { del = fem::dmax1(tenth * delta, rho); FEM_DO_SAFE(i, 1, n) { w(i) = bmat(knew, i); } FEM_DO_SAFE(k, 1, npt) { pqw(k) = zero; } FEM_DO_SAFE(j, 1, nptm) { temp = zmat(knew, j); if (j < idz) { temp = -temp; } FEM_DO_SAFE(k, 1, npt) { pqw(k) += temp * zmat(k, j); } } qmstep(n, npt, m, amat, b, xpt, xopt, nact, iact, rescon, qfac, kopt, knew, del, step, w, pqw, w(np), w(np + m), ifeas); } //C //C Set VQUAD to the change to the quadratic model when the move STEP is //C made from XOPT. If STEP is a trust region step, then VQUAD should be //C negative. If it is nonnegative due to rounding errors in this case, //C there is a branch to label 530 to try to improve the model. //C vquad = zero; ih = 0; FEM_DO_SAFE(j, 1, n) { vquad += step(j) * gopt(j); FEM_DO_SAFE(i, 1, j) { ih++; temp = step(i) * step(j); if (i == j) { temp = half * temp; } vquad += temp * hq(ih); } } FEM_DO_SAFE(k, 1, npt) { temp = zero; FEM_DO_SAFE(j, 1, n) { temp += xpt(k, j) * step(j); sp(npt + k) = temp; } vquad += half * pq(k) * temp * temp; } if (ksave == 0 && vquad >= zero) { goto statement_530; } //C //C Calculate the next value of the objective function. The difference //C between the actual new value of F and the value predicted by the //C model is recorded in DIFF. //C statement_220: nf++; if (nf > maxfun) { nf = nf - 1; if (iprint > 0) { write(6, "(/,4x,'Return from LINCOA because CALFUN has been'," "' called MAXFUN times.')"); } goto statement_600; } xdiff = zero; FEM_DO_SAFE(i, 1, n) { xnew(i) = xopt(i) + step(i); x(i) = xbase(i) + xnew(i); xdiff += fem::pow2((x(i) - xsav(i))); } xdiff = fem::dsqrt(xdiff); if (ksave == - 1) { xdiff = rho; } if (xdiff <= tenth * rho || xdiff >= delta + delta) { ifeas = 0; if (iprint > 0) { write(6, "(/,4x,'Return from LINCOA because rounding errors'," "' prevent reasonable changes to X.')"); } goto statement_600; } if (ksave <= 0) { ifeas = 1; } f = dfloat(ifeas); calfun(cmn, n, x, f); if (iprint == 3) { { write_loop wloop(cmn, 6, "(/,4x,'Function number',i6,' F =',1p,d18.10," "' The corresponding X is:',/(2x,5d15.6))"); wloop, nf, f; FEM_DO_SAFE(i, 1, n) { wloop, x(i); } } } if (ksave == - 1) { goto statement_600; } diff = f - fopt - vquad; //C //C If X is feasible, then set DFFALT to the difference between the new //C value of F and the value predicted by the alternative model. //C if (ifeas == 1 && itest < 3) { FEM_DO_SAFE(k, 1, npt) { pqw(k) = zero; w(k) = fval(k) - fval(kopt); } FEM_DO_SAFE(j, 1, nptm) { sum = zero; FEM_DO_SAFE(i, 1, npt) { sum += w(i) * zmat(i, j); } if (j < idz) { sum = -sum; } FEM_DO_SAFE(k, 1, npt) { pqw(k) += sum * zmat(k, j); } } vqalt = zero; FEM_DO_SAFE(k, 1, npt) { sum = zero; FEM_DO_SAFE(j, 1, n) { sum += bmat(k, j) * step(j); } vqalt += sum * w(k); vqalt += pqw(k) * sp(npt + k) * (half * sp(npt + k) + sp(k)); } dffalt = f - fopt - vqalt; } if (itest == 3) { dffalt = diff; itest = 0; } //C //C Pick the next value of DELTA after a trust region step. //C if (ksave == 0) { ratio = (f - fopt) / vquad; if (ratio <= tenth) { delta = half * delta; } else if (ratio <= 0.7e0) { delta = fem::dmax1(half * delta, snorm); } else { temp = fem::dsqrt(2.0e0) * delta; delta = fem::dmax1(half * delta, snorm + snorm); delta = fem::dmin1(delta, temp); } if (delta <= 1.4e0 * rho) { delta = rho; } } //C //C Update BMAT, ZMAT and IDZ, so that the KNEW-th interpolation point //C can be moved. If STEP is a trust region step, then KNEW is zero at //C present, but a positive value is picked by subroutine UPDATE. //C update(n, npt, xpt, bmat, zmat, idz, ndim, sp, step, kopt, knew, pqw, w); if (knew == 0) { if (iprint > 0) { write(6, "(/,4x,'Return from LINCOA because the denominator'," "' of the updating formula is zero.')"); } goto statement_600; } //C //C If ITEST is increased to 3, then the next quadratic model is the //C one whose second derivative matrix is least subject to the new //C interpolation conditions. Otherwise the new model is constructed //C by the symmetric Broyden method in the usual way. //C if (ifeas == 1) { itest++; if (fem::dabs(dffalt) >= tenth * fem::dabs(diff)) { itest = 0; } } //C //C Update the second derivatives of the model by the symmetric Broyden //C method, using PQW for the second derivative parameters of the new //C KNEW-th Lagrange function. The contribution from the old parameter //C PQ(KNEW) is included in the second derivative matrix HQ. W is used //C later for the gradient of the new KNEW-th Lagrange function. //C if (itest < 3) { FEM_DO_SAFE(k, 1, npt) { pqw(k) = zero; } FEM_DO_SAFE(j, 1, nptm) { temp = zmat(knew, j); if (temp != zero) { if (j < idz) { temp = -temp; } FEM_DO_SAFE(k, 1, npt) { pqw(k) += temp * zmat(k, j); } } } ih = 0; FEM_DO_SAFE(i, 1, n) { w(i) = bmat(knew, i); temp = pq(knew) * xpt(knew, i); FEM_DO_SAFE(j, 1, i) { ih++; hq(ih) += temp * xpt(knew, j); } } pq(knew) = zero; FEM_DO_SAFE(k, 1, npt) { pq(k) += diff * pqw(k); } } //C //C Include the new interpolation point with the corresponding updates of //C SP. Also make the changes of the symmetric Broyden method to GOPT at //C the old XOPT if ITEST is less than 3. //C fval(knew) = f; sp(knew) = sp(kopt) + sp(npt + kopt); ssq = zero; FEM_DO_SAFE(i, 1, n) { xpt(knew, i) = xnew(i); ssq += fem::pow2(step(i)); } sp(npt + knew) = sp(npt + kopt) + ssq; if (itest < 3) { FEM_DO_SAFE(k, 1, npt) { temp = pqw(k) * sp(k); FEM_DO_SAFE(i, 1, n) { w(i) += temp * xpt(k, i); } } FEM_DO_SAFE(i, 1, n) { gopt(i) += diff * w(i); } } //C //C Update FOPT, XSAV, XOPT, KOPT, RESCON and SP if the new F is the //C least calculated value so far with a feasible vector of variables. //C if (f < fopt && ifeas == 1) { fopt = f; FEM_DO_SAFE(j, 1, n) { xsav(j) = x(j); xopt(j) = xnew(j); } kopt = knew; snorm = fem::dsqrt(ssq); FEM_DO_SAFE(j, 1, m) { if (rescon(j) >= delta + snorm) { rescon(j) = snorm - rescon(j); } else { rescon(j) += snorm; if (rescon(j) + delta > zero) { temp = b(j); FEM_DO_SAFE(i, 1, n) { temp = temp - xopt(i) * amat(i, j); } temp = fem::dmax1(temp, zero); if (temp >= delta) { temp = -temp; } rescon(j) = temp; } } } FEM_DO_SAFE(k, 1, npt) { sp(k) += sp(npt + k); } //C //C Also revise GOPT when symmetric Broyden updating is applied. //C if (itest < 3) { ih = 0; FEM_DO_SAFE(j, 1, n) { FEM_DO_SAFE(i, 1, j) { ih++; if (i < j) { gopt(j) += hq(ih) * step(i); } gopt(i) += hq(ih) * step(j); } } FEM_DO_SAFE(k, 1, npt) { temp = pq(k) * sp(npt + k); FEM_DO_SAFE(i, 1, n) { gopt(i) += temp * xpt(k, i); } } } } //C //C Replace the current model by the least Frobenius norm interpolant if //C this interpolant gives substantial reductions in the predictions //C of values of F at feasible points. //C if (itest == 3) { FEM_DO_SAFE(k, 1, npt) { pq(k) = zero; w(k) = fval(k) - fval(kopt); } FEM_DO_SAFE(j, 1, nptm) { sum = zero; FEM_DO_SAFE(i, 1, npt) { sum += w(i) * zmat(i, j); } if (j < idz) { sum = -sum; } FEM_DO_SAFE(k, 1, npt) { pq(k) += sum * zmat(k, j); } } FEM_DO_SAFE(j, 1, n) { gopt(j) = zero; FEM_DO_SAFE(i, 1, npt) { gopt(j) += w(i) * bmat(i, j); } } FEM_DO_SAFE(k, 1, npt) { temp = pq(k) * sp(k); FEM_DO_SAFE(i, 1, n) { gopt(i) += temp * xpt(k, i); } } FEM_DO_SAFE(ih, 1, nh) { hq(ih) = zero; } } //C //C If a trust region step has provided a sufficient decrease in F, then //C branch for another trust region calculation. Every iteration that //C takes a model step is followed by an attempt to take a trust region //C step. //C knew = 0; if (ksave > 0) { goto statement_20; } if (ratio >= tenth) { goto statement_20; } //C //C Alternatively, find out if the interpolation points are close enough //C to the best point so far. //C statement_530: distsq = fem::dmax1(delta * delta, 4.0e0 * rho * rho); FEM_DO_SAFE(k, 1, npt) { sum = zero; FEM_DO_SAFE(j, 1, n) { sum += fem::pow2((xpt(k, j) - xopt(j))); } if (sum > distsq) { knew = k; distsq = sum; } } //C //C If KNEW is positive, then branch back for the next iteration, which //C will generate a "model step". Otherwise, if the current iteration //C has reduced F, or if DELTA was above its lower bound when the last //C trust region step was calculated, then try a "trust region" step //C instead. //C if (knew > 0) { goto statement_20; } knew = 0; if (fopt < fsave) { goto statement_20; } if (delsav > rho) { goto statement_20; } //C //C The calculations with the current value of RHO are complete. //C Pick the next value of RHO. //C statement_560: if (rho > rhoend) { delta = half * rho; if (rho > 250.0e0 * rhoend) { rho = tenth * rho; } else if (rho <= 16.0e0 * rhoend) { rho = rhoend; } else { rho = fem::dsqrt(rho * rhoend); } delta = fem::dmax1(delta, rho); if (iprint >= 2) { if (iprint >= 3) { write(6, "(5x)"); } write(6, "(/,4x,'New RHO =',1p,d11.4,5x,'Number of',' function values =',i6)"), rho, nf; { write_loop wloop(cmn, 6, format_590); wloop, fopt; FEM_DO_SAFE(i, 1, n) { wloop, xbase(i) + xopt(i); } } } goto statement_10; } //C //C Return from the calculation, after branching to label 220 for another //C Newton-Raphson step if it has not been tried before. //C if (ksave == - 1) { goto statement_220; } statement_600: if (fopt <= f || ifeas == 0) { FEM_DO_SAFE(i, 1, n) { x(i) = xsav(i); } f = fopt; } if (iprint >= 1) { write(6, "(/,4x,'At the return from LINCOA',5x,'Number of function values =',i6)"), nf; { write_loop wloop(cmn, 6, format_590); wloop, f; FEM_DO_SAFE(i, 1, n) { wloop, x(i); } } } w(1) = f; w(2) = dfloat(nf) + half; } void lincoa( common& cmn, int const& n, int const& npt, int const& m, arr_cref<float, 2> a, int const& ia, arr_cref<float> b, arr_ref<float> x, float const& rhobeg, float const& rhoend, int const& iprint, int const& maxfun, arr_ref<float> w, int& iw) { a(dimension(ia, star)); b(dimension(star)); x(dimension(star)); w(dimension(star)); common_write write(cmn); float zero = fem::float0; float smallx = fem::float0; int np = fem::int0; int nptm = fem::int0; int iamat = fem::int0; int ib = fem::int0; int iflag = fem::int0; int j = fem::int0; float sum = fem::float0; float temp = fem::float0; int i = fem::int0; int ndim = fem::int0; int ixb = fem::int0; int ixp = fem::int0; int ifv = fem::int0; int ixs = fem::int0; int ixo = fem::int0; int igo = fem::int0; int ihq = fem::int0; int ipq = fem::int0; int ibmat = fem::int0; int izmat = fem::int0; int istp = fem::int0; int isp = fem::int0; int ixn = fem::int0; int iac = fem::int0; int irc = fem::int0; int iqf = fem::int0; int irf = fem::int0; int ipqw = fem::int0; //C //C This subroutine seeks the least value of a function of many variables, //C subject to general linear inequality constraints, by a trust region //C method that forms quadratic models by interpolation. Usually there //C is much freedom in each new model after satisfying the interpolation //C conditions, which is taken up by minimizing the Frobenius norm of //C the change to the second derivative matrix of the model. One new //C function value is calculated on each iteration, usually at a point //C where the current model predicts a reduction in the least value so //C far of the objective function subject to the linear constraints. //C Alternatively, a new vector of variables may be chosen to replace //C an interpolation point that may be too far away for reliability, and //C then the new point does not have to satisfy the linear constraints. //C The arguments of the subroutine are as follows. //C //C N must be set to the number of variables and must be at least two. //C NPT must be set to the number of interpolation conditions, which is //C required to be in the interval [N+2,(N+1)(N+2)/2]. Typical choices //C of the author are NPT=N+6 and NPT=2*N+1. Larger values tend to be //C highly inefficent when the number of variables is substantial, due //C to the amount of work and extra difficulty of adjusting more points. //C M must be set to the number of linear inequality constraints. //C A is a matrix whose columns are the constraint gradients, which are //C required to be nonzero. //C IA is the first dimension of the array A, which must be at least N. //C B is the vector of right hand sides of the constraints, the J-th //C constraint being that the scalar product of A(.,J) with X(.) is at //C most B(J). The initial vector X(.) is made feasible by increasing //C the value of B(J) if necessary. //C X is the vector of variables. Initial values of X(1),X(2),...,X(N) //C must be supplied. If they do not satisfy the constraints, then B //C is increased as mentioned above. X contains on return the variables //C that have given the least calculated F subject to the constraints. //C RHOBEG and RHOEND must be set to the initial and final values of a //C trust region radius, so both must be positive with RHOEND<=RHOBEG. //C Typically, RHOBEG should be about one tenth of the greatest expected //C change to a variable, and RHOEND should indicate the accuracy that //C is required in the final values of the variables. //C The value of IPRINT should be set to 0, 1, 2 or 3, which controls the //C amount of printing. Specifically, there is no output if IPRINT=0 and //C there is output only at the return if IPRINT=1. Otherwise, the best //C feasible vector of variables so far and the corresponding value of //C the objective function are printed whenever RHO is reduced, where //C RHO is the current lower bound on the trust region radius. Further, //C each new value of F with its variables are output if IPRINT=3. //C MAXFUN must be set to an upper bound on the number of calls of CALFUN, //C its value being at least NPT+1. //C W is an array used for working space. Its length must be at least //C M*(2+N) + NPT*(4+N+NPT) + N*(9+3*N) + MAX [ M+3*N, 2*M+N, 2*NPT ]. //C On return, W(1) is set to the final value of F, and W(2) is set to //C the total number of function evaluations plus 0.5. //C //C SUBROUTINE CALFUN (N,X,F) has to be provided by the user. It must set //C F to the value of the objective function for the variables X(1), //C X(2),...,X(N). The value of the argument F is positive when CALFUN //C is called if and only if the current X satisfies the constraints //C to working accuracy. //C //C Check that N, NPT and MAXFUN are acceptable. //C zero = 0.0e0; smallx = 1.0e-6 * rhoend; np = n + 1; nptm = npt - np; if (n <= 1) { write(6, "(/,4x,'Return from LINCOA because N is less than 2.')"); goto statement_80; } if (npt < n + 2 || npt > ((n + 2) * np) / 2) { write(6, "(/,4x,'Return from LINCOA because NPT is not in'," "' the required interval.')"); goto statement_80; } if (maxfun <= npt) { write(6, "(/,4x,'Return from LINCOA because MAXFUN is less',' than NPT+1.')"); goto statement_80; } //C //C Normalize the constraints, and copy the resultant constraint matrix //C and right hand sides into working space, after increasing the right //C hand sides if necessary so that the starting point is feasible. //C iamat = fem::max0(m + 3 * n, 2 * m + n, 2 * npt) + 1; ib = iamat + m * n; iflag = 0; if (m > 0) { iw = iamat - 1; FEM_DO_SAFE(j, 1, m) { sum = zero; temp = zero; FEM_DO_SAFE(i, 1, n) { sum += a(i, j) * x(i); temp += fem::pow2(a(i, j)); } if (temp == zero) { write(6, "(/,4x,'Return from LINCOA because the gradient of'," "' a constraint is zero.')"); goto statement_80; } temp = fem::dsqrt(temp); if (sum - b(j) > smallx * temp) { iflag = 1; } w(ib + j - 1) = fem::dmax1(b(j), sum) / temp; FEM_DO_SAFE(i, 1, n) { iw++; w(iw) = a(i, j) / temp; } } } if (iflag == 1) { if (iprint > 0) { write(6, "(/,4x,'LINCOA has made the initial X feasible by'," "' increasing part(s) of B.')"); } } //C //C Partition the working space array, so that different parts of it can be //C treated separately by the subroutine that performs the main calculation. //C ndim = npt + n; ixb = ib + m; ixp = ixb + n; ifv = ixp + n * npt; ixs = ifv + npt; ixo = ixs + n; igo = ixo + n; ihq = igo + n; ipq = ihq + (n * np) / 2; ibmat = ipq + npt; izmat = ibmat + ndim * n; istp = izmat + npt * nptm; isp = istp + n; ixn = isp + npt + npt; iac = ixn + n; irc = iac + n; iqf = irc + m; irf = iqf + n * n; ipqw = irf + (n * np) / 2; //C //C The above settings provide a partition of W for subroutine LINCOB. //C lincob(cmn, n, npt, m, w(iamat), w(ib), x, rhobeg, rhoend, iprint, maxfun, w(ixb), w(ixp), w(ifv), w(ixs), w(ixo), w(igo), w(ihq), w(ipq), w(ibmat), w(izmat), ndim, w(istp), w(isp), w(ixn), iw, w(irc), w(iqf), w(irf), w(ipqw), w); statement_80:; } //C Calculate the tetrahedron of least volume that encloses the points //C (XP(J),YP(J),ZP(J)), J=1,2,...,NP. Our method requires the origin //C to be strictly inside the convex hull of these points. There are //C twelve variables that define the four faces of each tetrahedron //C that is considered. Each face has the form ALPHA*X+BETA*Y+GAMMA*Z=1, //C the variables X(3K-2), X(3K-1) and X(3K) being the values of ALPHA, //C BETA and GAMMA for the K-th face, K=1,2,3,4. Let the set T contain //C all points in three dimensions that can be reached from the origin //C without crossing a face. Because the volume of T may be infinite, //C the objective function is the smaller of FMAX and the volume of T, //C where FMAX is set to an upper bound on the final volume initially. //C There are 4*NP linear constraints on the variables, namely that each //C of the given points (XP(J),YP(J),ZP(J)) shall be in T. Let XS = min //C XP(J), YS = min YP(J), ZS = min ZP(J) and SS = max XP(J)+YP(J)+ZP(J), //C where J runs from 1 to NP. The initial values of the variables are //C X(1)=1/XS, X(5)=1/YS, X(9)=1/ZS, X(2)=X(3)=X(4)=X(6)=X(7) =X(8)=0 //C and X(10)=X(11)=X(12)=1/SS, which satisfy the linear constraints, //C and which provide the bound FMAX=(SS-XS-YS-ZS)**3/6. Other details //C of the test calculation are given below, including the choice of //C the data points (XP(J),YP(J),ZP(J)), J=1,2,...,NP. The smaller final //C value of the objective function in the case NPT=35 shows that the //C problem has local minima. //C void program_unnamed( int argc, char const* argv[]) { common cmn(argc, argv); common_write write(cmn); //C //C Set some constants. //C float one = 1.0e0; float two = 2.0e0; float zero = 0.0e0; float pi = 4.0e0 * datan(one); int ia = 12; int n = 12; //C //C Set the data points. //C int np = 50; float sumx = zero; float sumy = zero; float sumz = zero; int j = fem::int0; float theta = fem::float0; arr_1d<50, float> xp(fem::fill0); arr_1d<50, float> yp(fem::fill0); arr_1d<50, float> zp(fem::fill0); FEM_DO_SAFE(j, 1, np) { theta = dfloat(j - 1) * pi / dfloat(np - 1); xp(j) = fem::dcos(theta) * fem::dcos(two * theta); sumx += xp(j); yp(j) = fem::dsin(theta) * fem::dcos(two * theta); sumy += yp(j); zp(j) = fem::dsin(two * theta); sumz += zp(j); } sumx = sumx / dfloat(np); sumy = sumy / dfloat(np); sumz = sumz / dfloat(np); FEM_DO_SAFE(j, 1, np) { xp(j) = xp(j) - sumx; yp(j) = yp(j) - sumy; zp(j) = zp(j) - sumz; } //C //C Set the linear constraints. //C int m = 4 * np; int k = fem::int0; arr_1d<200, float> b(fem::fill0); int i = fem::int0; arr<float, 2> a(dimension(12, 200), fem::fill0); FEM_DO_SAFE(k, 1, m) { b(k) = one; FEM_DO_SAFE(i, 1, n) { a(i, k) = zero; } } int iw = fem::int0; FEM_DO_SAFE(j, 1, np) { FEM_DO_SAFE(i, 1, 4) { k = 4 * j + i - 4; iw = 3 * i; a(iw - 2, k) = xp(j); a(iw - 1, k) = yp(j); a(iw, k) = zp(j); } } //C //C Set the initial vector of variables. The JCASE=1,6 loop gives six //C different choices of NPT when LINCOA is called. //C float xs = zero; float ys = zero; float zs = zero; float ss = zero; FEM_DO_SAFE(j, 1, np) { xs = fem::dmin1(xs, xp(j)); ys = fem::dmin1(ys, yp(j)); zs = fem::dmin1(zs, zp(j)); ss = fem::dmax1(ss, xp(j) + yp(j) + zp(j)); } cmn.fmax = fem::pow3((ss - xs - ys - zs)) / 6.0e0; int jcase = fem::int0; arr_1d<12, float> x(fem::fill0); int npt = fem::int0; float rhobeg = fem::float0; float rhoend = fem::float0; int iprint = fem::int0; int maxfun = fem::int0; arr<float> w(dimension(500000), fem::fill0); FEM_DO_SAFE(jcase, 1, 6) { FEM_DO_SAFE(i, 2, 8) { x(i) = zero; } x(1) = one / xs; x(5) = one / ys; x(9) = one / zs; x(10) = one / ss; x(11) = one / ss; x(12) = one / ss; //C //C Call of LINCOA, which provides the printing given at the end of this //C note. //C npt = 5 * jcase + 10; rhobeg = 1.0e0; rhoend = 1.0e-6; iprint = 1; maxfun = 10000; write(6, "(/,/,4x,'Output from LINCOA with NPT =',i4,' and RHOEND =',1p," "d12.4)"), npt, rhoend; lincoa(cmn, n, npt, m, a, ia, b, x, rhobeg, rhoend, iprint, maxfun, w, iw); } FEM_STOP(0); } } // namespace placeholder_please_replace int main( int argc, char const* argv[]) { return fem::main_with_catch( argc, argv, placeholder_please_replace::program_unnamed); }

Archived from routemaster.html

var icons = { // coursepoint icons flagsign: { path: "M 0.5 20.5 L 0.5 0.5 12.5 6 0.5 11.5 ", fillColor: '#FCDFFF', fillOpacity: 0.7, strokeColor: 'purple', strokeWeight: 1.5, anchor: new google.maps.Point(0.5,20.5), } , turnleft: { path: "M 18.5 20.5 L 16.5 11.5 A 2 2 0 0 0 14.5 9.5 "+ "L 11.5 10 11.5 13.5 "+ "6.5 7.5 11.5 1.5 11.5 5 16.5 5.5 A 3.5 3.5 0 0 1 20 9 z", fillColor: '#FCDFFF', fillOpacity: 0.7, strokeColor: 'purple', strokeWeight: 1.5, anchor: new google.maps.Point(18.5,20.5), } , straighton: { path: "M 7.5 20.5 L 4.5 6.5 0.5 6.5 7.5 0.5 14.5 6.5 10 6.5 z", fillColor: '#FCDFFF', fillOpacity: 0.7, strokeColor: 'purple', strokeWeight: 1.5, anchor: new google.maps.Point(7.5,20.5), } , turnright: { path: "M 3.5 20.5 L 5.5 11.5 A 2 2 0 0 1 7.5 9.5 L 10.5 10 10.5 13.5 "+ "15.5 7.5 10.5 1.5 10.5 5 5.5 5.5 A 3.5 3.5 0 0 0 2 9 z", fillColor: '#FCDFFF', fillOpacity: 0.7, strokeColor: 'purple', strokeWeight: 1.5, anchor: new google.maps.Point(3.5,20.5), } , shriek: { path: "M 8.5 21.5 A 2.5 2.5 0 0 1 8.5 16.5 A 2.5 2.5 0 1 1 8.5 21.5 "+ "M 8.5 14.5 4.5 5.5 A 4.5 4.5 0 1 1 12.5 5.5 L 8.5 14.5" , fillColor: '#FCDFFF', fillOpacity: 0.7, strokeColor: 'purple', strokeWeight: 1.5, anchor: new google.maps.Point(8.5,21.5), } , // icon for arrow representing current waypoint arrow: { path: "M 6 9 0 15 6 0 12 15 z", fillColor: 'black', fillOpacity: 1, strokeColor: 'black', strokeWeight: 0, anchor: new google.maps.Point(6,6), rotation: 0, clickable: false } , // icon for concentric circles representing draggable waypoint concircle: { path: "M 6 0 A 6 6 0 1 0 6 12 A 6 6 0 1 0 6 0 M 6 3 " + "A 3 3 0 1 0 6 9 A 3 3 0 1 0 6 3", fillColor: 'black', fillOpacity: 0, strokeColor: 'black', strokeWeight: 1, strokeOpacity: 1, anchor: new google.maps.Point(6,6), clickable: false } , // camera icon camera: { path: "M 0.5 4 A 1.5 1.5 0 0 1 2 2.5 L 5.5 2.5 7 0.5 11 0.5 " + "12.5 2.5 14 2.5 A 1.5 1.5 0 0 1 16 3 L 20 7 16 11 " + "A 1.5 1.5 0 0 1 15 11.5 L 2 11.5 A 1.5 1.5 0 0 1 0.5 10 z " + "M 9 4 A 3 3 0 0 1 9 10 A 3 3 0 0 1 9 4 " , fillColor: '#FCDFFF', fillOpacity: 0.7, strokeColor: 'purple', strokeWeight: 1.5, anchor: new google.maps.Point(21,7), clickable: false } } ; function xmlfloat(x) { return parseFloat(x.childNodes[0].textContent) ; } function isvaliddate(d) { if(Object.prototype.toString.call(d)!=="[object Date]") return false ; else return !isNaN(d.getTime()) ; } /* ------------------------------- data structure --------------------------- */ // I found the following logic quite hard to get right. A (non-null) label // satisfies the following constraints: // o. the marker is non-null // o. the map may be null, and if it is null the title may also be null and the // icon may be arbitrary // o. if the type is null, the map is null // o. the map is null if and only if the clickhandler is inactive // the same constraints apply (mutatis mutandis) to the photo, so it follows // that the label may have a null map and the photo non-null (and vice versa) // we therefore conclude that a label must be in one of 3 states: // o. type null, map null, handlers inactive, but marker non-null // o. type non-null, map null, handlers inactive, marker non-null // o. type non-null, map non-null, handlers active, marker non-null // the state in which type is non-null and map is null is applied to all // labels in a segment being deleted (we preserve the information in the // action list but don't want the label to be displayed) function datatype(pos,h,t) { this.pos = pos ; this.h = h ; this.marker = this.photomarker = this.type = this.t = null ; if(t!=undefined&&t!=null&&isvaliddate(t)&&t.getTime()>365*24*3600000) this.t = t ; this.photo = [] ; this.caption = '' ; this.clickhandler = this.righthandler = this.photohandler = null ; } // member functions datatype.prototype.geticon = function() { if(this.type=='Left') return icons.turnleft ; else if(this.type=='Straight') return icons.straighton ; else if(this.type=='Right') return icons.turnright ; else if(this.type=='Danger') return icons.shriek ; else return icons.flagsign ; } ; datatype.prototype.setlabelmap = function(m) { if(m==null||this.type==null) m = null ; else m = map ; if(m==null&&this.marker==null) return ; this.marker.setMap(m) ; if(m==null&&this.clickhandler!=null) { google.maps.event.removeListener(this.clickhandler) ; google.maps.event.removeListener(this.righthandler) ; this.clickhandler = this.righthandler = null ; } if(m!=null&&this.clickhandler==null) { this.clickhandler = this.marker.addListener('click',selpoint) ; this.righthandler = this.marker.addListener('rightclick',labelcycle) ; } } ; datatype.prototype.setphotomap = function(m) { if(m==null||this.photo.length==0) m = null ; else m = map ; if(m==null&&this.photomarker==null) return ; this.photomarker.setMap(m) ; if(m==null&&this.photohandler!=null) { google.maps.event.removeListener(this.photohandler) ; this.photohandler = null ; } if(m!=null&&this.photohandler==null) this.photohandler = this.photomarker.addListener('click',selpoint) ; } ; datatype.prototype.setlabel = function(t,c) { this.type = t ; this.caption = c ; if(t==null) { if(this.marker!=null) this.setlabelmap(null) ; return ; } if(this.marker==null) this.marker = new google.maps.Marker ({ position:this.pos,map:map,icon:this.geticon(),title:c,zIndex:1 }) ; else { this.marker.setIcon(this.geticon()) ; this.marker.setTitle(c) ; } this.setlabelmap(map) ; } ; datatype.prototype.setphoto = function(ind,p) { var i ; if(p==null) { for(i=ind;i<this.photo.length-1;i++) this.photo[i] = this.photo[i+1] ; this.photo.length -= 1 ; if(this.photo.length==0&&this.photomarker!=null) this.setphotomap(null) ; return ; } else { this.photo[ind] = p ; if(ind==0) this.photomarker.setTitle(p) ; } } ; datatype.prototype.addphoto = function(p) { this.photo.push(p) ; if(this.photomarker==null) this.photomarker = new google.maps.Marker ({ position:this.pos,map:map,icon:icons.camera,title:p,zIndex:1 }) ; this.setphotomap(map) ; } ; datatype.prototype.setpos = function(p) { this.pos = p ; if(this.type!=null) this.marker.setPosition(p) ; if(this.photo.length>0) this.photomarker.setPosition(p) ; } ; datatype.prototype.setmap = function(m) { this.setlabelmap(m) ; this.setphotomap(m) ; } ; datatype.prototype.settype = function(t) { this.type = t ; this.marker.setIcon(this.geticon()) ; } ; datatype.prototype.labelcycle = function() { var oldtype = this.type , type ; if(oldtype=='Generic') type = 'Left' ; else if(oldtype=='Left') type = 'Straight' ; else if(oldtype=='Straight') type = 'Right' ; else if(oldtype=='Right') type = 'Danger' ; else type = 'Generic' ; this.settype(type) ; return [ oldtype , type ] ; } function addlabel(data,pos,type,caption) { var j,ind,mindist ; for(j=0;j<data.length;j++) if(j==0||dist(pos,data[j].pos)<mindist) { mindist = dist(pos,data[j].pos) ; ind = j ; } data[ind].setlabel(type,caption) ; } function propstype() { this.longtitle = this.title = this.list = this.inputlen = this.source = null ; this.stats = this.tracklink = this.overview = null ; this.photo = [] ; this.optim = { already: 0, ndel: 0, origlen: 0, parms: null } } /* -------------------------------------------------------------------------- */ function readtcx(xmldoc) { var nodeno,type,lat,lon,i,j,node,alt,pos,segment,props,title,list,txt ; var ind,caption,data,photo,time,valid,anc,xmlnodes,nsegment,propno,names ; var track,trackpoint,trackno,course,coursepoint,courseno,courselen,overview ; // loop over trackpoints track = xmldoc.getElementsByTagName('Trackpoint') ; for(courselen=[],data=[],anc=null,trackno=0;trackno<track.length;trackno++) { trackpoint = track[trackno] ; if(trackpoint.parentNode.parentNode.nodeName=='Course') if(trackpoint.parentNode.parentNode!=anc) { anc = trackpoint.parentNode.parentNode ; courselen.push([trackno,anc]) ; } lat = lon = alt = time = null ; photo = [] ; for(valid=1,nodeno=0;nodeno<trackpoint.childNodes.length;nodeno++) { node = trackpoint.childNodes[nodeno] ; if(node.nodeName=='AltitudeMeters') alt = xmlfloat(node) ; else if(node.nodeName=='Time') // '1970-01-01T03:040:08Z' time = new Date(node.childNodes[0].textContent) ; else if(node.nodeName=='Position') for(j=0;j<node.childNodes.length;j++) { if(node.childNodes[j].nodeName=='LatitudeDegrees') lat = xmlfloat(node.childNodes[j]) ; else if(node.childNodes[j].nodeName=='LongitudeDegrees') lon = xmlfloat(node.childNodes[j]) ; } else if(node.nodeName=='Extensions') for(j=0;j<node.childNodes.length;j++) { if(node.childNodes[j].nodeName=='Photo') photo = node.childNodes[j].childNodes[0].textContent.split(' ') ; else if(node.childNodes[j].nodeName=='ValidTime') valid = 0 ; } } if(lat==null||lon==null) continue ; pos = new google.maps.LatLng(lat,lon) ; data.push(new datatype(pos,alt,valid?time:null)) ; for(ind=0;ind<photo.length;ind++) data[data.length-1].addphoto(photo[ind]) ; } if(track.length==0) { alert('no trackpoints') ; throw '' ; } if(courselen.length==0) courselen.push([0,null]) ; courselen.push([track.length,null]) ; // loop over coursepoints course = xmldoc.getElementsByTagName('CoursePoint') ; for(courseno=0;courseno<course.length;courseno++) { coursepoint = course[courseno] ; caption = type = lat = lon = null ; for(nodeno=0;nodeno<coursepoint.childNodes.length;nodeno++) { node = coursepoint.childNodes[nodeno] ; if(node.nodeName=='Name') caption = node.childNodes[0].textContent ; else if(node.nodeName=='PointType') type = node.childNodes[0].textContent ; else if(node.nodeName=='Position') for(j=0;j<node.childNodes.length;j++) { if(node.childNodes[j].nodeName=='LatitudeDegrees') lat = xmlfloat(node.childNodes[j]) ; else if(node.childNodes[j].nodeName=='LongitudeDegrees') lon = xmlfloat(node.childNodes[j]) ; } } if(lat==null||lon==null||caption==null||type==null) { alert('Badly formatted course point' ) ; throw '' ; } addlabel(data,new google.maps.LatLng(lat,lon),type,caption) ; } for(segment=[],i=0;i<courselen.length-1;i++) segment.push(data.slice(courselen[i][0],courselen[i+1][0])) ; nsegment = segment.length ; // props fields props = new Array(nsegment) ; for(i=0;i<nsegment;i++) { props[i] = new propstype() ; props[i].inputlen = data.length ; if(props[i].optim.origlen==0) props[i].optim.origlen = data.length ; } // optimised? xmlnodes = xmldoc.getElementsByTagName('Optimised') ; if(xmlnodes.length) { props[0].optim.already = 1 ; props[0].optim.origlen = parseInt(xmlnodes[0].getAttribute('from')) ; props[0].optim.ndel = props[0].optim.origlen - parseInt(xmlnodes[0].getAttribute('to')) ; props[0].optim.parms = { tol: parseFloat(xmlnodes[0].getAttribute('tol')) , maxsep: parseFloat(xmlnodes[0].getAttribute('maxsep')) , wppenalty: parseFloat(xmlnodes[0].getAttribute('wppenalty')) , vweight: parseFloat(xmlnodes[0].getAttribute('vweight')) } ; for(i=1;i<nsegment;i++) props[i].optim = props[0].optim ; } // overview fields (different values for each segment) all in one big loop names = [ 'Name' , 'LongTitle' , 'Stats' , 'PhotoList' , 'Overview' , 'TrackLink' , 'Photo' ] ; for(title=null,propno=0;propno<7;propno++) { xmlnodes = xmldoc.getElementsByTagName(names[propno]) ; for(i=0;i<xmlnodes.length;i++) { node = xmlnodes[i] ; if(propno==0) anc = node.parentNode ; else anc = node.parentNode.parentNode ; if(propno==3) txt = node.getAttribute('src') ; else if(propno==4||propno==5) txt = node.getAttribute('href') ; else txt = node.childNodes[0].textContent ; for(j=0;j<nsegment&&anc!=courselen[j][1];j++) ; if(j==nsegment) { if(propno==0&&title==null) if( node.parentNode.nodeName=='Courses' || node.parentNode.nodeName=='Lap' ) title = txt ; continue ; } if(propno==0) props[j].title = txt ; else if(propno==3) props[j].list = txt ; else if(propno<6) props[j][names[propno].toLowerCase()] = txt ; else props[j].photo = txt.match(/\S+/g) ; } } return { title: title , props: props , segments: segment } ; } /* -------------------------------------------------------------------------- */ function readgpx(xmldoc) { var xmlcoords,nodeno,type,lat,lon,i,node,alt,pos,caption,data,time ; var props = new propstype() ; // get the route name xmlcoords = xmldoc.getElementsByTagName('name') ; for(i=0;props.title==null&&i<xmlcoords.length;i++) if(xmlcoords[i].parentNode.nodeName!='wpt') props.title = xmlcoords[i].childNodes[0].textContent ; // get the route description xmlcoords = xmldoc.getElementsByTagName('desc') ; if(xmlcoords.length>0) props.longtitle = xmlcoords[0].childNodes[0].textContent ; // loop over the track points to get the coords xmlcoords = xmldoc.getElementsByTagName('trkpt') ; if(xmlcoords.length==0) xmlcoords = xmldoc.getElementsByTagName('rtept') ; for(data=[],i=0;i<xmlcoords.length;i++) { lat = parseFloat(xmlcoords[i].getAttribute('lat')) ; lon = parseFloat(xmlcoords[i].getAttribute('lon')) ; pos = new google.maps.LatLng(lat,lon) ; for(time=alt=null,nodeno=0;nodeno<xmlcoords[i].childNodes.length;nodeno++) { node = xmlcoords[i].childNodes[nodeno] ; if(node.nodeName=='ele') alt = parseFloat(node.textContent) ; else if(node.nodeName=='time') time = new Date(node.childNodes[0].textContent) ; } if(alt==null) { alert(pos+' has no altitude... unable to proceed') ; throw '' ; } data.push(new datatype(pos,alt,time)); } // loop over the course points to get the labels xmlcoords = xmldoc.getElementsByTagName('wpt') ; for(i=0;i<xmlcoords.length;i++) { caption = type = lat = lon = null ; lat = parseFloat(xmlcoords[i].getAttribute('lat')) ; lon = parseFloat(xmlcoords[i].getAttribute('lon')) ; for(nodeno=0;nodeno<xmlcoords[i].childNodes.length;nodeno++) { node = xmlcoords[i].childNodes[nodeno] ; if(node.nodeName=='name') caption = node.childNodes[0].textContent ; else if(node.nodeName=='type') type = node.childNodes[0].textContent ; } if(lat==null||lon==null||caption==null||type==null) { alert('Badly formatted course point' ) ; throw '' ; } addlabel(data,new google.maps.LatLng(lat,lon),type,caption) ; } props.inputlen = data.length ; return { props: [props] , segments: [data] } ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------------------------------------------------- */ function dist(x,y) { return google.maps.geometry.spherical.computeDistanceBetween(x,y) ; } function angle(x,y) { return google.maps.geometry.spherical.computeHeading(x,y)*Math.PI/180 ; } /* -------------------------------------------------------------------------- */ function optimise(idata,parms) { var stk,nnstk,stk2,clen=idata.length,i,j,m,step=new Array(clen-1) ; var opos,oalt,npos,nalt,ndatum,mpos,malt,arccentre,arctol,pathpos ; var legal,theta,omega,x,y,hyp,tdist,maxtheta,mintheta,d,dh,od,odh,odash ; var bearings=new Array(clen),nstk=new Array(clen),pi=Math.PI,tol=parms.tol ; stk = [ { data:[idata[0]] , err:0 , pathpos:1 } ] ; for(i=0;i<clen-1;i++) step[i] = dist(idata[i].pos,idata[i+1].pos) ; // this is a forwards dynamic program. in stk we have a list of hypotheses // each of which advances a different number of points through the data, // sorted increasing on how far they've advanced. at each step we take the // first item from the stack and try extending to each legal successor point. // note that a hypothesis whose pathpos is k is one whose last point is // idata[k-1]. // note too that I could (& should) preallocate stk and stk2 for efficiency while(stk[0].pathpos<clen) { pathpos = stk[0].pathpos ; opos = idata[pathpos-1].pos ; oalt = idata[pathpos-1].h ; // try extending to pathpos+i for(arctol=null,nnstk=i=0;i<clen-pathpos;i++) { ndatum = idata[pathpos+i] ; npos = ndatum.pos ; nalt = ndatum.h ; if(i==0) hyp = step[pathpos-1] ; else if((hyp=dist(opos,npos))>parms.maxsep) break ; omega = angle(opos,npos) ; // find the min and max legal bearing if(hyp>tol) { theta = Math.asin(tol/hyp) ; if(arctol==null) { arccentre = omega ; arctol = theta ; } else { for(odash=omega-arccentre;odash>pi;odash-=2*pi) ; while(odash<-pi) odash += 2*pi ; maxtheta = Math.min(arctol,odash+theta) ; mintheta = Math.max(-arctol,odash-theta) ; if(maxtheta<mintheta) break ; arccentre += (maxtheta+mintheta) /2 ; arctol = (maxtheta-mintheta) /2 ; } } /* -------------------------------------------------------------------- */
/*page*/ /* -------------------------------------------------------------------- */ bearings[i] = { hyp:hyp , omega:omega } ; // see whether this breaches the max error on any intermediate point for(legal=1,od=odh=tdist=m=0;m<i;m++,od=d,odh=dh) { mpos = idata[pathpos+m].pos ; malt = idata[pathpos+m].h ; x = bearings[m].hyp ; theta = bearings[m].omega ; d = x * Math.sin(theta-omega) ; dh = 0 ; if(d*d<tol*tol&&oalt!=null&&nalt!=null&&malt!=null) { y = hyp - x*Math.cos(theta-omega) ; y = Math.sqrt(d*d+y*y) ; dh = parms.vweight * ( malt - (oalt*y+nalt*x)/(x+y) ) ; } if(d*d+dh*dh>tol*tol) { legal = 0 ; break ; } tdist += step[pathpos-1+m] * ( d*d+d*od+od*od + dh*dh+odh*dh+odh*odh ) ; } // if we emerge with 'legal' non-zero then we may advance to pathpos+i // and tdist is the sum of squared errors if(legal) nstk[nnstk++] = ( { data: stk[0].data.concat([ndatum]) , err: stk[0].err + pi*tdist/3 + parms.wppenalty , pathpos: stk[0].pathpos+i+1 } ) ; if(ndatum.type!=null||ndatum.photo.length>0) break ; } // end loop over i // now we have in nstk the possible extensions of stk[0] in increasing // order of end point, so we merge with stk[0..stk.length-1] for(stk2=[],i=1,j=0;i<stk.length||j<nnstk;) if(i==stk.length) stk2.push(nstk[j++]) ; else if(j==nnstk||stk[i].pathpos<nstk[j].pathpos) stk2.push(stk[i++]) ; else if(stk[i].pathpos>nstk[j].pathpos) stk2.push(nstk[j++]) ; else if(stk[i].err<nstk[j].err) { stk2.push(stk[i++]) ; j += 1 ; } else { stk2.push(nstk[j++]) ; i += 1 ; } stk = stk2 ; } return stk[0].data ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ------------------------------- writetcx -------------------------------- */ function writetcx(props,idata) { var i,j,k,xmldoc,course,lap,datum,filename,track,time,routelen,extns ; var trackpoint,coursepoint,str,flag,clen=idata.length,tlast,di,dk ; var origlen,ndel,ano,photo,thisuri,maxsep,sep,tdist,ttime,time,otime ; var distance = new Array(clen) , msecs = new Array(clen) ; var valid = new Array(clen) ; for(tlast=null,maxsep=tdist=ttime=i=flag=0;i<clen;otime=time,i++) { time = idata[i].t ; if(time!=null) time = time.getTime() ; if(tlast!=null&&time!=null&&time<tlast) flag = 1 ; // out of order if(time!=null) tlast = time ; if(i) { sep = dist(idata[i-1].pos,idata[i].pos) ; distance[i] = distance[i-1] + sep ; if(sep>maxsep) maxsep = sep ; if(time!=null&&otime!=null) { tdist += sep ; ttime += time - otime ; } } else distance[i] = 0 ; } routelen = distance[clen-1] ; if(maxsep>100&&!confirm('Some gaps between waypoints are >100m.\n'+ 'This will cause problems if used for navigation in a Garmin 500.\n'+ 'You can hit [OK] and I will proceed anyway, or\n'+ 'you can hit [Cancel] to interpolate extra points\n'+ '(recommended - go to Route Info under the cogwheel).')) return null ; if(tdist==0||flag!=0) for(i=0;i<clen;i++) msecs[i] = distance[i] * 333 ; else for(i=0;i<clen;i=k) { for(;i<clen&&idata[i].t!=null;i++) ; // advance to null if(i==clen) break ; for(k=i+1;k<clen&&idata[k].t==null;k++) ; // advance to non-null for(j=i;j<k;j++) valid[i] = 0 ; if(i==0) for(time=msecs[k],j=i;j<k;j++) msecs[j] = time - (distance[k]-distance[j])*ttime/tdist ; else if(k==clen) for(time=msecs[i-1],j=i;j<clen;j++) msecs[j] = time + (distance[j]-distance[i-1])*ttime/tdist ; else for(j=i,di=distance[i-1],dk=distance[k];j<k;j++) msecs[j] = ( msecs[i-1]*(dk-distance[j]) + msecs[k]*(distance[j]-di) ) / (dk-di) ; } str = '<?xml version="1.0" encoding="UTF-8" standalone="no" ?>\n' + '<TrainingCenterDatabase xmlns="http://www.garmin.com/xmlschemas/' + 'TrainingCenterDatabase/v2"\n' + ' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"\n' + ' xsi:schemaLocation="http://www.garmin.com/' + 'xmlschemas/TrainingCenterDatabase/v2 ' + 'http://www.garmin.com/xmlschemas/TrainingCenterDatabasev2.xsd">\n' + '<!-- http://www.masterlyinactivity.com/software/routemaster.html -->' ; str += '\n <Folders><Courses><CourseFolder Name="Courses">\n' ; str += ' <CourseNameRef><Id>'+props.title+'</Id></CourseNameRef>\n' ; str += ' </CourseFolder></Courses></Folders>\n<Courses><Course>\n' ; str += ' <Name>'+props.title+'</Name>\n <Lap>\n' + adddist(routelen) ; time = (msecs[clen-1]-msecs[0]) / 1000 ; str += ' <TotalTimeSeconds>' + time.toFixed(0) + '</TotalTimeSeconds>\n' ; str += addpos('Begin',idata[0].pos) ; str += addpos('End',idata[clen-1].pos) ; str += ' <Intensity>Active</Intensity>\n' + ' </Lap>\n <Track>\n' ; // loop over trackpoints for(i=0;i<idata.length;i++) { str += ' <Trackpoint>\n' + addpos('',idata[i].pos) ; str += adddist(distance[i]) + addalt(idata[i].h) ; str += ' <Time>' + new Date(msecs[i]).toISOString() + '</Time>\n' ; if(idata[i].photo.length>0) { str += ' <Extensions><Photo>' ; for(k=0;k<idata[i].photo.length;k++) { if(k) str += ' ' ; str += idata[i].photo[k] ; } str += '</Photo></Extensions>\n' ; } if(valid[i]==0) str += ' <Extensions><ValidTime>False</ValidTime></Extensions>\n' ; str += ' <SensorState>Absent</SensorState>\n </Trackpoint>\n' ; } str += ' </Track>\n\n' ; extns = props.optim.ndel>0 || props.list!=null || props.longtitle!=null || props.overview != null ; if(extns) str += ' <Extensions>\n' ; if(props.optim.ndel) { str += ' <Optimised from="' + props.optim.origlen + '" to="' + (props.optim.origlen-props.optim.ndel) ; if(props.optim.parms!=null) str += '" tol="' + props.optim.parms.tol.toFixed(0) + '" maxsep="' + props.optim.parms.maxsep.toFixed(0) + '" wppenalty="' + props.optim.parms.wppenalty.toFixed(0) + '" vweight="' + props.optim.parms.vweight.toFixed(1) ; str += '"/>\n' } if(props.list!=null) str += ' <PhotoList src="'+props.list+'"/>\n' ; if(props.longtitle!=null) str += ' <LongTitle>'+props.longtitle+'</LongTitle>\n' ; if(props.overview!=null) str += ' <Overview href=">' + props.overview + '"/>\n' ; if(extns) str += ' </Extensions>\n\n' ; // finally loop over coursepoints for(i=0;i<idata.length;i++) if(idata[i].type!=null) { datum = idata[i] ; str += ' <CoursePoint><Name>'+datum.marker.title+'</Name>\n' ; str += ' <PointType>'+datum.type+'</PointType>\n' ; str += addpos('',idata[i].pos) + addalt(idata[i].h) ; time = new Date(msecs[i]) ; str += ' <Time>' + time.toISOString() + '</Time>\n </CoursePoint>\n' ; } return str + '</Course></Courses></TrainingCenterDatabase>\n' ; } /* -------------------------------------------------------------------------- */ function addpos(tag,pos) { var str = ' <'+tag+'Position>\n <LatitudeDegrees>' ; str += pos.lat().toFixed(5)+'</LatitudeDegrees>\n <LongitudeDegrees>' ; str += pos.lng().toFixed(5)+'</LongitudeDegrees>\n </'+tag+'Position>\n' ; return str ; } function addalt(x) { return ' <AltitudeMeters>' + x.toFixed(0) + '</AltitudeMeters>\n' ; } function adddist(x) { return ' <DistanceMeters>' + x.toFixed(0) + '</DistanceMeters>\n' ; } /* ----------------------------- writeoverview ----------------------------- */ function writeoverview(segments,title,list) { var i,j,h,oh,maxalt,minalt,routelen,up,down,ndata,photo,segno,idata,title ; var uri ; var str = '<?xml version="1.0" encoding="UTF-8" standalone="no" ?>\n' + '<!-- http://www.masterlyinactivity.com/software/routemaster.html -->' + '\n<!-- **** routemaster overview file **** not for navigation ****' + ' -->\n<TrainingCenterDatabase><Courses>\n' ; if(title!='Untitled Route'&&title!=null) str += '<Name>'+title+'</Name>\n' ; str += '\n' ; for(segno=0;segno<segments.length;segno++) { idata = segments[segno].data ; for(oh=maxalt=minalt=null,routelen=up=down=i=0;i<idata.length;oh=h,i++) { if(i) routelen += dist(idata[i-1].pos,idata[i].pos) ; if((h=idata[i].h)==null) continue ; if(maxalt==null||h>maxalt) maxalt = h ; if(minalt==null||h<minalt) minalt = h ; if(h!=null&&oh!=null) { if(h>oh) up += h-oh ; else down += oh-h ; } } for(photo=[],ndata=new Array(idata.length),i=0;i<idata.length;i++) { ndata[i] = new datatype(idata[i].pos,0) ; for(j=0;j<idata[i].photo.length;j++) photo.push(idata[i].photo[j]) ; } idata = optimise(ndata,{tol:500,maxsep:100000,wppenalty:10000,vweight:0}) ; str += '<Course>\n' ; if(segments[segno].props.title!=null) str += ' <Name>' + segments[segno].props.title + '</Name>\n' ; str += ' <Track>\n' ; // loop over trackpoints for(i=0;i<idata.length;i++) str += ' <Trackpoint><Position>\n <LatitudeDegrees>' + idata[i].pos.lat().toFixed(4)+'</LatitudeDegrees>\n ' + '<LongitudeDegrees>' + idata[i].pos.lng().toFixed(4) + '</LongitudeDegrees>\n </Position></Trackpoint>\n' ; str += ' </Track>\n\n <Extensions>\n' ; title = segments[segno].props.longtitle ; if(title!=null) str += ' <LongTitle>' + title + '</LongTitle>\n' ; uri = document.URL ; if((title=segments[segno].props.source)!=null) { if(title[1]=='uri') uri = reluri(uri,'?'+title[0]) ; else uri = reluri(uri,'?$FILE$/'+title[0]) ; } str += ' <Stats>Distance ' + (routelen/1000).toFixed(1) + 'km;' + ' altitude ' + minalt.toFixed(0) + '-' + maxalt.toFixed(0) + 'm; \u2191' + up.toFixed(0) + 'm \u2193' + down.toFixed(0) + 'm</Stats>\n <TrackLink href="' + uri + '"/>\n' ; if(list!=null) str += ' <PhotoList src="' + list + '"/>\n' ; if(photo.length) { str += ' <Photo>' ; for(i=0;i<photo.length;i++) { if(i>0&&i%6==0) str += '\n ' ; else if(i) str += ' ' ; str += photo[i] ; } if(photo.length%6==0) str += '\n ' ; str += '</Photo>\n' ; } str += ' </Extensions>\n</Course>\n\n' ; } return str + '</Courses></TrainingCenterDatabase>\n' ; } /* -------------------------------------------------------------------------- */ function gencolours(n) { var colours=new Array(n) ; var ind,density,k,a,na,i,j,m,r,g,b ; for(ind=0,density=1;ind<n;density*=2) { if(density==1) k = 3 ; else k = Math.floor(0.5+0.75*density*(1+density/2)) ; a = new Array(k) ; if(density==1) { a = [ [0,0] , [0,1] , [1,0] ] ; na = 3 ; } else for(na=i=0;i<=density;i++) { if((i&1)==0) for(j=1;i+j<=density;j+=2) a[na++] = [ i , j ] ; else for(j=density-i;j>=0;j--) a[na++] = [ i , j ] ; } if(na!=k) alert('logic error') ; for(k=0;(1<<k)<na;k++) ; for(i=0;i<(1<<k)&&ind<n;i++) { for(m=j=0;j<k;j++) m |= ((i>>j)&1) << (k-1-j) ; if(m>=na) continue ; r = ( density - a[m][0] - a[m][1] ) * (255/density) ; g = a[m][1] * (180/density) ; b = a[m][0] * (300/density) ; r = ("00"+Math.floor(0.5+r).toString(16)).substr(-2) ; g = ("00"+Math.floor(0.5+g).toString(16)).substr(-2) ; b = ("00"+Math.floor(0.5+b>255?255:b).toString(16)).substr(-2) ; colours[ind++] = '#' + r + g + b ; } } return colours ; }

Archived from routemaster.html

var segments=[],selected,actions,nactions,sel,dragging,loadno,overviewing=0 ; var pending,xpending,mouseopt=0,elevator,resuri,xmlfile=null ; var routetitle,body,mapdiv,pro ; var scroller=null,longtitle=null,overview=null,imgdiv,imghandle,imginfo,imgind ; var cursorbtn,scissorsbtn,binbtn,undobtn,redobtn,penbtn,setbtn=null,dlbtn ; var defparms = {tol:10,maxsep:95,wppenalty:1000,vweight:1} ; var neutral='<span style="font-family:helvetica">' ; var active='<span style="cursor:pointer;color:#0000bd" onclick=' ; var inactive='<span style="color:silver">' ; var textbox='<div style="margin-bottom:2px;border-bottom:solid 1px silver;' + 'padding-bottom:2px;font-family:helvetica">' ; var finalbox='<div style="font-family:helvetica">' ; var parser = new DOMParser() ; var map = null , clickhandle = null ; var unsavedchanges = [] ; var infowindow = { handle: null , type: null , open: function(s,pos,type) { this.handle = new google.maps.InfoWindow({content:s,position:pos}) ; this.handle.open(map) ; google.maps.event.addListener(this.handle,'closeclick',function() { if(infowindow.type=='highlight') { redraw(selected[0]) ; if(scroller!=null) { clearInterval(scroller) ; scroller = null ; } } else if(infowindow.type=='phinfo') walkto(selected[0],selected[1]) ; infowindow.handle = infowindow.type = null ; } ) ; this.type = type ; } , close: function() { if(this.handle==null) return null ; var response = this.type ; this.handle.close() ; if(response=='highlight') { redraw(selected[0]) ; if(scroller!=null) { clearInterval(scroller) ; scroller = null ; } } else if(response=='phinfo') walkto(selected[0],selected[1]) ; this.handle = this.type = null ; return response ; } } ; /* --------------- construct a segment from an xml document ----------------- */ function genseg(a,b) { this.data = a ; this.props = b ; this.route = this.routehandler = this.dots = this.dothandler = null ; this.colour = "red" ; } /* -------------------------------------------------------------------------- */ /* CONSTRUCTORS */ /* -------------------------------------------------------------------------- */ function dotpath(a,b) { this.path = [a,b] ; this.cursor = 'default' ; this.geodesic = true ; this.strokeOpacity = 0 ; this.icons = [ { icon: { path: 'M 0 0 L 1 0',strokeOpacity:1,scale:1 } , offset: '1px' , repeat: '4px' } ] ; this.zIndex = 0 ; } /* -------------------------------------------------------------------------- */ function linepath(s0,start,end,colour,width) { var i,len=(start<0?segments[s0].data.length:end-start) ; if(width==undefined) width = 2 ; this.path = new Array(len) ; if(start<0) for(i=0;i<len;i++) this.path[i] = segments[s0].data[i].pos ; else for(i=0;i<len;i++) this.path[i] = segments[s0].data[start+i].pos ; this.clickable = 'false' ; this.cursor = 'default' ; this.geodesic = true ; this.strokeColor = colour ; this.strokeOpacity = 1.0 ; this.strokeWeight = width ; if(width==2) this.zIndex = 0 ; else this.zIndex = 1 ; } /* -------------------------------------------------------------------------- */ function listinfo() { this.list = [] ; this.sizes = [] ; this.uri = null ; this.thumbind = this.scale = this.status = this.type = this.pixpage = null ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------------------------------------------------- */ /* UTILITY FUNCTIONS */ /* -------------------------------------------------------------------------- */ function interp(x,y,lamda) { return google.maps.geometry.spherical.interpolate(x,y,lamda) ; } function bearing(x,y) { return google.maps.geometry.spherical.computeHeading(x,y) ; } /* --------------------------- button handlers ----------------------------- */ function greyout(btn) { if(overviewing||btn.active==0) return 0 ; btn.btn.setAttribute('src',btn.greyimg) ; btn.ui.removeEventListener('click',btn.handler) ; if(btn==penbtn) btn.ui.removeEventListener('contextmenu',photoprompt) ; btn.ui.style.cursor = 'default' ; btn.active = 0 ; return 1 ; } function blackout(btn) { if(overviewing||btn.active) return ; btn.btn.setAttribute('src',btn.blackimg) ; btn.ui.addEventListener('click',btn.handler) ; if(btn==penbtn) btn.ui.addEventListener('contextmenu',photoprompt) ; btn.ui.style.cursor = 'pointer' ; btn.active = 1 ; } /* ------------------------ enter/exit full screen -------------------------- */ // most of the code is available from pixlib function enterFullscreen() { infowindow.close() ; enterfullscreen() ; } function exitFullscreen() { infowindow.close() ; if(document.exitFullscreen) document.exitFullscreen() ; else if(document.mozCancelFullScreen) document.mozCancelFullScreen() ; else if(document.webkitExitFullscreen) document.webkitExitFullscreen() ; } /* -------------------------------------------------------------------------- */ function findimg(id) { var i ; for(i=0;i<imginfo.list.length;i++) if(imginfo.list[i].name!=undefined&&imginfo.list[i].name==id) return i ; return -1 ; } /* ------------------- message warning of unsaved changes ------------------- */ function unsavedmsg(ok) { var msg , len = unsavedchanges.length , i ; if(len==0) return null ; msg = 'You have ' + len + ' unsaved change' + (len==1?"":'s') ; if(len<=3) for(i=0;i<len;i++) msg += (i?',':' (') + unsavedchanges[i] + (i==len-1?')':'') ; msg += '\nIf you hit ' + (ok?'[OK]':'[Leave page]') ; return msg + (len==1?' this change':' these changes') + ' will be lost.' ; } /* -------------------------------------------------------------------------- */
/*page*/ /* --------------- selpoint: choose the clicked waypoint ------------------- */ function selpoint(event) { var i,j,closest,d,mindist,s0,s1 ; if(dragging) return ; var flag = (infowindow.close()=='wpinfo') && (event.shiftKey==0) ; if(overviewing==0&&event.shiftKey) { s0 = selected[0] ; s1 = segments[s0].data.length ; insert(s0,s1,1) ; segments[s0].data[s1].setpos(event.latLng) ; lookupalt(s0,s1) ; redrawconnect(s0,s1) ; done(['move',s0,s1,event.latLng,event.latLng,1]) ; } else for(s1=s0=-1,i=0;i<segments.length;i++) for(j=0;j<segments[i].data.length;j++) { d = dist(segments[i].data[j].pos,event.latLng) ; if(s0<0||d<mindist) { s0 = i ; s1 = j ; mindist = d ; } } walkto(s0,s1,flag) ; } /* -------------------------- track highlighter ---------------------------- */ function highlight() { var s0=selected[0],scroll,thind=null ; infowindow.close() ; undraw(s0) ; draw(s0,4) ; if(imginfo.uri!=null) thind = thumbind(imginfo.sizes) ; scroll = highdiv(segments[s0].props,imginfo.list,imginfo.sizes, thind,segments[s0].props.photo) ; scroller = scroll.scroller ; infowindow.open(scroll.div,northernmost(segments[s0].data),'highlight') ; } /* ------------------------------- getbtnpos -------------------------------- */ function getbtnpos(btnno) { var bounds=map.getBounds(),sw,ne,lat,lon,lam ; sw = bounds.getSouthWest() ; ne = bounds.getNorthEast() ; lam = 52.0 / window.innerHeight ; lat = lam*ne.lat() + (1-lam)*sw.lat() ; lam = 0.5 + (btnno*32-112.0)/window.innerWidth ; lon = lam*ne.lng() + (1-lam)*sw.lng() ; return new google.maps.LatLng(lat,lon) ; } /* ----- unambig: does the selected waypoint determine a unique segment? ---- */ function unambig() // does the selected waypoint determine a unique segment? { var s0=selected[0],s1=selected[1] ; if(segments.length==1) return 1 ; if( ( s0==segments.length-1 || s1!=segments[s0].data.length-1 || ! segments[s0].data[s1].pos.equals(segments[s0+1].data[0].pos) ) && ( s0==0 || s1!=0 || ! segments[s0].data[s1].pos.equals (segments[s0-1].data[segments[s0-1].data.length-1].pos) ) ) return 1 ; else return 0 ; } /* -------------------------------------------------------------------------- */
/*page*/ /* --------------------------------- walkto --------------------------------- */ // draw a selection point (and possibly an info box) at [s0,s1], bringing up // a wpinfo window if flag = 0 or a selinfo window if flag = 1 function walkto(s0,s1,flag) { var s='',i,ind,excuse,imgname=null,phind=-1,list=imginfo.list,sset ; var datum = segments[s0].data[s1] , pos = datum.pos ; if(flag==undefined) flag = 0 ; selected = [ s0,s1 ] ; if(overviewing) return highlight() ; map.panToBounds(new google.maps.LatLngBounds(pos,pos)) ; drawsel(0,[s0,s1]) ; if(flag||(datum.type==null&&datum.photo.length==0)) { if(flag==1) wpinfo() ; else if(flag==2) seginfo() ; else if(flag==3) highlight() ; return ; } if(datum.type!=null) { if(datum.photo.length>0) s = textbox ; else s = finalbox ; if(datum.type!='Generic') s += datum.type + ': ' ; s += datum.marker.title + ' [' + active + '"labelprompt()">Edit</span>]' ; s += '</div>' ; } for(ind=0;ind<datum.photo.length;ind++) { s += textbox ; if(imginfo.status=='ready'&&(phind=findimg(datum.photo[ind]))>=0) { sset = srcset(list[phind],imginfo.sizes,imginfo.thumbind) ; s += '<img src="' + jpg(list[phind],imginfo.sizes,imginfo.thumbind) + '" width=' + list[phind].thumbshape[0] + ' height=' + list[phind].thumbshape[1] + (sset==''?'':(' srcset="'+sset+'"')) + '><br>' + '<b>' + list[phind].name + '</b>: ' ; } else { if(imginfo.status=='null') excuse = 'no list provided' ; else if(imginfo.status=='ready') { imgname = imginfo.uri ; i = imgname.lastIndexOf('/') ; if(i>=0) imgname = imgname.substring(i+1) ; excuse = 'not present in ' + imgname ; } else if(imginfo.status=='waiting') excuse = imgname + ' is not available' ; else excuse = 'imginfo.status = ' + imginfo.status ; s += 'Photo: ' + datum.photo[ind] + ' (' + excuse + ') ' ; } s += '['+active+ '"photoedit('+ind+')">Edit</span>'+']' ; if(phind>=0) s += ' : ['+active+ '"phinfo('+phind+')">Info</span>'+']' + ' : ['+active+ '"display('+ind+')">Enlarge</span>'+']' ; s += '</div>' ; } if(datum.photo.length>0) s += finalbox + '[' + active + '"photoprompt' + '(null)">Add photo</span>' + ']</div>' ; infowindow.open(s,pos,'walking') ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------- keystroke handler ---------------------------- */ function keystroke(e) { var s0=selected[0],s1=selected[1],slast,flag ; if(e.keyCode==40&&overviewing==0) { map.panTo(segments[s0].data[s1].pos) ; return ; } flag = infowindow.close() ; if(flag=='highlight') flag = 3 ; else if(flag=='seginfo') flag = 2 ; else if(flag=='wpinfo') flag = 1 ; else flag = 0 ; if(e.keyCode==32) { selclick() ; return ; } // space if(overviewing) { if(e.keyCode==70) enterfullscreen() ; if(flag!=3||(e.keyCode!=39&&e.keyCode!=37&&e.keyCode!=8&&e.keyCode!=46)) return ; } if(e.keyCode==13) // return { if(dragging) undraggit() ; else if(s0>=0) draggit(0) ; return ; } if(dragging) return ; if(e.keyCode==8||e.keyCode==46) // delete/backspace { e.preventDefault() ; if((flag==3&&segments.length>1)||binbtn.active) discard() ; return ; } if(e.keyCode==9) { e.preventDefault() ; inswp(1) ; return ; } // tab if(e.keyCode==39) // forwards { e.preventDefault() ; if(flag>=2) { s1 = 0 ; s0 += 1 ; if(s0==segments.length) s0 = 0 ; } else if(s1<segments[s0].data.length-1) s1 += 1 ; else { s0 += 1 ; if(s0==segments.length) s0 = 0 ; s1 = 0 ; } } else if(e.keyCode==37) // backwards { e.preventDefault() ; if(flag>=2) { s1 = 0 ; s0 -= 1 ; if(s0<0) s0 = segments.length-1 ; } else if(s1>0) s1 -= 1 ; else { s0 -= 1 ; if(s0<0) s0 = segments.length-1 ; s1 = segments[s0].data.length-1 ; } } else return ; walkto(s0,s1,flag) ; } /* --------------------- undraw & redraw segments -------------------------- */ function undraw(i) { segments[i].route.setMap(null) ; if(segments[i].clickhandler!=null) { google.maps.event.removeListener(segments[i].clickhandler) ; segments[i].clickhandler = null ; } } function redraw(i) { undraw(i) ; draw(i) ; } function recolour(i) { if(overviewing) return ; else if(i&1) segments[i].route.setOptions({strokeColor:"#ff9999"}) ; else segments[i].route.setOptions({strokeColor:"#ff0000"}) ; } function obliterate(s0) // undraw route and all labels { var i ; for(i=0;i<segments[s0].data.length;i++) segments[s0].data[i].setmap(null) ; undraw(s0) ; disconnect(s0-1) ; disconnect(s0) ; } /* ----------------------------- draw segments ------------------------------ */ function draw(i,width) { var colour,poly ; if(overviewing) colour = segments[i].colour ; else if(i&1) colour = "#ff9999" ; else colour = "#ff0000" ; if(width==undefined) poly = new linepath(i,-1,0,colour) ; else poly = new linepath(i,-1,0,colour,width) ; segments[i].route = new google.maps.Polyline(poly) ; segments[i].route.setMap(map) ; if(segments[i].clickhandler==null) segments[i].clickhandler = google.maps.event.addListener(segments[i].route,"click",selpoint) ; } /* ----------------------- connect and disconnect segments ------------------ */ function disconnect(i) { if(overviewing||i<0||i>=segments.length-1||segments[i].dots==null) return ; segments[i].dots.setMap(null) ; if(segments[i].dothandler!=null) { google.maps.event.removeListener(segments[i].dothandler) ; segments[i].dothandler = null ; } } function reconnect(i) { disconnect(i) ; connect(i) ; } function connect(i) { if(overviewing||i<0||i>=segments.length-1) return ; var opos = segments[i].data[segments[i].data.length-1].pos ; var npos = segments[i+1].data[0].pos ; if(opos.equals(npos)) return ; segments[i].dots = new google.maps.Polyline(new dotpath(opos,npos)) ; segments[i].dots.setMap(map) ; segments[i].dothandler = google.maps.event.addListener(segments[i].dots,"click",selpoint) ; } function redrawconnect(s0,s1) { redraw(s0) ; if(s1==0) reconnect(s0-1) ; if(s1=segments[s0].data.length-1) reconnect(s0) ; } /* ---------------------- draw the selection point -------------------------- */ // note: there's no point in allowing clicking on a marker because the // event position is always the marker position rather than the click position function drawsel(opt,selection) { if(selection!=undefined) selected = selection ; var ind,clen,s0=selected[0],s1=selected[1],pos=segments[s0].data[s1].pos ; if(opt) reprofile() ; clen = segments[s0].data.length ; if(clen==1) arrow.rotation = 90 ; else { if(s1==clen-1) ind = s1-1 ; else ind = s1 ; icons.arrow.rotation = bearing(segments[s0].data[ind].pos,segments[s0].data[ind+1].pos) ; } if(sel.marker==null) sel.marker = new google.maps.Marker ({ position:pos, map:map, cursor:'default', icon:icons.arrow , zIndex:2 }) ; else // avoid unnecessary redraws { if(icons.arrow.rotation!=sel.orientation) sel.marker.setIcon(icons.arrow) ; if(!pos.equals(sel.marker.getPosition())) sel.marker.setPosition(pos) ; } sel.orientation = icons.arrow.rotation ; drawxcur(pro,selected) ; blackout(penbtn) ; if(segments.length>1&&unambig()) blackout(binbtn) ; else greyout(binbtn) ; if(s1!=0&&s1!=clen-1) blackout(scissorsbtn) ; else greyout(scissorsbtn) ; } /* ------------- selclick: respond to click of cursor button --------------- */ function selclick() { mouseopt = 1-mouseopt ; infowindow.close() ; if(mouseopt) { map.setOptions({draggable:false, draggableCursor:'default'}) ; if(overviewing==0) cursorbtn.btn.setAttribute('src','hand.png') ; clickhandle = google.maps.event.addListener(map,"click",selpoint) ; } else { map.setOptions({draggable:true, draggableCursor:''}) ; if(overviewing==0) cursorbtn.btn.setAttribute('src','arrow.png') ; google.maps.event.removeListener(clickhandle) ; } } /* -------------------------------------------------------------------------- */ function genhead(uri,key) { if(uri==undefined||uri==null) resuri = 'http://www.masterlyinactivity.com/routemaster/resources/' ; else { resuri = uri + '/' ; document.write('<script src="http://maps.google.com/maps/api/js?' + ((key==null||key==undefined)?'':('key='+key+'&')) + 'libraries=geometry"></scr' + 'ipt>') ; } document.write ('<script src="' + resuri + 'dms.js"></scr' + 'ipt>' + '<script src="' + resuri + 'vector3d.js"></scr' + 'ipt>' + '<script src="' + resuri + 'latlon-ellipsoidal.js"></scr' + 'ipt>' + '<script src="' + resuri + 'utm.js"></scr' + 'ipt>' + '<script src="' + resuri + 'osgridref.js"></scr' + 'ipt>' + '<script src="' + resuri + 'FileSaver.js"></scr' + 'ipt>' + '<script src="' + resuri + 'Blob.js"></scr' + 'ipt>' + '<script src="' + resuri + 'tcxlib.js"></scr' + 'ipt>' + '<script src="' + resuri + 'routemasterlib.js"></scr' + 'ipt>' + '<script src="http://www.masterlyinactivity.com/pixlib.js"></scr' + 'ipt>' + '<style type="text/css">html, body {width: 100%; height: 100%}' + 'body {margin:0px}a:link{color:#66aaaa}' + 'a:visited{color:#cc3388}a:active{color:#404040}</style>' + '</style><title>Routemaster</title>' + '<link rel="shortcut icon" href=' + resuri + 'bus.gif>' ) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------------------------------------------------- */ /* FUNCTIONS TO GENERATE THE INITIAL MAP */ /* -------------------------------------------------------------------------- */ function genpage() { var thispage=document.URL,xhttp,quotind,plusind,listxhttp,div ; imginfo = new listinfo() ; imgdiv = null ; elevator = new google.maps.ElevationService ; window.onload = function() { window.addEventListener("beforeunload",function(e) { var msg = unsavedmsg(0) ; if(msg==null) return undefined ; (e || window.event).returnValue = msg ; //Gecko + IE return msg ; //Gecko + Webkit, Safari, Chrome etc. (msg is ignored by ffx) } ) ; } ; body = document.getElementsByTagName("body")[0] ; while(body.childNodes.length>0) body.removeChild(body.childNodes[body.childNodes.length-1]) ; mapdiv = document.createElement('div') ; mapdiv.setAttribute('id','map') ; mapdiv.setAttribute('style','width:100%;height:100%;position:absolute') ; body.appendChild(mapdiv) ; if((quotind=thispage.indexOf('?'))>=0) { thispage = thispage.substring(quotind+1) ; if((plusind=thispage.indexOf('+'))>0) { getlist(thispage.substring(plusind+1),'uri') ; thispage = thispage.substring(0,plusind) ; } else if(thispage.substring(thispage.length-3)=='.js') { getlist(thispage,'uri') ; mapdiv.appendChild(filedialogue(0)) ; return ; } xhttp = new XMLHttpRequest() ; xhttp.onreadystatechange = function() { if(xhttp.readyState==4) { if(xhttp.status==200) { var x = parser.parseFromString(xhttp.responseText,"application/xml") ; render(x,thispage,0,'uri') ; } else alert("Unable to read "+thispage+": error code "+xhttp.status) ; } } xhttp.open("GET",thispage,true) ; xhttp.send() ; } else { mapdiv.appendChild(filedialogue(0)) ; div = helpdiv(resuri,1) ; div.setAttribute('style','margin:4px;font-size:90%') ; mapdiv.appendChild(div) ; } } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------- getlist --------------------------------- */ function getlist(uri,imgtype) { var xhttp = new XMLHttpRequest(),list=null,sizes=null,pixpage=null,i,r ; var imagedir=null,thumbshape = [] ; imginfo.status = 'waiting' ; imginfo.type = imgtype ; xhttp.onreadystatechange = function() { if(xhttp.readyState==4) { if(xhttp.status!=200) { alert("Unable to read "+uri+": error code "+xhttp.status) ; return ; } eval(xhttp.responseText) ; imginfo.uri = uri ; imginfo.list = list ; imginfo.sizes = sizes ; imginfo.pixpage = reluri(uri,pixpage) ; imginfo.thumbind = thumbind(sizes) ; setthumbshape(list,sizes,thumbshape,reluri(uri,imagedir)) ; for(i=0;i<list.length;i++) if(list[i].retpage!=undefined) list[i].retpage = reluri(uri,list[i].retpage) ; imginfo.status = 'ready' ; } } xhttp.open("GET",uri,true) ; xhttp.send() ; } /* ----------------------------- file dialogue ------------------------------ */ function filedialogue(overwrite) { var input = document.createElement('input') ; var para = document.createElement('p') ; para.appendChild(document.createTextNode ('\u00a0\u00a0\u00a0Select TCX/GPX file: ')) ; input.setAttribute('type','file') ; input.setAttribute('accept','.tcx,.gpx') ; input.addEventListener('change',function(e) { reader = new FileReader() ; reader.onload = function(e) { var xmldoc = parser.parseFromString(reader.result,"application/xml") ; render(xmldoc,input.files[0].name,overwrite,'file') ; } reader.readAsText(input.files[0]) ; } ) ; para.appendChild(input) ; return para ; } /* -------------------------------------------------------------------------- */
/*page*/ /* --------------------- set up the map and buttons ------------------------- */ function render(xmldoc,filename,overwrite,origin) { var i,opts,centre,lat,lon,minlon,maxlon,minlat,maxlat,newseg,s0 ; var colours,segno,istcx ; infowindow.close() ; document.onkeydown = keystroke ; xmlfile = filename ; // read data i = filename.length ; if(filename.substring(i-4,i)=='.tcx') istcx = 1 ; else if(filename.substring(i-4,i)=='.gpx') istcx = 0 ; else { alert(filename+' is neither .tcx nor .gcx') ; throw '' ; } if(istcx) newseg = readtcx(xmldoc) ; else newseg = readgpx(xmldoc) ; if(newseg.segments.length==0||newseg.segments[0].length==0) { alert('no data returned') ; return ; } if(newseg.segments.length>1&&setbtn!=null) { alert('trying to add a multitrack overview... not permitted') ; return ; } if(newseg.segments.length==1) for(i=0;i<newseg.segments[0].length;i++) if(newseg.segments[0][i].h==null) { alert(newseg.segments[0][i].pos+' has no altitude... unable to proceed') ; return ; } // check and process photo list if(imginfo.type!='uri') for(i=0;i<newseg.segments.length;i++) if(newseg.props[i].list!=null) { if(imginfo.uri!=null&&newseg.props[i].list!=imginfo.uri) { alert('inconsistent photo lists: ' + imginfo.uri + ' and ' + newseg.props[i].list) ; return ; } imginfo = new listinfo() ; getlist(newseg.props[i].list,'tcx') ; } if(overwrite) { for(i=0;i<segments.length;i++) obliterate(i) ; unprofile() ; if(sel.marker!=null) sel.marker.setMap(null) ; segments = [] ; if(imginfo.type=='tcx') imginfo = new listinfo() ; } s0 = segments.length ; if(s0==0) { sel = { marker:null, orientation: null } ; pending = [] ; xpending = [] ; actions = [] ; unsavedchanges = [] ; nactions = dragging = 0 ; loadno = -1 ; pro = routetitle = null ; } if(newseg.segments.length>1) { overviewing = 1 ; colours = gencolours(newseg.segments.length) ; } if(istcx&&routetitle==null&&newseg.title!=null) settitle(newseg.title) ; // process the new segments for(i=0;i<newseg.segments.length;i++) { newseg.props[i].source = [ filename , origin ] ; if(longtitle==null) longtitle = newseg.props[i].longtitle ; if(overview==null) overview = newseg.props[i].overview ; if(routetitle==null||routetitle=='Untitled Route') if(newseg.props[i].title!=null) settitle(newseg.props[i].title) ; segments.push(new genseg(newseg.segments[i],newseg.props[i])) ; if(overviewing) segments[segments.length-1].colour = colours[i] ; actions[nactions++] = [ 'load',s0+i,newseg.segments[i].slice(),loadno,newseg.props[i] ] ; loadno = nactions-1 ; } if(routetitle==null) settitle('Untitled Route') ; if(!newseg.props[0].optim.already&&!overviewing) optimaction(segments.length-1,defparms,0) ; // find max and min lat and long - have to look at all segs, not just // newly loaded to avoid google's repeatedly adding a margin for(maxlat=null,segno=0;segno<segments.length;segno++) for(i=0;i<segments[segno].data.length;i++) { lat = segments[segno].data[i].pos.lat() ; lon = segments[segno].data[i].pos.lng() ; if(maxlat==null||lon<minlon) minlon = lon ; if(maxlat==null||lon>maxlon) maxlon = lon ; if(maxlat==null||lat<minlat) minlat = lat ; if(maxlat==null||lat>maxlat) maxlat = lat ; } if(s0==0) centre = new google.maps.LatLng((minlat+maxlat)/2,(minlon+maxlon)/2) ; if(map==null) // all this only done on first call { opts = { zoom: 22, center: centre, scaleControl: true, rotateControl: false, streetViewControl: false, mapTypeId: google.maps.MapTypeId.TERRAIN, disableDoubleClickZoom: true, styles: [ { "featureType": "poi", "stylers": [{ "visibility": "off" }] } ], mapTypeControl:true, mapTypeControlOptions: { style:google.maps.MapTypeControlStyle.HORIZONTAL_BAR }, mapTypeIds: [ google.maps.MapTypeId.ROADMAP, google.maps.MapTypeId.TERRAIN, google.maps.MapTypeId.SATELLITE ] } ; map = new google.maps.Map(mapdiv,opts) ; // set up buttons if(overviewing==0) { setbtn = genbutton('settings') ; cursorbtn = genbutton('cursor') ; cursorbtn.ui.addEventListener('click',selclick) ; scissorsbtn = genbutton('scissors') ; binbtn = genbutton('bin') ; penbtn = genbutton('pen') ; undobtn = genbutton('undo') ; redobtn = genbutton('redo') ; dlbtn = genbutton('dl') ; } selclick() ; } map.fitBounds(new google.maps.LatLngBounds (new google.maps.LatLng(minlat,minlon), new google.maps.LatLng(maxlat,maxlon))) ; for(segno=s0;segno<segments.length;segno++) for(i=0;i<segments[segno].data.length;i++) segments[segno].data[i].setmap(map) ; if(nactions>1) donesomething() ; // specifically, done loading & optimisation else actions.length = nactions ; // load with no optimisation hence no undo if(s0==0) { selected = [0,0] ; if(overviewing==0) drawsel(1) ; } else greyout(dlbtn) ; for(segno=s0;segno<segments.length;segno++) { draw(segno) ; connect(segno-1) ; } connect(segments.length-1) ; reprofile() ; } /* ------------------------------- settitle --------------------------------- */ function settitle(newtitle) { routetitle = newtitle ; var h = document.getElementsByTagName('title')[0] ; while(h.firstChild) h.removeChild(h.firstChild) ; h.appendChild(document.createTextNode(routetitle)) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ------------------------------- retitle ---------------------------------- */ function retitle() { infowindow.close() ; var response = window.prompt("Modify title: (max 15 chars)",routetitle) ; if(response==null) return ; else response = response.substring(0,15) ; if(response==routetitle) return ; settitle(response) ; actions[nactions++] = [ 'edittitle' , routetitle , response ] ; donesomething() ; } /* ----------------------------- longretitle -------------------------------- */ function longretitle() { var response ; infowindow.close() ; if(longtitle==null) response = window.prompt("Add long title:","") ; else response = window.prompt("Modify long title:",longtitle) ; if(response==null||response==longtitle) return ; longtitle = response ; actions[nactions++] = [ 'editlongtitle' , longtitle , response ] ; donesomething() ; } /* ------------------------------- genbutton -------------------------------- */ function genbutton(name) { var u,v,w,b,g,k,h,div=document.createElement('div'),act ; u = document.createElement('div') ; u.style.backgroundColor = '#ffffff' ; u.style.border = '2px solid #ffffff' ; u.style.borderRadius = '3px' ; u.style.boxShadow = '0 2px 6px rgba(0,0,0,.3)' ; if(name=='dl'||name=='settings'||name=='cursor') u.style.cursor = 'pointer' ; else u.style.cursor = 'default' ; u.style.marginBottom = '12px' ; if(name!='dl') u.style.marginRight = '4px' ; u.style.textAlign = 'center' ; div.appendChild(u) ; if(name=='scissors') { h = snip ; div.index = 3 ; } else if(name=='bin') { h = discard ; div.index = 4 ; } else if(name=='pen') { h = labelprompt ; div.index = 5 ; } else if(name=='undo') { h = undo ; div.index = 6 ; } else if(name=='redo') { h = redo ; div.index = 7 ; } else if(name=='dl') { h = dl ; div.index = 8 ; } else if(name=='settings') { h = popup ; div.index = 1 ; } else if(name=='cursor') { h = null ; div.index = 2 ; } g = greybtn(resuri,name) ; k = blackbtn(resuri,name) ; if(name=='dl'||name=='settings'||name=='cursor') b = buttonimg(k) ; else b = buttonimg(g) ; u.appendChild(b) ; map.controls[google.maps.ControlPosition.BOTTOM_CENTER].push(div) ; if(name=='dl'||name=='settings') u.addEventListener('click',h) ; if(name=='dl'||name=='settings') act = 1 ; else act = 0 ; return { ui:u , btn:b , active:act , greyimg:g , blackimg:k , handler:h } ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------------------------------------------------- */ /* OPTIMISATION */ /* -------------------------------------------------------------------------- */ function optimaction(segno,parms,force) { var s = segments[segno], result = optimise(s.data,parms) ; var ndel = s.data.length - result.length ; if((force==0&&ndel<s.data.length/2)||ndel==0) return 0 ; actions[loadno][4].optim.ndel = ndel ; actions[nactions++] = [ 'optimise' , segno , parms ] ; segments[segno] = new genseg(result,segments[segno].props) ; actions[loadno][4].optim.parms = { tol: parms.tol , maxsep: parms.maxsep , wppenalty: parms.wppenalty , vweight: parms.vweight } ; return 1 ; } /* -------------------------------------------------------------------------- */ function optimprompt() { var msg = 'Enter 4 optimisation parms (tol, maxsep, wppenalty and vweight)' ; var parmstr = defparms.tol + ' ' + defparms.maxsep.toFixed(0) + ' ' + defparms.wppenalty.toFixed(0) + ' ' + defparms.vweight.toFixed(1) ; var parms,i ; infowindow.close() ; for(i=0;;i++) { newparms = prompt(msg,parmstr) ; if(newparms==null) return ; if(newparms=='') { parms = defparms ; break ; } newparms = newparms.split(' ') ; if(newparms.length==0) { parms = defparms ; break ; } parms = { tol: parseFloat(newparms[0]) , maxsep: parseFloat(newparms[1]) , wppenalty: parseFloat(newparms[2]) , vweight: parseFloat(newparms[3]) } ; if( isNaN(parms.tol)==0 && isNaN(parms.maxsep)==0 && isNaN(parms.wppenalty)==0 && isNaN(parms.vweight)==0 ) break ; if(i==0) msg = '*** Illegal parms ***\n' + msg ; } if(optimaction(segments.length-1,parms,1)) { donesomething() ; draw(segments.length-1) ; } routeinfo() ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------------------------------------------------- */ /* THE MAIN POPUP MENU AND THE FUNCTIONS IT GOVERNS */ /* -------------------------------------------------------------------------- */ function popup() { var opts,pos,s0=selected[0],s1=selected[1] ; infowindow.close() ; if(dragging) { opts = finalbox + "Hit [return] when you've finished dragging.</div>" ; infowindow.open(opts,getbtnpos(0),'settings') ; return ; } // route options opts = textbox + active + '"routeinfo()">Route info</span><br>' ; if(pro==null||pro.prodiv==null) opts += active + '"drawprofile()">Show altitude profile</span><br>' ; else opts += active + '"unprofile()">Hide altitude profile</span><br>' ; opts += active + '"addload(1)">Load new route</span><br>' ; if(overview!=null) opts += '<a style="cursor:pointer;color:#0000bd;text-decoration:none" '+ 'href="' + overview + '" target="_blank">' + 'View routes ' + 'overview</a>'+neutral+' (opens in new tab/window)</span><br> ' ; opts += active + '"dl(1)">Download track as overview</span></div>' ; // segment options opts += textbox + active + '"seginfo()">Segment info</span><br>' ; if(unambig()) opts += active + '"revseg()">' ; else opts += inactive ; opts += 'Reverse segment</span><br>' ; opts += active + '"manualcal()">Calibrate segment altitudes</span><br>' ; opts += active + '"addload(0)">Load route as a new segment</span></div>' ; // waypoint options opts += textbox + active + '"wpinfo()">Waypoint info</span><br>' ; if(segments[selected[0]].data.length>1) opts += active + '"wpdel()">' ; else opts += inactive ; opts += 'Delete waypoint</span><br>' ; opts += active + '"draggit(0)">Make waypoint draggable</span><br>' ; opts += active + '"inswp(1)">Insert draggable waypoint ahead</span><br>' ; opts += active + '"inswp(-1)">Insert draggable waypoint behind</span>' ; opts += '</div>'+finalbox+active ; // tool options if(querycanfullscreen()) { if(queryfullscreen()==0) opts += '"enterFullscreen()">Enter full screen</span><br>' + active ; else opts += '"exitFullscreen()">Leave full screen</span><br>' + active ; } opts += '"help()">Help</span></div>' ; infowindow.open(opts,getbtnpos(0),'settings') ; } /* ------------------------------- calwork --------------------------------- */ function calwork(s0,y) { var i,s1 ; for(s1=0;s1<segments[s0].data.length;s1++) if(segments[s0].data[s1].h!=null) segments[s0].data[s1].h += y ; reprofile() ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ------------------------------ manualcal --------------------------------- */ function manualcal() { infowindow.close() ; var x,y,s0=selected[0] ; x = prompt('Enter offset in metres to add to altidudes:') ; if(x==null) return ; y = parseFloat(x) ; if(isNaN(y)) { alert(x+' is not a number') ; return ; } calwork(s0,y) ; done(['recal',s0,y]) ; } /* --------------------------------- help ----------------------------------- */ function help() { infowindow.close() ; infowindow.open(helpdiv(resuri),getbtnpos(0),'help') ; } /* --------------------------------- wpdel ---------------------------------- */ function wpdelwork(s0,s1) { var i,response=segments[s0].data[s1],clen=segments[s0].data.length ; response.setmap(null) ; for(i=s1;i<clen-1;i++) segments[s0].data[i] = segments[s0].data[i+1] ; segments[s0].data.length = clen-1 ; selected = [s0,s1] ; if(s1==segments[s0].data.length) selected[1] -= 1 ; redrawconnect(s0,s1) ; drawsel(1) ; return response ; } function wpdel() { var s0=selected[0],s1=selected[1],i ; infowindow.close() ; done(['wpdel',s0,s1,wpdelwork(s0,s1)]) ; } /* --------------------------------- revseg --------------------------------- */ function revsegwork(s0) { var i,s=segments[s0],j,x,len=s.data.length ; disconnect(s0-1) ; disconnect(s0) ; for(i=0;i<len/2;i++) { j = (len-1) - i ; x = s.data[i] ; s.data[i] = s.data[j] ; s.data[j] = x ; } for(i=0;i<s.data.length;i++) if(s.data[i].type=='Right') s.data[i].settype('Left') ; else if(s.data[i].type=='Left') s.data[i].settype('Right') ; if(s0==selected[0]) selected[1] = (len-1) - selected[1] ; connect(s0-1) ; connect(s0) ; drawsel(1) ; } /* -------------------------------------------------------------------------- */ function revseg() { infowindow.close() ; revsegwork(selected[0]) ; done(['revseg',selected[0]]) ; } /* -------------------------------------------------------------------------- */ function addload(overwrite) { var msg ; infowindow.close() ; if(overwrite) { msg = unsavedmsg(1) ; if(msg!=null) if(!confirm(msg)) return ; } infowindow.open(filedialogue(overwrite),getbtnpos(0),'addload') ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------------------------------------------------- */ /* DRAGGING A (POSSIBLY NEWLY INSERTED) WAYPOINT IS QUITE A LOT OF WORK */ /* -------------------------------------------------------------------------- */ function insert(s0,s1,n) { var i ; for(i=segments[s0].data.length+n-1;i>s1;i--) segments[s0].data[i] = segments[s0].data[i-n] ; for(i=0;i<n;i++) segments[s0].data[s1+i] = new datatype(null,null) ; } /* --------------------------------- inswp ---------------------------------- */ function inswp(dir) { var s0=selected[0],s1=selected[1],bounds,del,pos,data=segments[s0].data ; var len = data.length ; if(len==1) pos = data[0].pos ; if(dir>=0) s1 = selected[1] += 1 ; insert(s0,s1,1) ; if(len==1) { bounds = map.getBounds() ; del = bounds.getNorthEast().lng() - bounds.getSouthWest().lng() ; pos = new google.maps.LatLng(pos.lat(),pos.lng()+dir*del/10) ; } else if(s1==0) pos = interp(data[2].pos,data[1].pos,1.5) ; else if(s1<len) pos = interp(data[s1-1].pos,data[s1+1].pos,0.5) ; else pos = interp(data[s1-2].pos,data[s1-1].pos,1.5) ; data[s1].setpos(pos) ; draggit(1) ; } /* -------------------------------- draggit --------------------------------- */ // draggit makes the current waypoint draggable var l1,l2,startpos,seg0,seg1,seg2,colour,inserted ; function draggit(insparm) { var s0=selected[0],s1=selected[1],start,end,i,len=segments[s0].data.length ; startpos = segments[s0].data[s1].pos ; inserted = insparm ; infowindow.close() ; greyout(scissorsbtn) ; greyout(binbtn) ; greyout(penbtn) ; greyout(undobtn) ; greyout(redobtn) ; greyout(dlbtn) ; map.panToBounds(new google.maps.LatLngBounds(startpos,startpos)) ; sel.marker.setMap(null) ; sel.marker = new google.maps.Marker( { position: segments[s0].data[s1].pos, map: map, cursor: 'default', icon: icons.concircle , draggable: true , zIndex: 2 } ) ; if(s0&1) colour = "#ff9999" ; else colour = "#ff0000" ; segments[s0].route.setMap(null) ; if(segments[s0].clickhandler!=null) { google.maps.event.removeListener(segments[s0].clickhandler) ; segments[s0].clickhandler = null ; } seg0 = seg2 = null; if(s1>1) { seg0 = new google.maps.Polyline(new linepath(s0,0,s1,colour)) ; seg0.setMap(map) ; } if(s1==0) start = 0 ; else start = s1-1 ; if(s1==len-1) end = s1+1 ; else end = s1+2 ; seg1 = new google.maps.Polyline(new linepath(s0,start,end,colour)) ; seg1.setMap(map) ; if(s1<segments[s0].data.length-2) { seg2 = new google.maps.Polyline(new linepath(s0,s1+1,len,colour)) ; seg2.setMap(map) ; } l1 = google.maps.event.addListener(sel.marker,'drag',function() { segments[s0].data[s1].setpos(this.getPosition()) ; seg1.setMap(null) ; seg1 = new google.maps.Polyline(new linepath(s0,start,end,colour)) ; seg1.setMap(map) ; if(s1==0&&s0>0) { disconnect(s0-1) ; connect(s0-1) ; } if(s1==len-1&&s0<segments.length-1) { disconnect(s0) ; connect(s0) ; } } ) ; dragging = 1 ; } /* ------------------------------- undraggit -------------------------------- */ // undraggit is invoked by [return] to terminate waypoint dragging function undraggit() { var s0=selected[0],s1=selected[1],i,s1dash,pos=segments[s0].data[s1].pos ; var xpos ; google.maps.event.removeListener(l1) ; dragging = 0 ; if(seg0!=null) seg0.setMap(null) ; seg1.setMap(null) ; if(seg2!=null) seg2.setMap(null) ; segments[s0].route = new google.maps.Polyline(new linepath(s0,-1,0,colour)) ; segments[s0].route.setMap(map) ; segments[s0].data[s1].h = null ; lookupalt(s0,s1) ; sel.marker.setMap(null) ; sel.marker = null ; // force a redraw drawsel(1) ; if(inserted||dist(startpos,pos)>5) done(['move',s0,s1,startpos,pos,inserted]) ; if(segments.length==1) blackout(dlbtn) ; } /* -------------------------------------------------------------------------- */ function seginfo() { var pos = segments[selected[0]].data[selected[1]].pos ; infowindow.close() ; infowindow.open(seginfodiv(segments,selected[0]),pos,'seginfo') ; } /* -------------------------------------------------------------------------- */ function deltimes() { var s0,s1,task=[] ; for(s0=0;s0<segments.length;s0++) for(s1=0;s1<segments[s0].data.length;s1++) if(segments[s0].data[s1].t!=null) { task.push([s0,s1,segments[s0].data[s1].t]) ; segments[s0].data[s1].t = null ; } infowindow.close() ; done(['deltimes',task]) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------------------------------------------------- */ /* CODE TO GET ALTITUDES FOR NEWLY INSERTED POINTS */ /* -------------------------------------------------------------------------- */ function lookupalt(s0,s1) // set up a request for the alt of the new point { var hi,lo,segno=s0,ptno=s1,lopos,hipos,datum=segments[s0].data[s1] ; for(lo=null,s0=segno,s1=ptno-1;lo==null;s1--) { if(s1<0) { s0 -= 1 ; if(s0<0) break ; s1 = segments[s0].data.length-1 ; } if(segments[s0].data[s1].h!=null) lo = [s0,s1] ; } for(hi=null,s0=segno,s1=ptno+1;hi==null;s1++) { if(s1==segments[s0].data.length) { s0 += 1 ; if(s0==segments.length) break ; s1 = 0 ; } if(segments[s0].data[s1].h!=null) hi = [s0,s1] ; } if(lo==null&&hi==null) { alert('no points left with altitudes: unable to proceed') ; throw '' ; } if(lo!=null) lopos = segments[lo[0]].data[lo[1]].pos ; if(hi!=null) hipos = segments[hi[0]].data[hi[1]].pos ; if(lo==null||(hi!=null&&dist(lopos,datum.pos)>dist(hipos,datum.pos))) { lo = hi ; lopos = hipos ; } pending.push([datum,lopos,segments[lo[0]].data[lo[1]].h]) ; elevator.getElevationForLocations({locations:[datum.pos,lopos]},calibrate) ; } /* -------------------------------------------------------------------------- */ // pending is the list of inserted points for which google altitudes are needed // note a pitfall with the elevation service - it's hard to tell which // response corresponds to which request: the coordinates may not match // because google truncates to 0.00001 deg; hence the use of the dist function. function calibrate(results,status) { var pno,flag ; if(status===google.maps.ElevationStatus.OVER_QUERY_LIMIT) alert('No calibration data available: over Google query limit') ; else if(status!==google.maps.ElevationStatus.OK||results.length!=2) alert('Calibration error') ; // now find whether any of our elevation results allow us to fix an altitude for(pno=0;pno<pending.length;pno++) if( dist(pending[pno][0].pos,results[0].location)<5 && dist(pending[pno][1],results[1].location)<5 ) { if(pno>0) alert('Warning: Google elevation results out of sequence') ; diff = results[0].elevation - results[1].elevation ; pending[pno][0].h = pending[pno][2] + diff ; } for(i=pno=0;pno<pending.length;pno++) if(pending[pno][0].h==null) { if(pno!=i) pending[i] = pending[pno] ; i += 1 ; } pending.length = i ; } /* -------------------------------------------------------------------------- */ /* FUNCTIONS FOR HANDLING THE ALTITUDE PROFILE */ /* -------------------------------------------------------------------------- */ function drawprofile() { infowindow.close() ; if((pro=procoords(segments))==null) return ; drawpro(pro) ; body.appendChild(pro.prodiv) ; body.appendChild(pro.curdiv) ; drawxcur(pro,selected) ; } /* ------------------------------- unprofile -------------------------------- */ function unprofile() { var i,match,node ; infowindow.close() ; if(pro==null||pro.prodiv==null) return ; pro.curdiv.removeEventListener('click',pro.curhandle) ; for(match=0,i=body.childNodes.length-1;match==0&&i>=0;i--) { node = body.childNodes[i] ; match = (node==pro.prodiv) ; body.removeChild(node) ; } pro = null ; } function reprofile() { if(pro!=null&&pro.prodiv!=null) { unprofile() ; drawprofile() ; } } /* -------------------------------------------------------------------------- */ /* ROUTEINFO IS A MENU WHICH ITSELF SUPPORTS THE COMBINE FUNCTION */ /* -------------------------------------------------------------------------- */ function routeinfo() { var s0,s1,s,d,asc,des,oalt,alt,nlabels,nowpts,i,unsaved,props,spacing,npix ; var maxsep,sep,outoforder,tlast,otime,time,tdist,ttime,ntimes ; infowindow.close() ; props = actions[loadno][4] ; tlast = null ; tdist = ttime = outoforder = 0 ; maxsep = nlabels = npix = des = asc = d = nowpts = ntimes = 0 ; for(s0=0;s0<segments.length;s0++) { nowpts += segments[s0].data.length ; for(oalt=null,s1=0;s1<segments[s0].data.length;otime=time,s1++) { if((alt=segments[s0].data[s1].h)!=null) { if(oalt!=null) { if(alt>oalt) asc += alt-oalt ; else des += oalt - alt ; } oalt = alt ; } if(segments[s0].data[s1].type!=null) nlabels += 1 ; npix += segments[s0].data[s1].photo.length ; time = segments[s0].data[s1].t ; if(time!=null) { time = time.getTime() ; ntimes += 1 ; } if(tlast!=null&&time!=null&&time<tlast) outoforder = 1 ; // out of order if(time!=null) tlast = time ; if(s1) { sep = dist(segments[s0].data[s1-1].pos,segments[s0].data[s1].pos) ; d += sep ; if(sep>maxsep) maxsep = sep ; if(time!=null&&otime!=null) { tdist += sep/1000 ; ttime += (time-otime)/(3600*1000) ; } } } } s = finalbox +'<nobr>Title: <b>'+routetitle+'</b> [' ; s += active + '"retitle()">Edit</span>' + ']</nobr><br>' ; if(longtitle==null) s += '<nobr>[' + active + '"longretitle()">' + 'Add long title</span>]</nobr><br>' ; else { if(longtitle.length<50) s += '<nobr>Long title: <b>' + longtitle + '</b>' ; else s += '<div style="margin-bottom:2px;border-bottom:solid 1px silver;' + 'padding-bottom:2px">Long title: ' + longtitle ; s += ' [' + active + '"longretitle()">Edit</span>]' ; if(longtitle.length<50) s += '</nobr><br>' ; else s += '</div>' ; } if(loadno>0) { s += '<nobr>&nbsp;&nbsp;&nbsp;Last added route' ; if(props.title!=null) s += ' (' + props.title + ')' ; s += ':</nobr><br><nobr>&nbsp;&nbsp;&nbsp;' ; } else s += '<nobr>' ; s += 'Track points on input: ' + props.inputlen ; if(props.optim.already) { s += ' (previously optimised)</nobr>' ; if(nowpts!=props.inputlen) s += '<br>Now ' + nowpts + ' track points' ; } else if(props.optim.ndel==0) { if(nactions==loadno+1) s += ' [' + active + '"optimprompt()">' ; else s += ' [' + inactive ; s += 'Optimise' + neutral + ']</span></nobr>' ; } else s += ', optimised to ' + (props.inputlen-props.optim.ndel) + '</nobr>' ; if(!props.optim.already&&props.inputlen-props.optim.ndel!=nowpts) s += '<br>Now ' + nowpts + ' track points' ; s += '<br>' ; if(outoforder==0) { if(ntimes==0) s += 'No timings provided<br>' ; else { s += '<nobr>' ; if(ntimes<nowpts) s += (nowpts-ntimes) + ' points have no associated timings ' ; s += '[' + active + '"deltimes()">Discard timings</span>' + neutral + ']<nobr><br>' ; } if(tdist>0&&ttime>0) s += 'Average speed = ' + (tdist/ttime).toFixed(1) + ' km/hr<br>' ; } else s += 'Times are out of sequence (will be discarded on download)<br>' ; if(nlabels>0) s += nlabels + ' labelled course point' + (nlabels>1?'s':'') + '<br>' ; if(npix>0) s += npix + ' photo' + (npix>1?'s':'') + '<br>' ; unsaved = unsavedchanges.length ; if(unsaved>0) s += unsaved + ' unsaved change' + (unsaved>1?'s':'') + '<br>' ; if(segments.length>1) s += segments.length + ' segments [' + active + '"combine()">Combine</span>' + neutral + ']<br>' + '<i>Note that segments must be combined before saving</i><br>' ; s += 'Max waypoint separation: '+maxsep.toFixed(0)+'m<br>' ; if(maxsep>=100) s += '<i>Note that separations &gt;100m are illegal on Garmin</i><br>' + '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[</span>' + active + '"extrapts()">' + 'Interpolate extra points</span>]<br>' + neutral ; s += 'Total distance: '+(d/1000).toFixed(3)+'km<br>' ; s += 'Total ascent: '+asc.toFixed(0)+'m<br>' ; s += 'Total descent: '+des.toFixed(0)+'m</div>' ; s += '</div>' ; infowindow.open(s,getbtnpos(0),'routeinfo') ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ------------------------- interpolate extra points ----------------------- */ function extrapts(opt) { var s0,s1,sep,data,n,opos,npos,i,lambda,lox,nlox,taskno,ind ; var task = [ 'extra' , selected[0] , selected[1] ] ; infowindow.close() ; for(nlox=s0=0;s0<segments.length;s0++) for(data=segments[s0].data,s1=1;s1<data.length;s1++) if((sep=dist(opos=data[s1-1].pos,npos=data[s1].pos))>100) { n = Math.floor(sep/95) ; insert(s0,s1,n) ; for(i=0;i<n;i++) { lambda = (i+1) / (n+1) ; data[s1+i].setpos(new google.maps. LatLng(lambda*npos.lat()+(1-lambda)*opos.lat(), lambda*npos.lng()+(1-lambda)*opos.lng())) ; } if(selected[0]==s0&&s1<=selected[1]) selected[1] += n ; task.push([s0,s1,data.slice(s1-1,s1+n+1)]) ; s1 += n ; nlox += n+2 ; } if(nlox==0) return ; done(task) ; xpending.push(task) ; lox = new Array(nlox) ; for(ind=0,taskno=3;taskno<task.length;taskno++) { n = task[taskno][2].length ; for(i=0;i<n;i++) lox[ind++] = task[taskno][2][i].pos ; } elevator.getElevationForLocations( {locations:lox} , function (results,status) { // assume that the results come in sequence, ie. correspond to xpending[0] var task=xpending.shift(),taskno,d0,dn,lambda ; if(status===google.maps.ElevationStatus.OVER_QUERY_LIMIT) alert('No calibration data available: over Google query limit') ; else if(status!==google.maps.ElevationStatus.OK) alert('Calibration error') ; for(ind=0,taskno=3;taskno<task.length;taskno++,ind+=n+2) { n = task[taskno][2].length-2 ; d0 = task[taskno][2][0].h - results[ind].elevation ; dn = task[taskno][2][n+1].h - results[ind+n+1].elevation ; if( dist(task[taskno][2][0].pos,results[ind].location)>5 || dist(task[taskno][2][n+1].pos,results[ind+n+1].location)>5 ) alert('Anomaly with Google elevation results') ; for(i=0;i<n;i++) { lambda = (i+1) / (n+1) ; task[taskno][2][1+i].h = results[ind+1+i].elevation + lambda*dn + (1-lambda)*d0 ; } } } ) ; routeinfo() ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ------------------------------- combine1 --------------------------------- */ function combine1(sa,sb) { var i,ca,cb,calen,cblen,la=null,lb=null,cdup=0 ; undraw(sb) ; disconnect(sb-1) ; calen = segments[sa].data.length ; cblen = segments[sb].data.length ; cb = segments[sb].data[0].pos ; cdup = ( ca = cb.equals(segments[sa].data[calen-1].pos) ) ; if(cdup) { la = segments[sa].data[calen-1] ; lb = segments[sb].data[0] ; segments[sa].data.length = ( calen -= 1 ) ; } if(selected[0]==sb) selected = [ sa , selected[1]+calen ] ; segments[sa].data = segments[sa].data.concat(segments[sb].data) ; return [ cblen , cdup , la , lb ] ; } function combinework() { var task,s0 ; for(task=['combine',segments.length],s0=1;s0<segments.length;s0++) task.push(combine1(0,s0)) ; segments.length = 1 ; return task ; } /* -------------------------------------------------------------------------- */ function combine() { infowindow.close() ; done(combinework()) ; drawsel(1) ; redraw(0) ; blackout(dlbtn) ; } function recombine() { var s0 ; for(s0=1;s0<segments.length;s0++) { undraw(s0) ; combine1(0,s0) ;} segments.length = 1 ; drawsel(1) ; redraw(0) ; blackout(dlbtn) ; } /* -------------------------------------------------------------------------- */ // combine returns [ cblen , cdup , la , lb ] ; function uncombine(task) { var i,j,llen,flag,subtask ; for(flag=0,s0=task[1]-1,i=task.length-1;i>=2;i--,s0--) { subtask = task[i] ; cblen = subtask[0] ; cdup = subtask[1] ; llen = segments[0].data.length ; segments[s0] = { data: segments[0].data.slice(llen-cblen,llen) , route: null , clickhandler: null } ; llen = segments[0].data.length = llen+cdup-cblen ; if(cdup) { segments[0].data[llen-1] = subtask[2] ; segments[s0].data[0] = subtask[3] ; } if(flag==0&&selected[1]>=llen) { selected = [ s0 , selected[1]-llen ] ; flag = 1 ; } } drawsel(1) ; undraw(0) ; for(i=0;i<segments.length;i++) { draw(i) ; connect(i) ; } greyout(dlbtn) ; } /* -------------------------------------------------------------------------- */ /* WPINFO IS A MENU GIVING ACCESS TO THE SETALT FUNCTION */ /* -------------------------------------------------------------------------- */ function wpinfo() { infowindow.close() ; var s0=selected[0],s1=selected[1],alt,nalt,s,x,lat,lng,grad,gradstr,time ; var datum = segments[s0].data[s1] , pos = datum.pos ; lat = pos.lat() ; lng = pos.lng() ; s = finalbox ; if(lat>=0) s += lat.toFixed(5) + '\u00b0 N, ' ; else { lat = -lat ; s += lat.toFixed(5) + '\u00b0 S, ' ; } if(lng>=0) s += lng.toFixed(5) + '\u00b0 E<br>' ; else { lng = -lng ; s += lng.toFixed(5) + '\u00b0 W<br>' ; } x = new LatLon(lat,lng) ; if(lat>49.9&&lat<62&&lng>-12&&lng<2.5&&lat-1.5*lng<75) s += 'OS grid ref: ' + OsGridRef.latLonToOsGrid(x) ; else s += 'UTM coords = ' + x.toUtm() ; s += '<br>' ; alt = segments[s0].data[s1].h ; if(alt!=null) s += 'Altitude: ' + alt.toFixed(0) + 'm ' + active + '"setalt(1)">[Edit]' ; else s += active + '"setalt(0)">Set altitude' ; s += '</span><br>' ; time = segments[s0].data[s1].t ; if(time!=null&&time.getFullYear()>1980) s += 'Date: ' + time.toDateString() + '<br>' + 'Time: ' + time.toTimeString() + '<br>' ; if(datum.type!=null) s += datum.type + ': ' + datum.marker.title + ' [' + active + '"labelprompt()">Edit</span>]<br>' ; if(alt==null||s1==segments[s0].data.length-1) nalt = null ; else { nalt = segments[s0].data[s1+1].h ; if(nalt!=null) x = dist(pos,segments[s0].data[s1+1].pos) ; } if(nalt!=null&&Math.abs(nalt-alt)<x) { grad = 100*Math.asin((nalt-alt)/x) ; gradstr = Math.abs(grad).toFixed(0) ; if(gradstr=='0') s += 'Flat<br>' ; else if(grad>0) s += 'Climb '+gradstr+'%<br>' ; else s += 'Descend: '+gradstr+'%<br>' ; } s += '<span style="font-size:80%">' ; if(segments.length>1) s += 'Segment '+s0+' p' ; else s += 'P' ; s += 'oint ' + s1 + '</span></div>' ; infowindow.open(s,pos,'wpinfo') ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------- setalt ---------------------------------- */ function setalt(edit) { infowindow.close() ; var s0=selected[0],s1=selected[1],x,y=null,oldalt ; oldalt = segments[s0].data[s1].h.toFixed(0) ; if(edit) x = prompt('Enter altitude (m):',oldalt) ; else x = prompt('Enter altitude (m):') ; if(x==null) return ; if(x!=''&&isNaN(y=parseFloat(x))) { alert(x+' is not a number') ; return ; } if(y==null&&oldalt==null) return ; if(y!=null&&Math.abs(y-oldalt)<0.1) return ; segments[s0].data[s1].h = y ; done(['setalt',s0,s1,oldalt,y]) ; reprofile() ; wpinfo() ; } /* -------------------------------------------------------------------------- */ /* THE LABELS ARE ACCESSED FROM THE PEN BUTTON OR BY CLICKING ON THE MAP */ /* -------------------------------------------------------------------------- */ function labelprompt() { var oldcaption='',oldtype,type='Generic',s0=selected[0],s1=selected[1] ; var str , flag = (infowindow.close()=='wpinfo') ; var datum = segments[s0].data[s1] ; oldtype = datum.type ; if(oldtype!=null) oldcaption = datum.marker.title ; if(oldcaption==null) oldcaption = '' ; if(oldtype!=null) type = oldtype ; if(oldtype==null) str = 'Enter' ; else str = 'Modify or delete' ; var caption = window.prompt(str+' label:',oldcaption) ; if(caption==null) { if(flag) wpinfo() ; else walkto(s0,s1) ; return ; } else if(caption=='') type = null ; else caption = caption.substring(0,10) ; if(caption==oldcaption) { if(flag) wpinfo() ; else walkto(s0,s1) ; return ; } segments[s0].data[s1].setlabel(type,caption) ; done(['editlabel',s0,s1,oldcaption,caption,oldtype,type]) ; if(flag) wpinfo() ; else walkto(s0,s1) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ------------------------------ labelcycle -------------------------------- */ function labelcycle() { var s0,s1,datum,types,caption,flag=(infowindow.close()=='wpinfo') ; ; for(s0=0;s0<segments.length;s0++) for(s1=0;s1<segments[s0].data.length;s1++) { datum = segments[s0].data[s1] ; if(datum.marker!=this) continue ; types = datum.labelcycle() ; caption = datum.marker.title ; selected = [s0,s1] ; done(['editlabel',s0,s1,caption,caption,types[0],types[1]]) ; if(flag) wpinfo() ; return ; } } function photoprompt(e) { var s0=selected[0],s1=selected[1] ; if(e!=null) e.preventDefault() ; var flag = (infowindow.close()=='wpinfo') ; var datum = segments[s0].data[s1] ; var photo = window.prompt('Enter photo name:','') ; if(photo!=null&&photo!='') { done(['editphoto',s0,s1,datum.photo.length,null,photo]) ; datum.addphoto(photo) ; } if(flag) wpinfo() ; else walkto(s0,s1) ; } function photoedit(ind) { var s0=selected[0],s1=selected[1],i ; var flag = (infowindow.close()=='wpinfo') ; var datum = segments[s0].data[s1] ; var photo = window.prompt('New photo name:',datum.photo[ind]) ; if(photo!=null&&photo!='') for(i=0;i<datum.photo.length;i++) if(datum.photo[i]==photo) { photo = null ; break ; } if(photo!=null) { if(photo=='') photo = null ; done(['editphoto',s0,s1,ind,datum.photo[ind],photo]) ; datum.setphoto(ind,photo) ; } if(flag) wpinfo() ; else walkto(s0,s1) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ----------------------------- display photo ------------------------------ */ var lmove,rmove ; function advance(s0,s1,ind) { for(ind++;;ind++) { if(ind>=segments[s0].data[s1].photo.length) { ind = 0 ; s1 += 1 ; } if(s1==segments[s0].data.length) { s0 += 1 ; if(s0==segments.length) return null ; s1 = 0 ; } if(ind<segments[s0].data[s1].photo.length) return [s0,s1,ind] ; } } function retreat(s0,s1,ind) { for(ind--;;ind--) { if(ind<0) { s1 -= 1 ; ind = null ; } if(s1<0) { if(s0==0) return null ; else s0 -= 1 ; s1 = segments[s0].data.length-1 ; } if(ind==null) ind = segments[s0].data[s1].photo.length - 1 ; if(ind>=0) return [s0,s1,ind] ; } } function prev() { dodisplay(lmove[0],lmove[1],lmove[2],-1) ; } function backtogps() { document.onkeydown = keystroke ; window.removeEventListener('resize',resize) ; body.removeChild(imgdiv) ; } function next() { dodisplay(rmove[0],rmove[1],rmove[2],1) ; } function display(ind) { var phind ; document.onkeydown = imgwalk ; window.addEventListener('resize',resize) ; infowindow.close() ; imgdiv = document.createElement('div') ; imgdiv.setAttribute('style','position:fixed;width:100%;height:100%;'+ 'left:0;top:0;background:black') ; dodisplay(selected[0],selected[1],ind,1) ; body.appendChild(imgdiv) ; } function dodisplay(s0,s1,ind,dir) { var phind=findimg(segments[s0].data[s1].photo[ind]) , pre=null ; selected[0] = s0 ; selected[1] = s1 ; lmove = retreat(s0,s1,ind) ; rmove = advance(s0,s1,ind) ; if(dir<0&&lmove!=null) pre = findimg(segments[lmove[0]].data[lmove[1]].photo[lmove[2]]) ; else if(dir>=0&&rmove!=null) pre = findimg(segments[rmove[0]].data[rmove[1]].photo[rmove[2]]) ; if(pre!=null) pre = imginfo.list[pre] ; gendisplay(imgdiv,imginfo.list[findimg(segments[s0].data[s1].photo[ind])], imginfo.sizes,lmove==null?null:'javascript:prev()', 'javascript:backtogps()',rmove==null?null:'javascript:next()', 'GPS track',pre) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ------------------------------ image walk -------------------------------- */ function imgwalk(e) { e.preventDefault() ; if(e.keyCode==39) { if(rmove!=null) next() ; return ; } else if(e.keyCode==37) { if(lmove!=null) prev() ; return ; } else if(e.keyCode==40) reduce() ; else if(e.keyCode==38) enlarge() ; else if(e.keyCode==70) enterfullscreen() ; else backtogps() ; } /* --------------------------------- photo info ----------------------------- */ function phinfo(i) { infowindow.close() ; var s0=selected[0],s1=selected[1],s,shape,ind,hind,r,k ; var list=imginfo.list,sizes=imginfo.sizes ; s = finalbox + 'Name: ' + list[i].name + '<br>Title: ' + list[i].title ; for(hind=null,ind=0;ind<i;ind++) if(list[ind].name==null) hind = ind ; if(hind!=null) s += "<br>Under \u201c" + list[hind].title + "\u201d" ; // how many sizes? for(r=ind=0;ind<sizes.length;ind++) if(sizes[ind].type==undefined) r += 1 ; s += '<br>Available in ' + r + ' size' + (r>1?'s: ':': ') ; // print the sizes for(k=ind=0;ind<sizes.length;ind++) if(sizes[ind].type==undefined) { if(k>0) { if(k==r-1) s += ' and ' ; else s += ', ' ; } shape = imgsize(list[i],sizes,ind) ; s += shape[0] + 'x' + shape[1] ; k += 1 ; } shape = list[i].thumbshape ; s += '<br>Thumb: ' + shape[0] + 'x' + shape[1] ; for(ind=0;ind<sizes.length;ind++) if(sizes[ind].type=='raw') { shape = imgsize(list[i],sizes,ind) ; s += '<br>Raw: ' + shape[0] + 'x' + shape[1] ; } if(imginfo.pixpage!=null) s += '<br><a style="cursor:pointer;color:#0000bd;text-decoration:none" '+ 'href="'+imginfo.pixpage+'" target="_blank">'+ 'Full photo set</a>'+neutral+' (opens in new tab/window)</span>' ; if(list[i].retid!=null) { for(hind=null,ind=0;ind<=i;ind++) if(list[ind].retpage!=undefined&&list[ind].retpage!=null) hind = list[ind].retpage + '.html#' + list[i].retid ; if(hind!=null) s += '<br><a style="cursor:pointer;color:#0000bd;'+ 'text-decoration:none" href="' + hind + '" target="_blank">'+ 'Route notes</a>'+neutral+' (opens in new tab/window)</span>' ; } infowindow.open(s,segments[s0].data[s1].pos,'phinfo') ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ------------------------- snip: apply scissors -------------------------- */ function snipwork(s0,s1) { var i,k,newlen ; undraw(s0) ; segments.length += 1 ; for(i=segments.length-1;i>s0+1;i--) segments[i] = segments[i-1] ; newlen = segments[s0].data.length - s1 ; segments[s0+1] = new genseg(segments[s0].data.slice(s1),segments[s0].props) ; segments[s0+1].dots = segments[s0].dots ; segments[s0+1].dothandler = segments[s0].dothandler ; segments[s0].dots = segments[s0].dothandler = null ; segments[s0].data.length = s1 + 1 ; segments[s0].data[s1] = new datatype(segments[s0].data[s1].pos,segments[s0].data[s1].h) ; draw(s0) ; draw(s0+1) ; for(i=s0+2;i<segments.length;i++) recolour(i) ; drawsel(1,[s0+1,0]) ; greyout(dlbtn) ; } function snip() { var i,s0=selected[0],s1=selected[1] ; infowindow.close() ; done(['snip',s0,s1]) ; snipwork(s0,s1) ; } /* ------------------------ discard: bin a segment ------------------------- */ function binwork(s0) { var i ; obliterate(s0) ; for(i=s0;i<segments.length-1;i++) segments[i] = segments[i+1] ; segments.length -= 1 ; for(i=s0;i<segments.length;i++) recolour(i) ; connect(s0-1) ; selected[1] = 0 ; if(selected[0]==segments.length) selected[0] = 0 ; drawsel(1) ; if(segments.length==1) blackout(dlbtn) ; } function discard() { var i,s0=selected[0] ; infowindow.close() ; done(['bin',s0,segments[s0]]) ; binwork(s0) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------- actionname ------------------------------ */ function actionname(x) { if(x[0]=='bin') return 'delete segment' ; if(x[0]=='snip') return 'split segment' ; if(x[0]=='editlabel') { if(x[4]=='') return 'delete label' ; else if(x[3]=='') return 'label waypoint' ; else return 'edit label' ; } if(x[0]=='edittitle') return 'edit title' ; if(x[0]=='editlongtitle') return 'edit long title' ; if(x[0]=='wpdel') return 'delete waypoint' ; if(x[0]=='move') { if(x[5]) return 'insert waypoint' ; else return 'drag waypoint' ; } if(x[0]=='recal') return 'recalibrate altitudes' ; if(x[0]=='setalt') return 'set waypoint altitude' ; if(x[0]=='resign') return 'change label symbol' ; if(x[0]=='combine') return 'combine '+x[1]+' segments' ; if(x[0]=='revseg') return 'reverse segment' ; if(x[0]=='interpolate') return 'interpolate missing altitudes' ; if(x[0]=='optimise') return 'optimisation' ; if(x[0]=='deltimes') return 'delete times' ; if(x[0]=='editphoto') { if(x[5]==null) return 'delete photo' ; else if(x[4]==null) return 'add photo' ; else return 'change photo' ; } if(x[0]=='extra') return 'interpolate extra points' ; } function actiontype(x) { if( x=='snip'||x=='combine'||x=='interpolate' || x=='optimise'||x=='load' ) return 0 ; else return 1 ; } /* -------------------------------------------------------------------------- */ function done(something) { if( nactions>0 && unsavedchanges.length>0 && something[0]=='editlabel' && actions[nactions-1][0]==something[0] && actions[nactions-1][1]==something[1] // don't merge change with delete && actions[nactions-1][2]==something[2] && something[6]!=null ) { actions[nactions-1][4] = something[4] ; // caption actions[nactions-1][6] = something[6] ; // type } else { actions[nactions++] = something ; donesomething() ; } } function donesomething() { actions.length = nactions ; blackout(undobtn) ; greyout(redobtn) ; if(actiontype(actions[nactions-1][0])!=0) { if(unsavedchanges.length>=3) unsavedchanges.push(null) ; else unsavedchanges.push(actionname(actions[nactions-1])) ; } } /* -------------------------------------------------------------------------- */
/*page*/ /* --------------------------------- undo ---------------------------------- */ function undo() { infowindow.close() ; var opts = active + '"confirmedundo()">Undo ' + actionname(actions[nactions-1])+'</span>' ; infowindow.open(opts,getbtnpos(5),'undo') ; } function confirmedundo() { var i,ano=nactions-1,action=actions[ano][0],s0=actions[ano][1],s1,caption ; var oldcaption,task,ind ; infowindow.close() ; if(action!='revseg'&&action!='interpolate'&&action!='deltimes') s1 = actions[ano][2] ; if(action=='bin') { disconnect(s0-1) ; for(i=segments.length;i>s0;i--) { segments[i] = segments[i-1] ; recolour(i) ; } segments[s0] = s1 ; for(s1=0;s1<segments[s0].data.length;s1++) segments[s0].data[s1].setmap(map) ; draw(s0) ; connect(s0-1) ; connect(s0) ; if(selected[0]>=s0) selected[0] += 1 ; drawsel(1) ; greyout(dlbtn) ; } else if(action=='snip') // undo snip { selected = [ s0 , segments[s0].data.length-1 ] ; combine1(s0,s0+1) ; for(i=s0+1;i<segments.length-1;i++) { segments[i] = segments[i+1] ; recolour(i) ; } segments.length -= 1 ; if(segments.length==1) blackout(dlbtn) ; draw(s0) ; drawsel(1) ; } else if(action=='editlabel') // undo create/edit/delete label segments[s0].data[s1].setlabel(actions[ano][5],actions[ano][3]) ; else if(action=='edittitle') settitle(s0) ; else if(action=='editlongtitle') longtitle = s0 ; else if(action=='wpdel') // ['wpdel',s0,s1,wpdelwork(s0,s1)] { insert(s0,s1,1) ; segments[s0].data[s1] = actions[ano][3] ; segments[s0].data[s1].setmap(map) ; redrawconnect(s0,s1) ; drawsel(1,[s0,s1]) ; } else if(action=='move') { if(actions[ano][5]) wpdelwork(s0,s1) ; else move(s0,s1,actions[ano][3]) ; } else if(action=='recal') calwork(s0,-s1) ; else if(action=='setalt') segments[s0].data[s1].h = actions[ano][3] ; else if(action=='combine') uncombine(actions[ano]) ; else if(action=='revseg') revsegwork(s0) ; else if(action=='interpolate') for(i=0;i<s0.length;i++) segments[0].data[s0[i]].h = null ; else if(action=='deltimes') for(i=0;i<s0.length;i++) segments[s0[i][0]].data[s0[i][1]].t = s0[i][2] ; else if(action=='optimise') // [ 'load' , s0 , data.slice() , loadno , props ] { for(ano--;ano>=0&&actions[ano][0]!='load';ano--) ; segments[s0].data = actions[ano][2] ; actions[loadno][4].optim.ndel = 0 ; redraw(s0) ; drawsel(1,[s0,0]) ; } else if(action=='editphoto') { ind = actions[ano][3] ; if(actions[ano][5]==null) // undo delete for(i=segments[s0].data[s1].photo.length;i>ind;i--) segments[s0].data[s1].photo[i] = segments[s0].data[s1].photo[i-1] ; if(ind>=segments[s0].data[s1].photo.length) segments[s0].data[s1].addphoto(actions[ano][4]) ; else segments[s0].data[s1].setphoto(ind,actions[ano][4]) ; } else if(action=='extra') for(selected=[s0,s1],i=actions[ano].length-1;i>=3;i--) { task = actions[ano][i] segments[task[0]].data.splice(task[1],task[2].length-2) ; } nactions -= 1 ; if(nactions<=1) greyout(undobtn) ; blackout(redobtn) ; if(actiontype(actions[nactions][0])!=0&&unsavedchanges.length>0) unsavedchanges.length -= 1 ; ; if(action=='optimise'||action=='dltimes') routeinfo() ; else if(action=='editphoto'||action=='editlabel') walkto(s0,s1) ; } /* --------------------------------- move ----------------------------------- */ function move(s0,s1,pos) { segments[s0].data[s1].setpos(pos) ; redrawconnect(s0,s1) ; drawsel(1) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* --------------------------------- redo ---------------------------------- */ function redo() { infowindow.close() ; var opts = active + '"confirmedredo()">Redo ' + actionname(actions[nactions])+'</span>' ; infowindow.open(opts,getbtnpos(6),'redo') ; } function confirmedredo() { var i,action=actions[nactions][0],s0=actions[nactions][1],s1,caption,a,b,c ; var task,ind,photo ; if(action!='bin'&&action!='revseg'&&action!='interpolate'&&action!='deltimes') s1 = actions[nactions][2] ; infowindow.close() ; if(action=='bin') binwork(s0) ; else if(action=='snip') snipwork(s0,s1) ; else if(action=='editlabel') // redo create/edit/delete label segments[s0].data[s1].setlabel(actions[nactions][6],actions[nactions][4]) ; else if(action=='edittitle') settitle(s1) ; else if(action=='editlongtitle') longtitle = s1 ; else if(action=='wpdel') wpdelwork(s0,s1) ; else if(action=='move') // ['move',s0,s1,oldpos,newpos,inserted] { if(actions[nactions][5]) insert(s0,s1,1) ; move(s0,s1,actions[nactions][4]) ; } else if(action=='recal') calwork(s0,s1) ; else if(action=='setalt') segments[s0].data[s1].h = actions[nactions][4] ; else if(action=='combine') recombine(actions[nactions]) ; else if(action=='revseg') revsegwork(s0) ; else if(action=='interpolate') interpolatework(segments[0].data) ; else if(action=='deltimes') for(i=0;i<s0.length;i++) segments[s0[i][0]].data[s0[i][1]].t = null ; else if(action=='optimise') { result = optimise(segments[s0].data,actions[nactions][2]) ; actions[loadno][4].optim.ndel = segments[s0].data.length - result.length ; segments[s0].data = result ; redraw(s0) ; drawsel(1,[s0,0]) ; routeinfo() ; } else if(action=='editphoto') { ind = actions[nactions][3] ; photo = actions[nactions][5] ; if(actions[nactions][4]==null) segments[s0].data[s1].addphoto(photo) ; else segments[s0].data[s1].setphoto(ind,photo) ; } else if(action=='extra') for(selected=[s0,s1],i=3;i<actions[nactions].length;i++) { task = actions[nactions][i] ; a = segments[task[0]].data.slice(0,task[1]) ; b = task[2].slice(1,task[2].length-1) ; c = segments[task[0]].data.slice(task[1]) ; segments[task[0]].data = a.concat(b,c) ; } nactions += 1 ; if(nactions==actions.length) greyout(redobtn) ; blackout(undobtn) ; if(actiontype(actions[nactions-1][0])!=0) unsavedchanges.push(action) ; if(action=='editphoto'||action=='editlabel') walkto(s0,s1) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ---------------------------------- dl ----------------------------------- */ function dl(opt) { var props=new propstype(),str,ndel,origlen,ano,i,npix,noalt,filename ; infowindow.close() ; if(opt==undefined) opt = 0 ; // filename i = routetitle.indexOf(' ') ; if(i<=0) filename = routetitle ; else filename = routetitle.substring(0,i) ; if(filename==''||filename==null) filename = 'Untitled' ; filename += '.tcx' ; // check for photos and altitudeless points for(noalt=npix=i=0;i<segments[0].data.length;i++) { npix += segments[0].data[i].photo.length ; if(segments[0].data[i].h==null) noalt += 1 ; } // photo list if(npix>0&&imginfo.status=='ready') { if(imginfo.type=='tcx') props.list = imginfo.uri ; // vice 'uri' else { props.list = document.URL ; if((i=props.list.lastIndexOf('?'))>=0) props.list = props.list.substring(0,i) ; props.list = reluri(props.list,imginfo.uri) ; } } if(opt) // write overview and return { str = writeoverview(segments,routetitle,props.list) ; saveAs(new Blob([str],{type: "text/plain;charset=utf-8"}),filename) ; return ; } // decide what to do if some points have no altitudes if(noalt&&!confirm(noalt+' waypoints have no associated altitudes.\n'+ 'You can hit [OK] and I will interpolate altitudes (not guaranteed), or\n'+ 'you can hit [Cancel] and try again later when the altitudes may be '+ 'available.')) return null ; if(noalt) interpolate() ; // record optimisation for(ano=-1,ndel=origlen=0,i=loadno;i>=0;i=actions[i][3]) { ndel += actions[i][4].optim.ndel ; origlen += actions[i][4].optim.origlen ; if(actions[i][4].optim.parms!=null&&ano==-1) ano = i ; } if(ano>=0) props.optim.parms = actions[ano][4].optim.parms ; props.optim.origlen = origlen ; props.optim.ndel = ndel ; // title props.title = routetitle ; props.longtitle = longtitle ; str = writetcx(props,segments[0].data) ; if(str==null) return ; unsavedchanges = [] ; saveAs(new Blob([str],{type: "text/plain;charset=utf-8"}),filename) ; } /* ------------------------------ interpolate ------------------------------- */ function interpolate() { infowindow.close() ; var response = interpolatework(segments[0].data) ; if(response.length>0) done([ 'interpolate' , response ]) ; } function interpolatework(a) { var i,j,k,response,x,y,distance,sum,len=a.length ; for(response=[],i=0;i<len;i=j) { for(;i<len&&a[i].h!=null;i++) ; // advance to null for(j=i+1;j<len&&a[j].h==null;j++) response.push(j) ; // advance to non-null if(i==0) { for(y=a[j].h;i<j;i++) a[i].h = y ; continue ; } if(j==len) { for(x=a[i-1].h;i<j;i++) a[i].h = x ; continue ; } distance = new Array(1+j-i) ; for(sum=k=0;k<=j-i;k++) sum = distance[k] = sum + dist(a[i+k-1].pos,a[i+k].pos) ; for(x=a[i-1].h,y=a[j].h,k=0;k<j-i;k++) a[i+k].h = ( x*(sum-distance[i]) + y*distance[i] ) / sum ; } return response ; } /* -------------------------------------------------------------------------- */

Archived from routemaster.html

/* ---------------------------- relative uri ------------------------------- */ function reluri(u1,u2) { var last = u1.lastIndexOf('/') ; if(last<0) return u2 ; u1 = u1.substring(0,last) ; while(u2.substring(0,3)=='../') { last = u1.lastIndexOf('/') ; if(last<0) return u2 ; u2 = u2.substring(3) ; u1 = u1.substring(0,last) ; } return u1 + '/' + u2 ; } /* -------------------------------------------------------------------------- */ function textdiv(title,body,lim) { var div=document.createElement('div'),b,nobr ; if(lim==undefined||lim<=0) lim = null ; if(title!=null) { b = document.createElement('b') ; b.appendChild(document.createTextNode(title+': ')) ; } if(lim==null||body.length<lim) { nobr = document.createElement('nobr') ; if(title!=null) nobr.appendChild(b) ; nobr.appendChild(document.createTextNode(body)) ; div.appendChild(nobr) ; } else { if(title!=null) div.appendChild(b) ; div.appendChild(document.createTextNode(body)) ; div.setAttribute('style', 'margin-bottom:2px;border-bottom:solid 1px silver;padding-bottom:2px') ; } return div ; } /* -------------------------------------------------------------------------- */ function seginfodiv(segments,segno) { var div=document.createElement('div'),props=segments[segno].props,prose ; div.appendChild(textdiv(null,'Segment ' + segno + ' of ' + segments.length + ' (' + segments[segno].data.length + ' points)')) ; if(props.title!=null) div.appendChild(textdiv('Title',props.title)) ; if(props.source!=null) div.appendChild(textdiv('Source',props.source[0])) ; if(props.longtitle!=null) div.appendChild(textdiv('Long Title',props.longtitle,50)) ; if(props.stats!=null) div.appendChild(textdiv('Stats',props.stats)) ; prose = textdiv(null, 'Use the left and right arrow keys to move between segments') ; prose.setAttribute('style','color:silver') ; div.appendChild(prose) ; return div ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------------------------------------------------- */ function highdiv(props,list,sizes,sizeno,names) { var div,scroll,p,a,nfetched=2 ; var items,i,ind,maxh,minw,sum,scroll,image=[null,null,null] ; for(items=[],i=0;i<names.length;i++) if((ind=findimage(list,names[i]))!=null) { scroll = { ind: ind, shape: imgshape(list[ind],sizes,sizeno), top: 0 } ; items.push(scroll) ; } div = document.createElement('div') ; div.setAttribute('style','font-family:helvetica') ; if(items.length>0) { minw = items[0].shape[0] ; if(items.length>1) minw += items[items.length-1].shape[0] ; for(i=0;i<2&&i<items.length;i++) image[i] = new scrolltype(i) ; for(maxh=i=0;i<items.length;i++) { if(items[i].shape[1]>maxh) maxh = items[i].shape[1] ; if(i) { sum = items[i].shape[0] + items[i-1].shape[0] ; if(sum<minw) minw = sum ; } } for(i=0;i<items.length;i++) items[i].top = Math.floor(0.5+(maxh-items[i].shape[1])/2) ; scroll = document.createElement('div') ; if(items.length==1) minw -= 4 ; scroll.setAttribute('style','position:absolute;width:'+(minw+4)+'px;'+ 'height:'+maxh+'px;overflow:hidden') ; for(sum=i=0;i<2&&i<items.length;sum+=items[i].shape[0]+4,i++) { image[i].addimage(sum) ; scroll.appendChild(image[i].img) ; } div.appendChild(scroll) ; p = document.createElement('div') ; p.setAttribute('style','width:'+(minw+4)+'px;'+'height:'+(maxh+4)+'px') ; div.appendChild(p) ; } if(props.title!=null) div.appendChild(textdiv('Title',props.title)) ; if(props.longtitle!=null) div.appendChild(textdiv('Long Title',props.longtitle,50)) ; if(props.stats!=null) div.appendChild(textdiv('Stats',props.stats)) ; if(props.tracklink!=null) { nobr = document.createElement('nobr') ; a = document.createElement('a') ; a.setAttribute('style', 'cursor:pointer;color:#0000bd;text-decoration:none') ; a.setAttribute('href',props.tracklink) ; a.setAttribute('target',"_blank") ; a.setAttribute('onclick',"infowindow.close()") ; a.appendChild(document.createTextNode('View track')) ; nobr.appendChild(a) ; nobr.appendChild(document.createTextNode(' (opens in new tab/window)')) ; div.appendChild(nobr) ; } return { div:div , scroller: items.length<=2?null:setInterval(scroller,30) } ; function scrolltype(i) { this.img = this.top = this.pos = null ; this.ind = i ; // index into items this.wid = items[i].shape[0] ; this.addimage = function(pos) { var fetch=null,ind=this.ind ; if(ind<items.length-1&&nfetched<=ind) { nfetched += 1 ; fetch = function() { new Image().src = jpg(list[items[ind+1].ind],sizes,sizeno) ; } ; } this.img = genimage(list[items[ind].ind],sizes,sizeno,fetch) ; this.pos = pos ; this.scrollimage() ; } this.scrollimage = function() { this.img.setAttribute('style', 'position:absolute;top:'+items[this.ind].top+'px;left:'+this.pos+'px') ; } } function scroller() { var i,ind,offset ; for(i=0;i<3;i++) if(image[i]!=null) image[i].pos -= 1 ; if(image[0].pos+image[0].wid<=0) { scroll.removeChild(image[0].img) ; for(i=0;i<2;i++) image[i] = image[i+1] ; image[2] = null ; } for(i=0;i<3&&image[i]!=null;i++) image[i].scrollimage() ; offset = image[i-1].pos + image[i-1].wid + 4 ; if(offset<minw) { if(image[i-1].ind==items.length-1) ind = 0 ; else ind = image[i-1].ind + 1 ; image[i] = new scrolltype(ind) ; image[i].addimage(offset) ; scroll.appendChild(image[i].img) ; } } } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------------------------------------------------- */ function greybtn(uri,name) { if(name=='cursor') name = 'arrow' ; else name = 'grey' + name ; return resuri + name + '.png' ; } function blackbtn(uri,name) { if(name=='cursor') name = 'hand' ; else name = 'black' + name ; return resuri + name + '.png' ; } function buttonimg(gif) { var img = document.createElement('img') ; img.setAttribute('src',gif) ; img.setAttribute('width','24') ; img.setAttribute('height','24') ; return img ; } function buttoncell(gif1,gif2) { var td=document.createElement('td'),nobr=document.createElement('nobr') ; td.setAttribute('style','padding-bottom:4px') ; nobr.appendChild(buttonimg(gif1)) ; if(gif2!=null&&gif2!=undefined) { nobr.appendChild(document.createTextNode(' ')) ; nobr.appendChild(buttonimg(gif2)) ; } td.appendChild(nobr) ; return td ; } function textcell(p1,p2) { var td=document.createElement('td'),nobr ; td.setAttribute('style','padding-bottom:4px') ; nobr = document.createElement('nobr') ; nobr.appendChild(document.createTextNode(p1)) ; td.appendChild(nobr) ; if(p2!=null&&p2!=undefined) { td.appendChild(document.createElement('br')) ; nobr = document.createElement('nobr') nobr.appendChild(document.createTextNode(p2)) ; td.appendChild(nobr) ; } return td ; } function appendrow(td,p) { var nobr = document.createElement('nobr') ; nobr.appendChild(document.createTextNode(p)) ; td.appendChild(nobr) ; td.appendChild(document.createElement('br')) ; } function genlink(uri,legend) { var a = document.createElement('a') ; a.setAttribute('style','cursor:pointer;color:#0000bd;text-decoration:none') ; a.setAttribute('href',uri) ; a.appendChild(document.createTextNode(legend)) ; return a ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------------------------------------------------- */ function northernmost(data) { var i,maxlat,maxi ; for(i=0;i<data.length;i++) if(i==0||data[i].pos.lat()>maxlat) { maxi = i ; maxlat = data[i].pos.lat() ; } return data[maxi].pos ; } /* -------------------------------------------------------------------------- */ function helpdiv(uri,noblank) { var div=document.createElement('div'),d,t,tr,td,a ; if(noblank==undefined) noblank = 0 ; d = document.createElement('div') d.setAttribute('style','margin-bottom:2px;border-bottom:solid 1px silver;' + 'padding-bottom:2px;font-family:helvetica') ; t = document.createElement('table') ; t.setAttribute('cellpadding',0) ; t.setAttribute('cellspacing',0) ; tr = document.createElement('tr') ; tr.appendChild(buttoncell(greybtn(uri,'cursor'),blackbtn(uri,'cursor'))) ; td = document.createElement('td') ; td.setAttribute('rowspan',100) ; td.appendChild(document.createTextNode('\u00a0\u00a0\u00a0')) ; // &nbsp; tr.appendChild(td) ; tr.appendChild(textcell ('toggle between using the mouse to select waypoints and to drag the map', '(the space bar has the same function)')) ; t.appendChild(tr) ; tr = document.createElement('tr') ; tr.appendChild(buttoncell(blackbtn(uri,'settings'),blackbtn(uri,'dl'))) ; tr.appendChild(textcell('access to help menu and to various tools and ' + 'functions /','download route as .tcx')) ; t.appendChild(tr) ; tr = document.createElement('tr') ; tr.appendChild(buttoncell(blackbtn(uri,'scissors'),blackbtn(uri,'bin'))) ; tr.appendChild(textcell ('split the current segment at the selected point /', 'delete the currrent segment (or use the delete or backspace key)')) ; t.appendChild(tr) ; tr = document.createElement('tr') ; tr.appendChild(buttoncell(blackbtn(uri,'pen'))) ; tr.appendChild(textcell ('add a labelled coursepoint at the current position (1-10chars)', 'click on flag to edit; right-click to change symbol; '+ 'delete label to delete')) ; t.appendChild(tr) ; /* ------------------------------------------------------------------------ */
/*page*/ /* ------------------------------------------------------------------------ */ tr = document.createElement('tr') ; td = document.createElement('td') ; td.setAttribute('valign','top') ; td.appendChild(document.createTextNode('Keyboard: ')) ; tr.appendChild(td) ; td = document.createElement('td') ; appendrow(td, '\u2190/\u2192 move the current waypoint forwards or backwards;') ; appendrow(td,'\u2193 centres the map on the current waypoint;') ; appendrow(td,'[return] makes the current waypoint draggable;') ; appendrow(td,'[tab] inserts a draggable waypoint;') ; appendrow(td,'[space]=toggle cursor mode;') ; appendrow(td,'[del], [backspace]=delete segment (bin button).') ; tr.appendChild(td) ; t.appendChild(tr) ; tr = document.createElement('tr') ; td = document.createElement('td') ; td.setAttribute('valign','top') ; td.appendChild(document.createTextNode('Mouse: ')) ; tr.appendChild(td) ; td = document.createElement('td') ; appendrow(td,'when the cursor is in selection mode:') ; appendrow(td, '[shift click] extends the current segment by the cursor position.') ; tr.appendChild(td) ; t.appendChild(tr) ; d.appendChild(t) ; div.appendChild(d) ; /* ------------------------------------------------------------------------ */ d = document.createElement('div') d.setAttribute('style','font-family:helvetica') ; if(noblank) { a = genlink('http://www.masterlyinactivity.com/routemaster/?routes/'+ 'Cleeve.tcx','Example track to experiment with') ; d.appendChild(a) ; d.appendChild(document.createElement('br')) ; } a = genlink('http://www.masterlyinactivity.com/software/routemaster.html', 'Technical documentation and source code') ; if(noblank==0) a.setAttribute('target','_blank') ; d.appendChild(a) ; if(noblank==0) d.appendChild(document.createTextNode(' (opens in new tab/window)')) ; div.appendChild(d) ; return div ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------------------------------------------------- */ /* FUNCTIONS FOR COMPUTING & DISPLAYING THE ALTITUDE PROFILE */ /* -------------------------------------------------------------------------- */ function profiletype(x,y,sind) { var i,ymin,ymax ; this.x = x ; this.y = y ; this.sind = sind ; this.sum = x[x.length-1] ; for(ymin=ymax=null,i=0;i<y.length;i++) if(y[i]!=null) { if(ymax==null||y[i]>ymax) ymax = y[i] ; if(ymin==null||y[i]<ymin) ymin = y[i] ; } if(ymin>0) { if(ymax>3*ymin) ymin = 0 ; else ymin *= 1 - (ymax/ymin-1)/2 ; } this.ymin = ymin ; this.ymax = ymax ; this.yspan = Math.max(1,ymax-ymin) ; this.prodiv = this.curdiv = this.curcan = this.curhandle = null ; } // member functions profiletype.prototype.getx = function(x) { return 10 + 600 * x / this.sum ; } ; profiletype.prototype.locx = function(i) { return this.getx(this.x[i]) ; } ; profiletype.prototype.gety = function(y) { return 10 + 180*(this.ymax-y)/this.yspan ; } ; profiletype.prototype.getxy = function(i) { return [this.getx(this.x[i]),this.y[i]==null?null:this.gety(this.y[i])] ; } ; profiletype.prototype.getsel = function(x) { var lo=0,hi=this.x.length-1,m,s0 ; if(x<0) x = 0 ; else if(x>600) x = 600 ; x += 10 ; while(hi>lo+1) // binary search { m = Math.floor((lo+hi)/2) ; if(this.locx(m)>x) hi = m ; else lo = m ; } if(Math.abs(this.locx(lo)-x)<Math.abs(this.locx(hi)-x)) m = lo ; else m = hi ; for(s0=0;s0<this.sind.length-1&&this.sind[s0+1]<=m;s0++) ; return [ s0 , m-this.sind[s0] ] ; } ; /* -------------------------------------------------------------------------- */ function procoords(segments) { var n,x,y,s0,s1,sum,len,pos,oldpos,sind,i ; for(n=s0=0;s0<segments.length;s0++) n += segments[s0].data.length ; x = new Array(n) ; y = new Array(n) ; sind = new Array(segments.length+1) ; x[0] = 0 ; for(ymax=ymin=null,sum=i=s0=0;s0<segments.length;s0++) for(sind[s0]=i,len=segments[s0].data.length,s1=0;s1<len;s1++,i++) { y[i] = segments[s0].data[s1].h ; pos = segments[s0].data[s1].pos ; if(i) sum = x[i] = sum + dist(pos,oldpos) ; oldpos = pos ; } sind[segments.length] = n ; if(n==0) return null ; else return new profiletype(x,y,sind) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------------------------------------------------- */ function drawpro(pro) { var div=document.createElement('div'),c=document.createElement('canvas') ; var ctx,i,n,s0,s1,len,xinit,ox,step,xy ; div.setAttribute ('style','position:absolute;height:190px;right:0;top:0;width:620px') ; c.setAttribute('width',620) ; c.setAttribute('height',200) ; div.appendChild(c) ; ctx = c.getContext("2d") ; ctx.font = "10px Helvetica" ; ctx.lineWidth = 0 ; ctx.globalAlpha = 0.6 ; ctx.fillStyle = 'lightgray' ; ctx.rect(0,0,620,200) ; ctx.fill() ; // draw a profile of each segment for(i=n=s0=0;s0<pro.sind.length-1;s0++) { len = pro.sind[s0+1] - pro.sind[s0] ; if(s0&1) ctx.fillStyle = "#ff9999" ; else ctx.fillStyle = "#ff0000" ; for(xinit=ox=null,s1=0;s1<len;s1++,n++,ox=xy[0]) { xy = pro.getxy(n) ; if(xy[1]!=null) { if(xinit==null) { ctx.beginPath() ; ctx.moveTo(xy[0],xy[1]) ; xinit = xy[0] ; } else ctx.lineTo(xy[0],xy[1]) ; } } ctx.lineTo(xy[0],190) ; ctx.lineTo(xinit,190) ; ctx.closePath() ; ctx.fill() ; } // lines if(pro.yspan>2500) step = 1000 ; else if(pro.yspan>1250) step = 500 ; else step = 100 ; for(i=step*Math.floor(pro.ymin/step+1);i<pro.ymax;i+=step) { y = 0.5 + pro.gety(i) ; ctx.beginPath() ; ctx.lineWidth = 1 ; ctx.strokeStyle = '#555' ; ctx.moveTo(10,y) ; ctx.lineTo(610,y) ; ctx.stroke() ; ctx.strokeText(i,590,y-2) ; } pro.prodiv = div ; // cursor pro.curdiv = document.createElement('div') ; pro.curdiv.setAttribute ('style','position:absolute;height:190px;right:0;top:0;width:620px') ; pro.curcan = null ; pro.curhandle = pro.curdiv.addEventListener("click",function(e) { var pos = e.clientX - (window.innerWidth-610) ; if((pos-594)*(pos-594)+(e.clientY-16)*(e.clientY-16)<200) { unprofile() ; return ; } drawsel(0,pro.getsel(pos)) ; } ) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------------------------------------------------- */ function drawxcur(pro,sel) { if(pro==null||pro.prodiv==null) return null ; var pos = pro.getx(pro.x[sel[1]+pro.sind[sel[0]]]) , i ; var canvas=document.createElement('canvas') , ctx=canvas.getContext("2d") ; canvas.setAttribute('width',620) ; canvas.setAttribute('height',200) ; ctx.beginPath() ; ctx.lineWidth = 1 ; ctx.moveTo(pos,10) ; ctx.lineTo(pos,190) ; ctx.stroke() ; // the circle of the 'x' ctx.beginPath() ; ctx.strokeStyle = '#555' ; ctx.fillStyle = 'white' ; ctx.lineWidth = 3 ; ctx.arc(604,16,14.1,0,2*Math.PI,false) ; ctx.stroke() ; ctx.fill() ; for(i=6;i<=26;i+=20) // the two bars of the 'x' { ctx.beginPath() ; ctx.moveTo(594,i) ; ctx.lineTo(614,32-i) ; ctx.stroke() ; } if(pro.curcan!=null) pro.curdiv.removeChild(pro.curcan) ; pro.curdiv.appendChild(pro.curcan=canvas) ; } /* -------------------------------------------------------------------------- */

Archived from routemaster.html

var segments=[],selected,actions,nactions,sel,dragging,loadno,overviewing=0 ; var pending,xpending,mouseopt=0,elevator,resuri,xmlfile=null ; var routetitle,body,mapdiv,pro ; var scroller=null,longtitle=null,overview=null,imgdiv,imghandle,imginfo,imgind ; var cursorbtn,scissorsbtn,binbtn,undobtn,redobtn,penbtn,setbtn=null,dlbtn ; var defparms = {tol:10,maxsep:95,wppenalty:1000,vweight:1} ; var neutral='<span style="font-family:helvetica">' ; var active='<span style="cursor:pointer;color:#0000bd" onclick=' ; var inactive='<span style="color:silver">' ; var textbox='<div style="margin-bottom:2px;border-bottom:solid 1px silver;' + 'padding-bottom:2px;font-family:helvetica">' ; var finalbox='<div style="font-family:helvetica">' ; var parser = new DOMParser() ; var map = null , clickhandle = null ; var unsavedchanges = [] ; var infowindow = { handle: null , type: null , open: function(s,pos,type) { this.handle = new google.maps.InfoWindow({content:s,position:pos}) ; this.handle.open(map) ; google.maps.event.addListener(this.handle,'closeclick',function() { if(infowindow.type=='highlight') { redraw(selected[0]) ; if(scroller!=null) { clearInterval(scroller) ; scroller = null ; } } else if(infowindow.type=='phinfo') walkto(selected[0],selected[1]) ; infowindow.handle = infowindow.type = null ; } ) ; this.type = type ; } , close: function() { if(this.handle==null) return null ; var response = this.type ; this.handle.close() ; if(response=='highlight') { redraw(selected[0]) ; if(scroller!=null) { clearInterval(scroller) ; scroller = null ; } } else if(response=='phinfo') walkto(selected[0],selected[1]) ; this.handle = this.type = null ; return response ; } } ; /* --------------- construct a segment from an xml document ----------------- */ function genseg(a,b) { this.data = a ; this.props = b ; this.route = this.routehandler = this.dots = this.dothandler = null ; this.colour = "red" ; } /* -------------------------------------------------------------------------- */ /* CONSTRUCTORS */ /* -------------------------------------------------------------------------- */ function dotpath(a,b) { this.path = [a,b] ; this.cursor = 'default' ; this.geodesic = true ; this.strokeOpacity = 0 ; this.icons = [ { icon: { path: 'M 0 0 L 1 0',strokeOpacity:1,scale:1 } , offset: '1px' , repeat: '4px' } ] ; this.zIndex = 0 ; } /* -------------------------------------------------------------------------- */ function linepath(s0,start,end,colour,width) { var i,len=(start<0?segments[s0].data.length:end-start) ; if(width==undefined) width = 2 ; this.path = new Array(len) ; if(start<0) for(i=0;i<len;i++) this.path[i] = segments[s0].data[i].pos ; else for(i=0;i<len;i++) this.path[i] = segments[s0].data[start+i].pos ; this.clickable = 'false' ; this.cursor = 'default' ; this.geodesic = true ; this.strokeColor = colour ; this.strokeOpacity = 1.0 ; this.strokeWeight = width ; if(width==2) this.zIndex = 0 ; else this.zIndex = 1 ; } /* -------------------------------------------------------------------------- */ function listinfo() { this.list = [] ; this.sizes = [] ; this.uri = null ; this.thumbind = this.scale = this.status = this.type = this.pixpage = null ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------------------------------------------------- */ /* UTILITY FUNCTIONS */ /* -------------------------------------------------------------------------- */ function interp(x,y,lamda) { return google.maps.geometry.spherical.interpolate(x,y,lamda) ; } function bearing(x,y) { return google.maps.geometry.spherical.computeHeading(x,y) ; } /* --------------------------- button handlers ----------------------------- */ function greyout(btn) { if(overviewing||btn.active==0) return 0 ; btn.btn.setAttribute('src',btn.greyimg) ; btn.ui.removeEventListener('click',btn.handler) ; if(btn==penbtn) btn.ui.removeEventListener('contextmenu',photoprompt) ; btn.ui.style.cursor = 'default' ; btn.active = 0 ; return 1 ; } function blackout(btn) { if(overviewing||btn.active) return ; btn.btn.setAttribute('src',btn.blackimg) ; btn.ui.addEventListener('click',btn.handler) ; if(btn==penbtn) btn.ui.addEventListener('contextmenu',photoprompt) ; btn.ui.style.cursor = 'pointer' ; btn.active = 1 ; } /* ------------------------ enter/exit full screen -------------------------- */ // most of the code is available from pixlib function enterFullscreen() { infowindow.close() ; enterfullscreen() ; } function exitFullscreen() { infowindow.close() ; if(document.exitFullscreen) document.exitFullscreen() ; else if(document.mozCancelFullScreen) document.mozCancelFullScreen() ; else if(document.webkitExitFullscreen) document.webkitExitFullscreen() ; } /* -------------------------------------------------------------------------- */ function findimg(id) { var i ; for(i=0;i<imginfo.list.length;i++) if(imginfo.list[i].name!=undefined&&imginfo.list[i].name==id) return i ; return -1 ; } /* ------------------- message warning of unsaved changes ------------------- */ function unsavedmsg(ok) { var msg , len = unsavedchanges.length , i ; if(len==0) return null ; msg = 'You have ' + len + ' unsaved change' + (len==1?"":'s') ; if(len<=3) for(i=0;i<len;i++) msg += (i?',':' (') + unsavedchanges[i] + (i==len-1?')':'') ; msg += '\nIf you hit ' + (ok?'[OK]':'[Leave page]') ; return msg + (len==1?' this change':' these changes') + ' will be lost.' ; } /* -------------------------------------------------------------------------- */
/*page*/ /* --------------- selpoint: choose the clicked waypoint ------------------- */ function selpoint(event) { var i,j,closest,d,mindist,s0,s1 ; if(dragging) return ; var flag = (infowindow.close()=='wpinfo') && (event.shiftKey==0) ; if(overviewing==0&&event.shiftKey) { s0 = selected[0] ; s1 = segments[s0].data.length ; insert(s0,s1,1) ; segments[s0].data[s1].setpos(event.latLng) ; lookupalt(s0,s1) ; redrawconnect(s0,s1) ; done(['move',s0,s1,event.latLng,event.latLng,1]) ; } else for(s1=s0=-1,i=0;i<segments.length;i++) for(j=0;j<segments[i].data.length;j++) { d = dist(segments[i].data[j].pos,event.latLng) ; if(s0<0||d<mindist) { s0 = i ; s1 = j ; mindist = d ; } } walkto(s0,s1,flag) ; } /* -------------------------- track highlighter ---------------------------- */ function highlight() { var s0=selected[0],scroll,thind=null ; infowindow.close() ; undraw(s0) ; draw(s0,4) ; if(imginfo.uri!=null) thind = thumbind(imginfo.sizes) ; scroll = highdiv(segments[s0].props,imginfo.list,imginfo.sizes, thind,segments[s0].props.photo) ; scroller = scroll.scroller ; infowindow.open(scroll.div,northernmost(segments[s0].data),'highlight') ; } /* ------------------------------- getbtnpos -------------------------------- */ function getbtnpos(btnno) { var bounds=map.getBounds(),sw,ne,lat,lon,lam ; sw = bounds.getSouthWest() ; ne = bounds.getNorthEast() ; lam = 52.0 / window.innerHeight ; lat = lam*ne.lat() + (1-lam)*sw.lat() ; lam = 0.5 + (btnno*32-112.0)/window.innerWidth ; lon = lam*ne.lng() + (1-lam)*sw.lng() ; return new google.maps.LatLng(lat,lon) ; } /* ----- unambig: does the selected waypoint determine a unique segment? ---- */ function unambig() // does the selected waypoint determine a unique segment? { var s0=selected[0],s1=selected[1] ; if(segments.length==1) return 1 ; if( ( s0==segments.length-1 || s1!=segments[s0].data.length-1 || ! segments[s0].data[s1].pos.equals(segments[s0+1].data[0].pos) ) && ( s0==0 || s1!=0 || ! segments[s0].data[s1].pos.equals (segments[s0-1].data[segments[s0-1].data.length-1].pos) ) ) return 1 ; else return 0 ; } /* -------------------------------------------------------------------------- */
/*page*/ /* --------------------------------- walkto --------------------------------- */ // draw a selection point (and possibly an info box) at [s0,s1], bringing up // a wpinfo window if flag = 0 or a selinfo window if flag = 1 function walkto(s0,s1,flag) { var s='',i,ind,excuse,imgname=null,phind=-1,list=imginfo.list ; var datum = segments[s0].data[s1] , pos = datum.pos ; if(flag==undefined) flag = 0 ; selected = [ s0,s1 ] ; if(overviewing) return highlight() ; map.panToBounds(new google.maps.LatLngBounds(pos,pos)) ; drawsel(0,[s0,s1]) ; if(flag||(datum.type==null&&datum.photo.length==0)) { if(flag==1) wpinfo() ; else if(flag==2) seginfo() ; else if(flag==3) highlight() ; return ; } if(datum.type!=null) { if(datum.photo.length>0) s = textbox ; else s = finalbox ; if(datum.type!='Generic') s += datum.type + ': ' ; s += datum.marker.title + ' [' + active + '"labelprompt()">Edit</span>]' ; s += '</div>' ; } for(ind=0;ind<datum.photo.length;ind++) { s += textbox ; if(imginfo.status=='ready'&&(phind=findimg(datum.photo[ind]))>=0) s += '<img src="' + jpg(list[phind],imginfo.sizes,imginfo.thumbind) + '" width=' + list[phind].thumbshape[0] + ' height=' + list[phind].thumbshape[1] + '><br>' + '<b>' + list[phind].name + '</b>: ' ; else { if(imginfo.status=='null') excuse = 'no list provided' ; else if(imginfo.status=='ready') { imgname = imginfo.uri ; i = imgname.lastIndexOf('/') ; if(i>=0) imgname = imgname.substring(i+1) ; excuse = 'not present in ' + imgname ; } else if(imginfo.status=='waiting') excuse = imgname + ' is not available' ; else excuse = 'imginfo.status = ' + imginfo.status ; s += 'Photo: ' + datum.photo[ind] + ' (' + excuse + ') ' ; } s += '['+active+ '"photoedit('+ind+')">Edit</span>'+']' ; if(phind>=0) s += ' : ['+active+ '"phinfo('+phind+')">Info</span>'+']' + ' : ['+active+ '"display('+ind+')">Enlarge</span>'+']' ; s += '</div>' ; } if(datum.photo.length>0) s += finalbox + '[' + active + '"photoprompt' + '(null)">Add photo</span>' + ']</div>' ; infowindow.open(s,pos,'walking') ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------- keystroke handler ---------------------------- */ function keystroke(e) { var s0=selected[0],s1=selected[1],slast,flag ; if(e.keyCode==40&&overviewing==0) { map.panTo(segments[s0].data[s1].pos) ; return ; } flag = infowindow.close() ; if(flag=='highlight') flag = 3 ; else if(flag=='seginfo') flag = 2 ; else if(flag=='wpinfo') flag = 1 ; else flag = 0 ; if(e.keyCode==32) { selclick() ; return ; } // space if(overviewing) { if(e.keyCode==70) enterfullscreen() ; if(flag!=3||(e.keyCode!=39&&e.keyCode!=37&&e.keyCode!=8&&e.keyCode!=46)) return ; } if(e.keyCode==13) // return { if(dragging) undraggit() ; else if(s0>=0) draggit(0) ; return ; } if(dragging) return ; if(e.keyCode==8||e.keyCode==46) // delete/backspace { e.preventDefault() ; if((flag==3&&segments.length>1)||binbtn.active) discard() ; return ; } if(e.keyCode==9) { e.preventDefault() ; inswp(1) ; return ; } // tab if(e.keyCode==39) // forwards { e.preventDefault() ; if(flag>=2) { s1 = 0 ; s0 += 1 ; if(s0==segments.length) s0 = 0 ; } else if(s1<segments[s0].data.length-1) s1 += 1 ; else { s0 += 1 ; if(s0==segments.length) s0 = 0 ; s1 = 0 ; } } else if(e.keyCode==37) // backwards { e.preventDefault() ; if(flag>=2) { s1 = 0 ; s0 -= 1 ; if(s0<0) s0 = segments.length-1 ; } else if(s1>0) s1 -= 1 ; else { s0 -= 1 ; if(s0<0) s0 = segments.length-1 ; s1 = segments[s0].data.length-1 ; } } else return ; walkto(s0,s1,flag) ; } /* --------------------- undraw & redraw segments -------------------------- */ function undraw(i) { segments[i].route.setMap(null) ; if(segments[i].clickhandler!=null) { google.maps.event.removeListener(segments[i].clickhandler) ; segments[i].clickhandler = null ; } } function redraw(i) { undraw(i) ; draw(i) ; } function recolour(i) { if(overviewing) return ; else if(i&1) segments[i].route.setOptions({strokeColor:"#ff9999"}) ; else segments[i].route.setOptions({strokeColor:"#ff0000"}) ; } function obliterate(s0) // undraw route and all labels { var i ; for(i=0;i<segments[s0].data.length;i++) segments[s0].data[i].setmap(null) ; undraw(s0) ; disconnect(s0-1) ; disconnect(s0) ; } /* ----------------------------- draw segments ------------------------------ */ function draw(i,width) { var colour,poly ; if(overviewing) colour = segments[i].colour ; else if(i&1) colour = "#ff9999" ; else colour = "#ff0000" ; if(width==undefined) poly = new linepath(i,-1,0,colour) ; else poly = new linepath(i,-1,0,colour,width) ; segments[i].route = new google.maps.Polyline(poly) ; segments[i].route.setMap(map) ; if(segments[i].clickhandler==null) segments[i].clickhandler = google.maps.event.addListener(segments[i].route,"click",selpoint) ; } /* ----------------------- connect and disconnect segments ------------------ */ function disconnect(i) { if(overviewing||i<0||i>=segments.length-1||segments[i].dots==null) return ; segments[i].dots.setMap(null) ; if(segments[i].dothandler!=null) { google.maps.event.removeListener(segments[i].dothandler) ; segments[i].dothandler = null ; } } function reconnect(i) { disconnect(i) ; connect(i) ; } function connect(i) { if(overviewing||i<0||i>=segments.length-1) return ; var opos = segments[i].data[segments[i].data.length-1].pos ; var npos = segments[i+1].data[0].pos ; if(opos.equals(npos)) return ; segments[i].dots = new google.maps.Polyline(new dotpath(opos,npos)) ; segments[i].dots.setMap(map) ; segments[i].dothandler = google.maps.event.addListener(segments[i].dots,"click",selpoint) ; } function redrawconnect(s0,s1) { redraw(s0) ; if(s1==0) reconnect(s0-1) ; if(s1=segments[s0].data.length-1) reconnect(s0) ; } /* ---------------------- draw the selection point -------------------------- */ // note: there's no point in allowing clicking on a marker because the // event position is always the marker position rather than the click position function drawsel(opt,selection) { if(selection!=undefined) selected = selection ; var ind,clen,s0=selected[0],s1=selected[1],pos=segments[s0].data[s1].pos ; if(opt) reprofile() ; clen = segments[s0].data.length ; if(clen==1) arrow.rotation = 90 ; else { if(s1==clen-1) ind = s1-1 ; else ind = s1 ; icons.arrow.rotation = bearing(segments[s0].data[ind].pos,segments[s0].data[ind+1].pos) ; } if(sel.marker==null) sel.marker = new google.maps.Marker ({ position:pos, map:map, cursor:'default', icon:icons.arrow , zIndex:2 }) ; else // avoid unnecessary redraws { if(icons.arrow.rotation!=sel.orientation) sel.marker.setIcon(icons.arrow) ; if(!pos.equals(sel.marker.getPosition())) sel.marker.setPosition(pos) ; } sel.orientation = icons.arrow.rotation ; drawxcur(pro,selected) ; blackout(penbtn) ; if(segments.length>1&&unambig()) blackout(binbtn) ; else greyout(binbtn) ; if(s1!=0&&s1!=clen-1) blackout(scissorsbtn) ; else greyout(scissorsbtn) ; } /* ------------- selclick: respond to click of cursor button --------------- */ function selclick() { mouseopt = 1-mouseopt ; infowindow.close() ; if(mouseopt) { map.setOptions({draggable:false, draggableCursor:'default'}) ; if(overviewing==0) cursorbtn.btn.setAttribute('src','hand.png') ; clickhandle = google.maps.event.addListener(map,"click",selpoint) ; } else { map.setOptions({draggable:true, draggableCursor:''}) ; if(overviewing==0) cursorbtn.btn.setAttribute('src','arrow.png') ; google.maps.event.removeListener(clickhandle) ; } } /* -------------------------------------------------------------------------- */ function genhead(uri,key) { if(uri==undefined||uri==null) resuri = 'http://www.masterlyinactivity.com/routemaster/resources/' ; else { resuri = uri + '/' ; document.write('<script src="http://maps.google.com/maps/api/js?' + ((key==null||key==undefined)?'':('key='+key+'&')) + 'libraries=geometry"></scr' + 'ipt>') ; } document.write ('<script src="' + resuri + 'dms.js"></scr' + 'ipt>' + '<script src="' + resuri + 'vector3d.js"></scr' + 'ipt>' + '<script src="' + resuri + 'latlon-ellipsoidal.js"></scr' + 'ipt>' + '<script src="' + resuri + 'utm.js"></scr' + 'ipt>' + '<script src="' + resuri + 'osgridref.js"></scr' + 'ipt>' + '<script src="' + resuri + 'FileSaver.js"></scr' + 'ipt>' + '<script src="' + resuri + 'Blob.js"></scr' + 'ipt>' + '<script src="' + resuri + 'testlib.js"></scr' + 'ipt>' + '<script src="' + resuri + 'routemasterlib.js"></scr' + 'ipt>' + '<script src="http://www.masterlyinactivity.com/pixlib.js"></scr' + 'ipt>' + '<style type="text/css">html, body {width: 100%; height: 100%}' + 'body {margin:0px}a:link{color:#66aaaa}' + 'a:visited{color:#cc3388}a:active{color:#404040}</style>' + '</style><title>Routemaster</title>' + '<link rel="shortcut icon" href=' + resuri + 'bus.gif>' ) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------------------------------------------------- */ /* FUNCTIONS TO GENERATE THE INITIAL MAP */ /* -------------------------------------------------------------------------- */ function genpage() { var thispage=document.URL,xhttp,quotind,plusind,listxhttp,div ; imginfo = new listinfo() ; imgdiv = null ; elevator = new google.maps.ElevationService ; window.onload = function() { window.addEventListener("beforeunload",function(e) { var msg = unsavedmsg(0) ; if(msg==null) return undefined ; (e || window.event).returnValue = msg ; //Gecko + IE return msg ; //Gecko + Webkit, Safari, Chrome etc. (msg is ignored by ffx) } ) ; } ; body = document.getElementsByTagName("body")[0] ; while(body.childNodes.length>0) body.removeChild(body.childNodes[body.childNodes.length-1]) ; mapdiv = document.createElement('div') ; mapdiv.setAttribute('id','map') ; mapdiv.setAttribute('style','width:100%;height:100%;position:absolute') ; body.appendChild(mapdiv) ; if((quotind=thispage.indexOf('?'))>=0) { thispage = thispage.substring(quotind+1) ; if((plusind=thispage.indexOf('+'))>0) { getlist(thispage.substring(plusind+1),'uri') ; thispage = thispage.substring(0,plusind) ; } else if(thispage.substring(thispage.length-3)=='.js') { getlist(thispage,'uri') ; mapdiv.appendChild(filedialogue(0)) ; return ; } xhttp = new XMLHttpRequest() ; xhttp.onreadystatechange = function() { if(xhttp.readyState==4) { if(xhttp.status==200) { var x = parser.parseFromString(xhttp.responseText,"application/xml") ; render(x,thispage,0,'uri') ; } else alert("Unable to read "+thispage+": error code "+xhttp.status) ; } } xhttp.open("GET",thispage,true) ; xhttp.send() ; } else { mapdiv.appendChild(filedialogue(0)) ; div = helpdiv(resuri,1) ; div.setAttribute('style','margin:4px;font-size:90%') ; mapdiv.appendChild(div) ; } } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------- getlist --------------------------------- */ function getlist(uri,imgtype) { var xhttp = new XMLHttpRequest(),list=null,sizes=null,pixpage=null,i,r ; var imagedir=null,thumbshape = [] ; imginfo.status = 'waiting' ; imginfo.type = imgtype ; xhttp.onreadystatechange = function() { if(xhttp.readyState==4) { if(xhttp.status!=200) { alert("Unable to read "+uri+": error code "+xhttp.status) ; return ; } eval(xhttp.responseText) ; imginfo.uri = uri ; imginfo.list = list ; imginfo.sizes = sizes ; imginfo.pixpage = reluri(uri,pixpage) ; imginfo.thumbind = thumbind(sizes) ; setthumbshape(list,sizes,thumbshape,reluri(uri,imagedir)) ; for(i=0;i<list.length;i++) if(list[i].retpage!=undefined) list[i].retpage = reluri(uri,list[i].retpage) ; imginfo.status = 'ready' ; } } xhttp.open("GET",uri,true) ; xhttp.send() ; } /* ----------------------------- file dialogue ------------------------------ */ function filedialogue(overwrite) { var input = document.createElement('input') ; var para = document.createElement('p') ; para.appendChild(document.createTextNode ('\u00a0\u00a0\u00a0Select TCX/GPX file: ')) ; input.setAttribute('type','file') ; input.setAttribute('accept','.tcx,.gpx') ; input.addEventListener('change',function(e) { reader = new FileReader() ; reader.onload = function(e) { var xmldoc = parser.parseFromString(reader.result,"application/xml") ; render(xmldoc,input.files[0].name,overwrite,'file') ; } reader.readAsText(input.files[0]) ; } ) ; para.appendChild(input) ; return para ; } /* -------------------------------------------------------------------------- */
/*page*/ /* --------------------- set up the map and buttons ------------------------- */ function render(xmldoc,filename,overwrite,origin) { var i,opts,centre,lat,lon,minlon,maxlon,minlat,maxlat,newseg,s0 ; var colours,segno,istcx ; infowindow.close() ; document.onkeydown = keystroke ; xmlfile = filename ; // read data i = filename.length ; if(filename.substring(i-4,i)=='.tcx') istcx = 1 ; else if(filename.substring(i-4,i)=='.gpx') istcx = 0 ; else { alert(filename+' is neither .tcx nor .gcx') ; throw '' ; } if(istcx) newseg = readtcx(xmldoc) ; else newseg = readgpx(xmldoc) ; if(newseg.segments.length==0||newseg.segments[0].length==0) { alert('no data returned') ; return ; } if(newseg.segments.length>1&&setbtn!=null) { alert('trying to add a multitrack overview... not permitted') ; return ; } if(newseg.segments.length==1) for(i=0;i<newseg.segments[0].length;i++) if(newseg.segments[0][i].h==null) { alert(newseg.segments[0][i].pos+' has no altitude... unable to proceed') ; return ; } // check and process photo list if(imginfo.type!='uri') for(i=0;i<newseg.segments.length;i++) if(newseg.props[i].list!=null) { if(imginfo.uri!=null&&newseg.props[i].list!=imginfo.uri) { alert('inconsistent photo lists: ' + imginfo.uri + ' and ' + newseg.props[i].list) ; return ; } imginfo = new listinfo() ; getlist(newseg.props[i].list,'tcx') ; } if(overwrite) { for(i=0;i<segments.length;i++) obliterate(i) ; unprofile() ; if(sel.marker!=null) sel.marker.setMap(null) ; segments = [] ; if(imginfo.type=='tcx') imginfo = new listinfo() ; } s0 = segments.length ; if(s0==0) { sel = { marker:null, orientation: null } ; pending = [] ; xpending = [] ; actions = [] ; unsavedchanges = [] ; nactions = dragging = 0 ; loadno = -1 ; pro = routetitle = null ; } if(newseg.segments.length>1) { overviewing = 1 ; colours = gencolours(newseg.segments.length) ; } if(istcx&&routetitle==null&&newseg.title!=null) settitle(newseg.title) ; // process the new segments for(i=0;i<newseg.segments.length;i++) { newseg.props[i].source = [ filename , origin ] ; if(longtitle==null) longtitle = newseg.props[i].longtitle ; if(overview==null) overview = newseg.props[i].overview ; if(routetitle==null||routetitle=='Untitled Route') if(newseg.props[i].title!=null) settitle(newseg.props[i].title) ; segments.push(new genseg(newseg.segments[i],newseg.props[i])) ; if(overviewing) segments[segments.length-1].colour = colours[i] ; actions[nactions++] = [ 'load',s0+i,newseg.segments[i].slice(),loadno,newseg.props[i] ] ; loadno = nactions-1 ; } if(routetitle==null) settitle('Untitled Route') ; if(!newseg.props[0].optim.already&&!overviewing) optimaction(segments.length-1,defparms,0) ; // find max and min lat and long - have to look at all segs, not just // newly loaded to avoid google's repeatedly adding a margin for(maxlat=null,segno=0;segno<segments.length;segno++) for(i=0;i<segments[segno].data.length;i++) { lat = segments[segno].data[i].pos.lat() ; lon = segments[segno].data[i].pos.lng() ; if(maxlat==null||lon<minlon) minlon = lon ; if(maxlat==null||lon>maxlon) maxlon = lon ; if(maxlat==null||lat<minlat) minlat = lat ; if(maxlat==null||lat>maxlat) maxlat = lat ; } if(s0==0) centre = new google.maps.LatLng((minlat+maxlat)/2,(minlon+maxlon)/2) ; if(map==null) // all this only done on first call { opts = { zoom: 22, center: centre, scaleControl: true, rotateControl: false, streetViewControl: false, mapTypeId: google.maps.MapTypeId.TERRAIN, disableDoubleClickZoom: true, styles: [ { "featureType": "poi", "stylers": [{ "visibility": "off" }] } ], mapTypeControl:true, mapTypeControlOptions: { style:google.maps.MapTypeControlStyle.HORIZONTAL_BAR }, mapTypeIds: [ google.maps.MapTypeId.ROADMAP, google.maps.MapTypeId.TERRAIN, google.maps.MapTypeId.SATELLITE ] } ; map = new google.maps.Map(mapdiv,opts) ; // set up buttons if(overviewing==0) { setbtn = genbutton('settings') ; cursorbtn = genbutton('cursor') ; cursorbtn.ui.addEventListener('click',selclick) ; scissorsbtn = genbutton('scissors') ; binbtn = genbutton('bin') ; penbtn = genbutton('pen') ; undobtn = genbutton('undo') ; redobtn = genbutton('redo') ; dlbtn = genbutton('dl') ; } selclick() ; } map.fitBounds(new google.maps.LatLngBounds (new google.maps.LatLng(minlat,minlon), new google.maps.LatLng(maxlat,maxlon))) ; for(segno=s0;segno<segments.length;segno++) for(i=0;i<segments[segno].data.length;i++) segments[segno].data[i].setmap(map) ; if(nactions>1) donesomething() ; // specifically, done loading & optimisation else actions.length = nactions ; // load with no optimisation hence no undo if(s0==0) { selected = [0,0] ; if(overviewing==0) drawsel(1) ; } else greyout(dlbtn) ; for(segno=s0;segno<segments.length;segno++) { draw(segno) ; connect(segno-1) ; } connect(segments.length-1) ; reprofile() ; } /* ------------------------------- settitle --------------------------------- */ function settitle(newtitle) { routetitle = newtitle ; var h = document.getElementsByTagName('title')[0] ; while(h.firstChild) h.removeChild(h.firstChild) ; h.appendChild(document.createTextNode(routetitle)) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ------------------------------- retitle ---------------------------------- */ function retitle() { infowindow.close() ; var response = window.prompt("Modify title: (max 15 chars)",routetitle) ; if(response==null) return ; else response = response.substring(0,15) ; if(response==routetitle) return ; settitle(response) ; actions[nactions++] = [ 'edittitle' , routetitle , response ] ; donesomething() ; } /* ----------------------------- longretitle -------------------------------- */ function longretitle() { var response ; infowindow.close() ; if(longtitle==null) response = window.prompt("Add long title:","") ; else response = window.prompt("Modify long title:",longtitle) ; if(response==null||response==longtitle) return ; longtitle = response ; actions[nactions++] = [ 'editlongtitle' , longtitle , response ] ; donesomething() ; } /* ------------------------------- genbutton -------------------------------- */ function genbutton(name) { var u,v,w,b,g,k,h,div=document.createElement('div'),act ; u = document.createElement('div') ; u.style.backgroundColor = '#ffffff' ; u.style.border = '2px solid #ffffff' ; u.style.borderRadius = '3px' ; u.style.boxShadow = '0 2px 6px rgba(0,0,0,.3)' ; if(name=='dl'||name=='settings'||name=='cursor') u.style.cursor = 'pointer' ; else u.style.cursor = 'default' ; u.style.marginBottom = '12px' ; if(name!='dl') u.style.marginRight = '4px' ; u.style.textAlign = 'center' ; div.appendChild(u) ; if(name=='scissors') { h = snip ; div.index = 3 ; } else if(name=='bin') { h = discard ; div.index = 4 ; } else if(name=='pen') { h = labelprompt ; div.index = 5 ; } else if(name=='undo') { h = undo ; div.index = 6 ; } else if(name=='redo') { h = redo ; div.index = 7 ; } else if(name=='dl') { h = dl ; div.index = 8 ; } else if(name=='settings') { h = popup ; div.index = 1 ; } else if(name=='cursor') { h = null ; div.index = 2 ; } g = greybtn(resuri,name) ; k = blackbtn(resuri,name) ; if(name=='dl'||name=='settings'||name=='cursor') b = buttonimg(k) ; else b = buttonimg(g) ; u.appendChild(b) ; map.controls[google.maps.ControlPosition.BOTTOM_CENTER].push(div) ; if(name=='dl'||name=='settings') u.addEventListener('click',h) ; if(name=='dl'||name=='settings') act = 1 ; else act = 0 ; return { ui:u , btn:b , active:act , greyimg:g , blackimg:k , handler:h } ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------------------------------------------------- */ /* OPTIMISATION */ /* -------------------------------------------------------------------------- */ function optimaction(segno,parms,force) { var s = segments[segno], result = optimise(s.data,parms) ; var ndel = s.data.length - result.length ; if((force==0&&ndel<s.data.length/2)||ndel==0) return 0 ; actions[loadno][4].optim.ndel = ndel ; actions[nactions++] = [ 'optimise' , segno , parms ] ; segments[segno] = new genseg(result,segments[segno].props) ; actions[loadno][4].optim.parms = { tol: parms.tol , maxsep: parms.maxsep , wppenalty: parms.wppenalty , vweight: parms.vweight } ; return 1 ; } /* -------------------------------------------------------------------------- */ function optimprompt() { var msg = 'Enter 4 optimisation parms (tol, maxsep, wppenalty and vweight)' ; var parmstr = defparms.tol + ' ' + defparms.maxsep.toFixed(0) + ' ' + defparms.wppenalty.toFixed(0) + ' ' + defparms.vweight.toFixed(1) ; var parms,i ; infowindow.close() ; for(i=0;;i++) { newparms = prompt(msg,parmstr) ; if(newparms==null) return ; if(newparms=='') { parms = defparms ; break ; } newparms = newparms.split(' ') ; if(newparms.length==0) { parms = defparms ; break ; } parms = { tol: parseFloat(newparms[0]) , maxsep: parseFloat(newparms[1]) , wppenalty: parseFloat(newparms[2]) , vweight: parseFloat(newparms[3]) } ; if( isNaN(parms.tol)==0 && isNaN(parms.maxsep)==0 && isNaN(parms.wppenalty)==0 && isNaN(parms.vweight)==0 ) break ; if(i==0) msg = '*** Illegal parms ***\n' + msg ; } if(optimaction(segments.length-1,parms,1)) { donesomething() ; draw(segments.length-1) ; } routeinfo() ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------------------------------------------------- */ /* THE MAIN POPUP MENU AND THE FUNCTIONS IT GOVERNS */ /* -------------------------------------------------------------------------- */ function popup() { var opts,pos,s0=selected[0],s1=selected[1] ; infowindow.close() ; if(dragging) { opts = finalbox + "Hit [return] when you've finished dragging.</div>" ; infowindow.open(opts,getbtnpos(0),'settings') ; return ; } // route options opts = textbox + active + '"routeinfo()">Route info</span><br>' ; if(pro==null||pro.prodiv==null) opts += active + '"drawprofile()">Show altitude profile</span><br>' ; else opts += active + '"unprofile()">Hide altitude profile</span><br>' ; opts += active + '"addload(1)">Load new route</span><br>' ; if(overview!=null) opts += '<a style="cursor:pointer;color:#0000bd;text-decoration:none" '+ 'href="' + overview + '" target="_blank">' + 'View overview of ' + 'routes</a>'+neutral+' (opens in new tab/window)</span><br> ' ; opts += active + '"dl(1)">Download track as overview</span></div>' ; // segment options opts += textbox + active + '"seginfo()">Segment info</span><br>' ; if(unambig()) opts += active + '"revseg()">' ; else opts += inactive ; opts += 'Reverse segment</span><br>' ; opts += active + '"manualcal()">Calibrate segment altitudes</span><br>' ; opts += active + '"addload(0)">Load route as a new segment</span></div>' ; // waypoint options opts += textbox + active + '"wpinfo()">Waypoint info</span><br>' ; if(segments[selected[0]].data.length>1) opts += active + '"wpdel()">' ; else opts += inactive ; opts += 'Delete waypoint</span><br>' ; opts += active + '"draggit(0)">Make waypoint draggable</span><br>' ; opts += active + '"inswp(1)">Insert draggable waypoint ahead</span><br>' ; opts += active + '"inswp(-1)">Insert draggable waypoint behind</span>' ; opts += '</div>'+finalbox+active ; // tool options if(querycanfullscreen()) { if(queryfullscreen()==0) opts += '"enterFullscreen()">Enter full screen</span><br>' + active ; else opts += '"exitFullscreen()">Leave full screen</span><br>' + active ; } opts += '"help()">Help</span></div>' ; infowindow.open(opts,getbtnpos(0),'settings') ; } /* ------------------------------- calwork --------------------------------- */ function calwork(s0,y) { var i,s1 ; for(s1=0;s1<segments[s0].data.length;s1++) if(segments[s0].data[s1].h!=null) segments[s0].data[s1].h += y ; reprofile() ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ------------------------------ manualcal --------------------------------- */ function manualcal() { infowindow.close() ; var x,y,s0=selected[0] ; x = prompt('Enter offset in metres to add to altidudes:') ; if(x==null) return ; y = parseFloat(x) ; if(isNaN(y)) { alert(x+' is not a number') ; return ; } calwork(s0,y) ; done(['recal',s0,y]) ; } /* --------------------------------- help ----------------------------------- */ function help() { infowindow.close() ; infowindow.open(helpdiv(resuri),getbtnpos(0),'help') ; } /* --------------------------------- wpdel ---------------------------------- */ function wpdelwork(s0,s1) { var i,response=segments[s0].data[s1],clen=segments[s0].data.length ; response.setmap(null) ; for(i=s1;i<clen-1;i++) segments[s0].data[i] = segments[s0].data[i+1] ; segments[s0].data.length = clen-1 ; selected = [s0,s1] ; if(s1==segments[s0].data.length) selected[1] -= 1 ; redrawconnect(s0,s1) ; drawsel(1) ; return response ; } function wpdel() { var s0=selected[0],s1=selected[1],i ; infowindow.close() ; done(['wpdel',s0,s1,wpdelwork(s0,s1)]) ; } /* --------------------------------- revseg --------------------------------- */ function revsegwork(s0) { var i,s=segments[s0],j,x,len=s.data.length ; disconnect(s0-1) ; disconnect(s0) ; for(i=0;i<len/2;i++) { j = (len-1) - i ; x = s.data[i] ; s.data[i] = s.data[j] ; s.data[j] = x ; } for(i=0;i<s.data.length;i++) if(s.data[i].type=='Right') s.data[i].settype('Left') ; else if(s.data[i].type=='Left') s.data[i].settype('Right') ; if(s0==selected[0]) selected[1] = (len-1) - selected[1] ; connect(s0-1) ; connect(s0) ; drawsel(1) ; } /* -------------------------------------------------------------------------- */ function revseg() { infowindow.close() ; revsegwork(selected[0]) ; done(['revseg',selected[0]]) ; } /* -------------------------------------------------------------------------- */ function addload(overwrite) { var msg ; infowindow.close() ; if(overwrite) { msg = unsavedmsg(1) ; if(msg!=null) if(!confirm(msg)) return ; } infowindow.open(filedialogue(overwrite),getbtnpos(0),'addload') ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------------------------------------------------- */ /* DRAGGING A (POSSIBLY NEWLY INSERTED) WAYPOINT IS QUITE A LOT OF WORK */ /* -------------------------------------------------------------------------- */ function insert(s0,s1,n) { var i ; for(i=segments[s0].data.length+n-1;i>s1;i--) segments[s0].data[i] = segments[s0].data[i-n] ; for(i=0;i<n;i++) segments[s0].data[s1+i] = new datatype(null,null) ; } /* --------------------------------- inswp ---------------------------------- */ function inswp(dir) { var s0=selected[0],s1=selected[1],bounds,del,pos,data=segments[s0].data ; var len = data.length ; if(len==1) pos = data[0].pos ; if(dir>=0) s1 = selected[1] += 1 ; insert(s0,s1,1) ; if(len==1) { bounds = map.getBounds() ; del = bounds.getNorthEast().lng() - bounds.getSouthWest().lng() ; pos = new google.maps.LatLng(pos.lat(),pos.lng()+dir*del/10) ; } else if(s1==0) pos = interp(data[2].pos,data[1].pos,1.5) ; else if(s1<len) pos = interp(data[s1-1].pos,data[s1+1].pos,0.5) ; else pos = interp(data[s1-2].pos,data[s1-1].pos,1.5) ; data[s1].setpos(pos) ; draggit(1) ; } /* -------------------------------- draggit --------------------------------- */ // draggit makes the current waypoint draggable var l1,l2,startpos,seg0,seg1,seg2,colour,inserted ; function draggit(insparm) { var s0=selected[0],s1=selected[1],start,end,i,len=segments[s0].data.length ; startpos = segments[s0].data[s1].pos ; inserted = insparm ; infowindow.close() ; greyout(scissorsbtn) ; greyout(binbtn) ; greyout(penbtn) ; greyout(undobtn) ; greyout(redobtn) ; greyout(dlbtn) ; map.panToBounds(new google.maps.LatLngBounds(startpos,startpos)) ; sel.marker.setMap(null) ; sel.marker = new google.maps.Marker( { position: segments[s0].data[s1].pos, map: map, cursor: 'default', icon: icons.concircle , draggable: true , zIndex: 2 } ) ; if(s0&1) colour = "#ff9999" ; else colour = "#ff0000" ; segments[s0].route.setMap(null) ; if(segments[s0].clickhandler!=null) { google.maps.event.removeListener(segments[s0].clickhandler) ; segments[s0].clickhandler = null ; } seg0 = seg2 = null; if(s1>1) { seg0 = new google.maps.Polyline(new linepath(s0,0,s1,colour)) ; seg0.setMap(map) ; } if(s1==0) start = 0 ; else start = s1-1 ; if(s1==len-1) end = s1+1 ; else end = s1+2 ; seg1 = new google.maps.Polyline(new linepath(s0,start,end,colour)) ; seg1.setMap(map) ; if(s1<segments[s0].data.length-2) { seg2 = new google.maps.Polyline(new linepath(s0,s1+1,len,colour)) ; seg2.setMap(map) ; } l1 = google.maps.event.addListener(sel.marker,'drag',function() { segments[s0].data[s1].setpos(this.getPosition()) ; seg1.setMap(null) ; seg1 = new google.maps.Polyline(new linepath(s0,start,end,colour)) ; seg1.setMap(map) ; if(s1==0&&s0>0) { disconnect(s0-1) ; connect(s0-1) ; } if(s1==len-1&&s0<segments.length-1) { disconnect(s0) ; connect(s0) ; } } ) ; dragging = 1 ; } /* ------------------------------- undraggit -------------------------------- */ // undraggit is invoked by [return] to terminate waypoint dragging function undraggit() { var s0=selected[0],s1=selected[1],i,s1dash,pos=segments[s0].data[s1].pos ; var xpos ; google.maps.event.removeListener(l1) ; dragging = 0 ; if(seg0!=null) seg0.setMap(null) ; seg1.setMap(null) ; if(seg2!=null) seg2.setMap(null) ; segments[s0].route = new google.maps.Polyline(new linepath(s0,-1,0,colour)) ; segments[s0].route.setMap(map) ; segments[s0].data[s1].h = null ; lookupalt(s0,s1) ; sel.marker.setMap(null) ; sel.marker = null ; // force a redraw drawsel(1) ; if(inserted||dist(startpos,pos)>5) done(['move',s0,s1,startpos,pos,inserted]) ; if(segments.length==1) blackout(dlbtn) ; } /* -------------------------------------------------------------------------- */ function seginfo() { var pos = segments[selected[0]].data[selected[1]].pos ; infowindow.close() ; infowindow.open(seginfodiv(segments,selected[0]),pos,'seginfo') ; } /* -------------------------------------------------------------------------- */ function deltimes() { var s0,s1,task=[] ; for(s0=0;s0<segments.length;s0++) for(s1=0;s1<segments[s0].data.length;s1++) if(segments[s0].data[s1].t!=null) { task.push([s0,s1,segments[s0].data[s1].t]) ; segments[s0].data[s1].t = null ; } infowindow.close() ; done(['deltimes',task]) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------------------------------------------------- */ /* CODE TO GET ALTITUDES FOR NEWLY INSERTED POINTS */ /* -------------------------------------------------------------------------- */ function lookupalt(s0,s1) // set up a request for the alt of the new point { var hi,lo,segno=s0,ptno=s1,lopos,hipos,datum=segments[s0].data[s1] ; for(lo=null,s0=segno,s1=ptno-1;lo==null;s1--) { if(s1<0) { s0 -= 1 ; if(s0<0) break ; s1 = segments[s0].data.length-1 ; } if(segments[s0].data[s1].h!=null) lo = [s0,s1] ; } for(hi=null,s0=segno,s1=ptno+1;hi==null;s1++) { if(s1==segments[s0].data.length) { s0 += 1 ; if(s0==segments.length) break ; s1 = 0 ; } if(segments[s0].data[s1].h!=null) hi = [s0,s1] ; } if(lo==null&&hi==null) { alert('no points left with altitudes: unable to proceed') ; throw '' ; } if(lo!=null) lopos = segments[lo[0]].data[lo[1]].pos ; if(hi!=null) hipos = segments[hi[0]].data[hi[1]].pos ; if(lo==null||(hi!=null&&dist(lopos,datum.pos)>dist(hipos,datum.pos))) { lo = hi ; lopos = hipos ; } pending.push([datum,lopos,segments[lo[0]].data[lo[1]].h]) ; elevator.getElevationForLocations({locations:[datum.pos,lopos]},calibrate) ; } /* -------------------------------------------------------------------------- */ // pending is the list of inserted points for which google altitudes are needed // note a pitfall with the elevation service - it's hard to tell which // response corresponds to which request: the coordinates may not match // because google truncates to 0.00001 deg; hence the use of the dist function. function calibrate(results,status) { var pno,flag ; if(status===google.maps.ElevationStatus.OVER_QUERY_LIMIT) alert('No calibration data available: over Google query limit') ; else if(status!==google.maps.ElevationStatus.OK||results.length!=2) alert('Calibration error') ; // now find whether any of our elevation results allow us to fix an altitude for(pno=0;pno<pending.length;pno++) if( dist(pending[pno][0].pos,results[0].location)<5 && dist(pending[pno][1],results[1].location)<5 ) { if(pno>0) alert('Warning: Google elevation results out of sequence') ; diff = results[0].elevation - results[1].elevation ; pending[pno][0].h = pending[pno][2] + diff ; } for(i=pno=0;pno<pending.length;pno++) if(pending[pno][0].h==null) { if(pno!=i) pending[i] = pending[pno] ; i += 1 ; } pending.length = i ; } /* -------------------------------------------------------------------------- */ /* FUNCTIONS FOR HANDLING THE ALTITUDE PROFILE */ /* -------------------------------------------------------------------------- */ function drawprofile() { infowindow.close() ; if((pro=procoords(segments))==null) return ; drawpro(pro) ; body.appendChild(pro.prodiv) ; body.appendChild(pro.curdiv) ; drawxcur(pro,selected) ; } /* ------------------------------- unprofile -------------------------------- */ function unprofile() { var i,match,node ; infowindow.close() ; if(pro==null||pro.prodiv==null) return ; pro.curdiv.removeEventListener('click',pro.curhandle) ; for(match=0,i=body.childNodes.length-1;match==0&&i>=0;i--) { node = body.childNodes[i] ; match = (node==pro.prodiv) ; body.removeChild(node) ; } pro = null ; } function reprofile() { if(pro!=null&&pro.prodiv!=null) { unprofile() ; drawprofile() ; } } /* -------------------------------------------------------------------------- */ /* ROUTEINFO IS A MENU WHICH ITSELF SUPPORTS THE COMBINE FUNCTION */ /* -------------------------------------------------------------------------- */ function routeinfo() { var s0,s1,s,d,asc,des,oalt,alt,nlabels,nowpts,i,unsaved,props,spacing,npix ; var maxsep,sep,outoforder,tlast,otime,time,tdist,ttime,ntimes ; infowindow.close() ; props = actions[loadno][4] ; tlast = null ; tdist = ttime = outoforder = 0 ; maxsep = nlabels = npix = des = asc = d = nowpts = ntimes = 0 ; for(s0=0;s0<segments.length;s0++) { nowpts += segments[s0].data.length ; for(oalt=null,s1=0;s1<segments[s0].data.length;otime=time,s1++) { if((alt=segments[s0].data[s1].h)!=null) { if(oalt!=null) { if(alt>oalt) asc += alt-oalt ; else des += oalt - alt ; } oalt = alt ; } if(segments[s0].data[s1].type!=null) nlabels += 1 ; npix += segments[s0].data[s1].photo.length ; time = segments[s0].data[s1].t ; if(time!=null) { time = time.getTime() ; ntimes += 1 ; } if(tlast!=null&&time!=null&&time<tlast) outoforder = 1 ; // out of order if(time!=null) tlast = time ; if(s1) { sep = dist(segments[s0].data[s1-1].pos,segments[s0].data[s1].pos) ; d += sep ; if(sep>maxsep) maxsep = sep ; if(time!=null&&otime!=null) { tdist += sep/1000 ; ttime += (time-otime)/(3600*1000) ; } } } } s = finalbox +'<nobr>Title: <b>'+routetitle+'</b> [' ; s += active + '"retitle()">Edit</span>' + ']</nobr><br>' ; if(longtitle==null) s += '<nobr>[' + active + '"longretitle()">' + 'Add long title</span>]</nobr><br>' ; else { if(longtitle.length<50) s += '<nobr>Long title: <b>' + longtitle + '</b>' ; else s += '<div style="margin-bottom:2px;border-bottom:solid 1px silver;' + 'padding-bottom:2px">Long title: ' + longtitle ; s += ' [' + active + '"longretitle()">Edit</span>]' ; if(longtitle.length<50) s += '</nobr><br>' ; else s += '</div>' ; } if(loadno>0) { s += '<nobr>&nbsp;&nbsp;&nbsp;Last added route' ; if(props.title!=null) s += ' (' + props.title + ')' ; s += ':</nobr><br><nobr>&nbsp;&nbsp;&nbsp;' ; } else s += '<nobr>' ; s += 'Track points on input: ' + props.inputlen ; if(props.optim.already) { s += ' (previously optimised)</nobr>' ; if(nowpts!=props.inputlen) s += '<br>Now ' + nowpts + ' track points' ; } else if(props.optim.ndel==0) { if(nactions==loadno+1) s += ' [' + active + '"optimprompt()">' ; else s += ' [' + inactive ; s += 'Optimise' + neutral + ']</span></nobr>' ; } else s += ', optimised to ' + (props.inputlen-props.optim.ndel) + '</nobr>' ; if(!props.optim.already&&props.inputlen-props.optim.ndel!=nowpts) s += '<br>Now ' + nowpts + ' track points' ; s += '<br>' ; if(outoforder==0) { if(ntimes==0) s += 'No timings provided<br>' ; else { s += '<nobr>' ; if(ntimes<nowpts) s += (nowpts-ntimes) + ' points have no associated timings ' ; s += '[' + active + '"deltimes()">Discard timings</span>' + neutral + ']<nobr><br>' ; } if(tdist>0&&ttime>0) s += 'Average speed = ' + (tdist/ttime).toFixed(1) + ' km/hr<br>' ; } else s += 'Times are out of sequence (will be discarded on download)<br>' ; if(nlabels>0) s += nlabels + ' labelled course point' + (nlabels>1?'s':'') + '<br>' ; if(npix>0) s += npix + ' photo' + (npix>1?'s':'') + '<br>' ; unsaved = unsavedchanges.length ; if(unsaved>0) s += unsaved + ' unsaved change' + (unsaved>1?'s':'') + '<br>' ; if(segments.length>1) s += segments.length + ' segments [' + active + '"combine()">Combine</span>' + neutral + ']<br>' + '<i>Note that segments must be combined before saving</i><br>' ; s += 'Max waypoint separation: '+maxsep.toFixed(0)+'m<br>' ; if(maxsep>=100) s += '<i>Note that separations &gt;100m are illegal on Garmin</i><br>' + '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[</span>' + active + '"extrapts()">' + 'Interpolate extra points</span>]<br>' + neutral ; s += 'Total distance: '+(d/1000).toFixed(3)+'km<br>' ; s += 'Total ascent: '+asc.toFixed(0)+'m<br>' ; s += 'Total descent: '+des.toFixed(0)+'m</div>' ; s += '</div>' ; infowindow.open(s,getbtnpos(0),'routeinfo') ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ------------------------- interpolate extra points ----------------------- */ function extrapts(opt) { var s0,s1,sep,data,n,opos,npos,i,lambda,lox,nlox,taskno,ind ; var task = [ 'extra' , selected[0] , selected[1] ] ; infowindow.close() ; for(nlox=s0=0;s0<segments.length;s0++) for(data=segments[s0].data,s1=1;s1<data.length;s1++) if((sep=dist(opos=data[s1-1].pos,npos=data[s1].pos))>100) { n = Math.floor(sep/95) ; insert(s0,s1,n) ; for(i=0;i<n;i++) { lambda = (i+1) / (n+1) ; data[s1+i].setpos(new google.maps. LatLng(lambda*npos.lat()+(1-lambda)*opos.lat(), lambda*npos.lng()+(1-lambda)*opos.lng())) ; } if(selected[0]==s0&&s1<=selected[1]) selected[1] += n ; task.push([s0,s1,data.slice(s1-1,s1+n+1)]) ; s1 += n ; nlox += n+2 ; } if(nlox==0) return ; done(task) ; xpending.push(task) ; lox = new Array(nlox) ; for(ind=0,taskno=3;taskno<task.length;taskno++) { n = task[taskno][2].length ; for(i=0;i<n;i++) lox[ind++] = task[taskno][2][i].pos ; } elevator.getElevationForLocations( {locations:lox} , function (results,status) { // assume that the results come in sequence, ie. correspond to xpending[0] var task=xpending.shift(),taskno,d0,dn,lambda ; if(status===google.maps.ElevationStatus.OVER_QUERY_LIMIT) alert('No calibration data available: over Google query limit') ; else if(status!==google.maps.ElevationStatus.OK) alert('Calibration error') ; for(ind=0,taskno=3;taskno<task.length;taskno++,ind+=n+2) { n = task[taskno][2].length-2 ; d0 = task[taskno][2][0].h - results[ind].elevation ; dn = task[taskno][2][n+1].h - results[ind+n+1].elevation ; if( dist(task[taskno][2][0].pos,results[ind].location)>5 || dist(task[taskno][2][n+1].pos,results[ind+n+1].location)>5 ) alert('Anomaly with Google elevation results') ; for(i=0;i<n;i++) { lambda = (i+1) / (n+1) ; task[taskno][2][1+i].h = results[ind+1+i].elevation + lambda*dn + (1-lambda)*d0 ; } } } ) ; routeinfo() ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ------------------------------- combine1 --------------------------------- */ function combine1(sa,sb) { var i,ca,cb,calen,cblen,la=null,lb=null,cdup=0 ; undraw(sb) ; disconnect(sb-1) ; calen = segments[sa].data.length ; cblen = segments[sb].data.length ; cb = segments[sb].data[0].pos ; cdup = ( ca = cb.equals(segments[sa].data[calen-1].pos) ) ; if(cdup) { la = segments[sa].data[calen-1] ; lb = segments[sb].data[0] ; segments[sa].data.length = ( calen -= 1 ) ; } if(selected[0]==sb) selected = [ sa , selected[1]+calen ] ; segments[sa].data = segments[sa].data.concat(segments[sb].data) ; return [ cblen , cdup , la , lb ] ; } function combinework() { var task,s0 ; for(task=['combine',segments.length],s0=1;s0<segments.length;s0++) task.push(combine1(0,s0)) ; segments.length = 1 ; return task ; } /* -------------------------------------------------------------------------- */ function combine() { infowindow.close() ; done(combinework()) ; drawsel(1) ; redraw(0) ; blackout(dlbtn) ; } function recombine() { var s0 ; for(s0=1;s0<segments.length;s0++) { undraw(s0) ; combine1(0,s0) ;} segments.length = 1 ; drawsel(1) ; redraw(0) ; blackout(dlbtn) ; } /* -------------------------------------------------------------------------- */ // combine returns [ cblen , cdup , la , lb ] ; function uncombine(task) { var i,j,llen,flag,subtask ; for(flag=0,s0=task[1]-1,i=task.length-1;i>=2;i--,s0--) { subtask = task[i] ; cblen = subtask[0] ; cdup = subtask[1] ; llen = segments[0].data.length ; segments[s0] = { data: segments[0].data.slice(llen-cblen,llen) , route: null , clickhandler: null } ; llen = segments[0].data.length = llen+cdup-cblen ; if(cdup) { segments[0].data[llen-1] = subtask[2] ; segments[s0].data[0] = subtask[3] ; } if(flag==0&&selected[1]>=llen) { selected = [ s0 , selected[1]-llen ] ; flag = 1 ; } } drawsel(1) ; undraw(0) ; for(i=0;i<segments.length;i++) { draw(i) ; connect(i) ; } greyout(dlbtn) ; } /* -------------------------------------------------------------------------- */ /* WPINFO IS A MENU GIVING ACCESS TO THE SETALT FUNCTION */ /* -------------------------------------------------------------------------- */ function wpinfo() { infowindow.close() ; var s0=selected[0],s1=selected[1],alt,nalt,s,x,lat,lng,grad,gradstr,time ; var datum = segments[s0].data[s1] , pos = datum.pos ; lat = pos.lat() ; lng = pos.lng() ; s = finalbox ; if(lat>=0) s += lat.toFixed(5) + '\u00b0 N, ' ; else { lat = -lat ; s += lat.toFixed(5) + '\u00b0 S, ' ; } if(lng>=0) s += lng.toFixed(5) + '\u00b0 E<br>' ; else { lng = -lng ; s += lng.toFixed(5) + '\u00b0 W<br>' ; } x = new LatLon(lat,lng) ; if(lat>49.9&&lat<62&&lng>-12&&lng<2.5&&lat-1.5*lng<75) s += 'OS grid ref: ' + OsGridRef.latLonToOsGrid(x) ; else s += 'UTM coords = ' + x.toUtm() ; s += '<br>' ; alt = segments[s0].data[s1].h ; if(alt!=null) s += 'Altitude: ' + alt.toFixed(0) + 'm ' + active + '"setalt(1)">[Edit]' ; else s += active + '"setalt(0)">Set altitude' ; s += '</span><br>' ; time = segments[s0].data[s1].t ; if(time!=null&&time.getFullYear()>1980) s += 'Date: ' + time.toDateString() + '<br>' + 'Time: ' + time.toTimeString() + '<br>' ; if(datum.type!=null) s += datum.type + ': ' + datum.marker.title + ' [' + active + '"labelprompt()">Edit</span>]<br>' ; if(alt==null||s1==segments[s0].data.length-1) nalt = null ; else { nalt = segments[s0].data[s1+1].h ; if(nalt!=null) x = dist(pos,segments[s0].data[s1+1].pos) ; } if(nalt!=null&&Math.abs(nalt-alt)<x) { grad = 100*Math.asin((nalt-alt)/x) ; gradstr = Math.abs(grad).toFixed(0) ; if(gradstr=='0') s += 'Flat<br>' ; else if(grad>0) s += 'Climb '+gradstr+'%<br>' ; else s += 'Descend: '+gradstr+'%<br>' ; } s += '<span style="font-size:80%">' ; if(segments.length>1) s += 'Segment '+s0+' p' ; else s += 'P' ; s += 'oint ' + s1 + '</span></div>' ; infowindow.open(s,pos,'wpinfo') ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------- setalt ---------------------------------- */ function setalt(edit) { infowindow.close() ; var s0=selected[0],s1=selected[1],x,y=null,oldalt ; oldalt = segments[s0].data[s1].h.toFixed(0) ; if(edit) x = prompt('Enter altitude (m):',oldalt) ; else x = prompt('Enter altitude (m):') ; if(x==null) return ; if(x!=''&&isNaN(y=parseFloat(x))) { alert(x+' is not a number') ; return ; } if(y==null&&oldalt==null) return ; if(y!=null&&Math.abs(y-oldalt)<0.1) return ; segments[s0].data[s1].h = y ; done(['setalt',s0,s1,oldalt,y]) ; reprofile() ; wpinfo() ; } /* -------------------------------------------------------------------------- */ /* THE LABELS ARE ACCESSED FROM THE PEN BUTTON OR BY CLICKING ON THE MAP */ /* -------------------------------------------------------------------------- */ function labelprompt() { var oldcaption='',oldtype,type='Generic',s0=selected[0],s1=selected[1] ; var str , flag = (infowindow.close()=='wpinfo') ; var datum = segments[s0].data[s1] ; oldtype = datum.type ; if(oldtype!=null) oldcaption = datum.marker.title ; if(oldcaption==null) oldcaption = '' ; if(oldtype!=null) type = oldtype ; if(oldtype==null) str = 'Enter' ; else str = 'Modify or delete' ; var caption = window.prompt(str+' label:',oldcaption) ; if(caption==null) { if(flag) wpinfo() ; else walkto(s0,s1) ; return ; } else if(caption=='') type = null ; else caption = caption.substring(0,10) ; if(caption==oldcaption) { if(flag) wpinfo() ; else walkto(s0,s1) ; return ; } segments[s0].data[s1].setlabel(type,caption) ; done(['editlabel',s0,s1,oldcaption,caption,oldtype,type]) ; if(flag) wpinfo() ; else walkto(s0,s1) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ------------------------------ labelcycle -------------------------------- */ function labelcycle() { var s0,s1,datum,types,caption,flag=(infowindow.close()=='wpinfo') ; ; for(s0=0;s0<segments.length;s0++) for(s1=0;s1<segments[s0].data.length;s1++) { datum = segments[s0].data[s1] ; if(datum.marker!=this) continue ; types = datum.labelcycle() ; caption = datum.marker.title ; selected = [s0,s1] ; done(['editlabel',s0,s1,caption,caption,types[0],types[1]]) ; if(flag) wpinfo() ; return ; } } function photoprompt(e) { var s0=selected[0],s1=selected[1] ; if(e!=null) e.preventDefault() ; var flag = (infowindow.close()=='wpinfo') ; var datum = segments[s0].data[s1] ; var photo = window.prompt('Enter photo name:','') ; if(photo!=null&&photo!='') { done(['editphoto',s0,s1,datum.photo.length,null,photo]) ; datum.addphoto(photo) ; } if(flag) wpinfo() ; else walkto(s0,s1) ; } function photoedit(ind) { var s0=selected[0],s1=selected[1],i ; var flag = (infowindow.close()=='wpinfo') ; var datum = segments[s0].data[s1] ; var photo = window.prompt('New photo name:',datum.photo[ind]) ; if(photo!=null&&photo!='') for(i=0;i<datum.photo.length;i++) if(datum.photo[i]==photo) { photo = null ; break ; } if(photo!=null) { if(photo=='') photo = null ; done(['editphoto',s0,s1,ind,datum.photo[ind],photo]) ; datum.setphoto(ind,photo) ; } if(flag) wpinfo() ; else walkto(s0,s1) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ----------------------------- display photo ------------------------------ */ var lmove,rmove ; function advance(s0,s1,ind) { for(ind++;;ind++) { if(ind>=segments[s0].data[s1].photo.length) { ind = 0 ; s1 += 1 ; } if(s1==segments[s0].data.length) { s0 += 1 ; if(s0==segments.length) return null ; s1 = 0 ; } if(ind<segments[s0].data[s1].photo.length) return [s0,s1,ind] ; } } function retreat(s0,s1,ind) { for(ind--;;ind--) { if(ind<0) { s1 -= 1 ; ind = null ; } if(s1<0) { if(s0==0) return null ; else s0 -= 1 ; s1 = segments[s0].data.length-1 ; } if(ind==null) ind = segments[s0].data[s1].photo.length - 1 ; if(ind>=0) return [s0,s1,ind] ; } } function prev() { dodisplay(lmove[0],lmove[1],lmove[2],-1) ; } function backtogps() { document.onkeydown = keystroke ; window.removeEventListener('resize',resize) ; body.removeChild(imgdiv) ; } function next() { dodisplay(rmove[0],rmove[1],rmove[2],1) ; } function display(ind) { var phind ; document.onkeydown = imgwalk ; window.addEventListener('resize',resize) ; infowindow.close() ; imgdiv = document.createElement('div') ; imgdiv.setAttribute('style','position:fixed;width:100%;height:100%;'+ 'left:0;top:0;background:black') ; dodisplay(selected[0],selected[1],ind,1) ; body.appendChild(imgdiv) ; } function dodisplay(s0,s1,ind,dir) { var phind=findimg(segments[s0].data[s1].photo[ind]) , pre=null ; selected[0] = s0 ; selected[1] = s1 ; lmove = retreat(s0,s1,ind) ; rmove = advance(s0,s1,ind) ; if(dir<0&&lmove!=null) pre = findimg(segments[lmove[0]].data[lmove[1]].photo[lmove[2]]) ; else if(dir>=0&&rmove!=null) pre = findimg(segments[rmove[0]].data[rmove[1]].photo[rmove[2]]) ; if(pre!=null) pre = imginfo.list[pre] ; gendisplay(imgdiv,imginfo.list[findimg(segments[s0].data[s1].photo[ind])], imginfo.sizes,lmove==null?null:'javascript:prev()', 'javascript:backtogps()',rmove==null?null:'javascript:next()', 'GPS track',pre) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ------------------------------ image walk -------------------------------- */ function imgwalk(e) { e.preventDefault() ; if(e.keyCode==39) { if(rmove!=null) next() ; return ; } else if(e.keyCode==37) { if(lmove!=null) prev() ; return ; } else if(e.keyCode==40) reduce() ; else if(e.keyCode==38) enlarge() ; else if(e.keyCode==70) enterfullscreen() ; else backtogps() ; } /* --------------------------------- photo info ----------------------------- */ function phinfo(i) { infowindow.close() ; var s0=selected[0],s1=selected[1],s,shape,ind,hind,r,k ; var list=imginfo.list,sizes=imginfo.sizes ; s = finalbox + 'Name: ' + list[i].name + '<br>Title: ' + list[i].title ; for(hind=null,ind=0;ind<i;ind++) if(list[ind].name==null) hind = ind ; if(hind!=null) s += "<br>Under \u201c" + list[hind].title + "\u201d" ; // how many sizes? for(r=ind=0;ind<sizes.length;ind++) if(sizes[ind].type==undefined) r += 1 ; s += '<br>Available in ' + r + ' size' + (r>1?'s: ':': ') ; // print the sizes for(k=ind=0;ind<sizes.length;ind++) if(sizes[ind].type==undefined) { if(k>0) { if(k==r-1) s += ' and ' ; else s += ', ' ; } shape = imgsize(list[i],sizes,ind) ; s += shape[0] + 'x' + shape[1] ; k += 1 ; } shape = list[i].thumbshape ; s += '<br>Thumb: ' + shape[0] + 'x' + shape[1] ; for(ind=0;ind<sizes.length;ind++) if(sizes[ind].type=='raw') { shape = imgsize(list[i],sizes,ind) ; s += '<br>Raw: ' + shape[0] + 'x' + shape[1] ; } if(imginfo.pixpage!=null) s += '<br><a style="cursor:pointer;color:#0000bd;text-decoration:none" '+ 'href="'+imginfo.pixpage+'" target="_blank">'+ 'Full photo set</a>'+neutral+' (opens in new tab/window)</span>' ; if(list[i].retid!=null) { for(hind=null,ind=0;ind<=i;ind++) if(list[ind].retpage!=undefined&&list[ind].retpage!=null) hind = list[ind].retpage + '.html#' + list[i].retid ; if(hind!=null) s += '<br><a style="cursor:pointer;color:#0000bd;'+ 'text-decoration:none" href="' + hind + '" target="_blank">'+ 'Route notes</a>'+neutral+' (opens in new tab/window)</span>' ; } infowindow.open(s,segments[s0].data[s1].pos,'phinfo') ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ------------------------- snip: apply scissors -------------------------- */ function snipwork(s0,s1) { var i,k,newlen ; undraw(s0) ; segments.length += 1 ; for(i=segments.length-1;i>s0+1;i--) segments[i] = segments[i-1] ; newlen = segments[s0].data.length - s1 ; segments[s0+1] = new genseg(segments[s0].data.slice(s1),segments[s0].props) ; segments[s0+1].dots = segments[s0].dots ; segments[s0+1].dothandler = segments[s0].dothandler ; segments[s0].dots = segments[s0].dothandler = null ; segments[s0].data.length = s1 + 1 ; segments[s0].data[s1] = new datatype(segments[s0].data[s1].pos,segments[s0].data[s1].h) ; draw(s0) ; draw(s0+1) ; for(i=s0+2;i<segments.length;i++) recolour(i) ; drawsel(1,[s0+1,0]) ; greyout(dlbtn) ; } function snip() { var i,s0=selected[0],s1=selected[1] ; infowindow.close() ; done(['snip',s0,s1]) ; snipwork(s0,s1) ; } /* ------------------------ discard: bin a segment ------------------------- */ function binwork(s0) { var i ; obliterate(s0) ; for(i=s0;i<segments.length-1;i++) segments[i] = segments[i+1] ; segments.length -= 1 ; for(i=s0;i<segments.length;i++) recolour(i) ; connect(s0-1) ; selected[1] = 0 ; if(selected[0]==segments.length) selected[0] = 0 ; drawsel(1) ; if(segments.length==1) blackout(dlbtn) ; } function discard() { var i,s0=selected[0] ; infowindow.close() ; done(['bin',s0,segments[s0]]) ; binwork(s0) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------- actionname ------------------------------ */ function actionname(x) { if(x[0]=='bin') return 'delete segment' ; if(x[0]=='snip') return 'split segment' ; if(x[0]=='editlabel') { if(x[4]=='') return 'delete label' ; else if(x[3]=='') return 'label waypoint' ; else return 'edit label' ; } if(x[0]=='edittitle') return 'edit title' ; if(x[0]=='editlongtitle') return 'edit long title' ; if(x[0]=='wpdel') return 'delete waypoint' ; if(x[0]=='move') { if(x[5]) return 'insert waypoint' ; else return 'drag waypoint' ; } if(x[0]=='recal') return 'recalibrate altitudes' ; if(x[0]=='setalt') return 'set waypoint altitude' ; if(x[0]=='resign') return 'change label symbol' ; if(x[0]=='combine') return 'combine '+x[1]+' segments' ; if(x[0]=='revseg') return 'reverse segment' ; if(x[0]=='interpolate') return 'interpolate missing altitudes' ; if(x[0]=='optimise') return 'optimisation' ; if(x[0]=='deltimes') return 'delete times' ; if(x[0]=='editphoto') { if(x[5]==null) return 'delete photo' ; else if(x[4]==null) return 'add photo' ; else return 'change photo' ; } if(x[0]=='extra') return 'interpolate extra points' ; } function actiontype(x) { if( x=='snip'||x=='combine'||x=='interpolate' || x=='optimise'||x=='load' ) return 0 ; else return 1 ; } /* -------------------------------------------------------------------------- */ function done(something) { if( nactions>0 && unsavedchanges.length>0 && something[0]=='editlabel' && actions[nactions-1][0]==something[0] && actions[nactions-1][1]==something[1] // don't merge change with delete && actions[nactions-1][2]==something[2] && something[6]!=null ) { actions[nactions-1][4] = something[4] ; // caption actions[nactions-1][6] = something[6] ; // type } else { actions[nactions++] = something ; donesomething() ; } } function donesomething() { actions.length = nactions ; blackout(undobtn) ; greyout(redobtn) ; if(actiontype(actions[nactions-1][0])!=0) { if(unsavedchanges.length>=3) unsavedchanges.push(null) ; else unsavedchanges.push(actionname(actions[nactions-1])) ; } } /* -------------------------------------------------------------------------- */
/*page*/ /* --------------------------------- undo ---------------------------------- */ function undo() { infowindow.close() ; var opts = active + '"confirmedundo()">Undo ' + actionname(actions[nactions-1])+'</span>' ; infowindow.open(opts,getbtnpos(5),'undo') ; } function confirmedundo() { var i,ano=nactions-1,action=actions[ano][0],s0=actions[ano][1],s1,caption ; var oldcaption,task,ind ; infowindow.close() ; if(action!='revseg'&&action!='interpolate'&&action!='deltimes') s1 = actions[ano][2] ; if(action=='bin') { disconnect(s0-1) ; for(i=segments.length;i>s0;i--) { segments[i] = segments[i-1] ; recolour(i) ; } segments[s0] = s1 ; for(s1=0;s1<segments[s0].data.length;s1++) segments[s0].data[s1].setmap(map) ; draw(s0) ; connect(s0-1) ; connect(s0) ; if(selected[0]>=s0) selected[0] += 1 ; drawsel(1) ; greyout(dlbtn) ; } else if(action=='snip') // undo snip { selected = [ s0 , segments[s0].data.length-1 ] ; combine1(s0,s0+1) ; for(i=s0+1;i<segments.length-1;i++) { segments[i] = segments[i+1] ; recolour(i) ; } segments.length -= 1 ; if(segments.length==1) blackout(dlbtn) ; draw(s0) ; drawsel(1) ; } else if(action=='editlabel') // undo create/edit/delete label segments[s0].data[s1].setlabel(actions[ano][5],actions[ano][3]) ; else if(action=='edittitle') settitle(s0) ; else if(action=='editlongtitle') longtitle = s0 ; else if(action=='wpdel') // ['wpdel',s0,s1,wpdelwork(s0,s1)] { insert(s0,s1,1) ; segments[s0].data[s1] = actions[ano][3] ; segments[s0].data[s1].setmap(map) ; redrawconnect(s0,s1) ; drawsel(1,[s0,s1]) ; } else if(action=='move') { if(actions[ano][5]) wpdelwork(s0,s1) ; else move(s0,s1,actions[ano][3]) ; } else if(action=='recal') calwork(s0,-s1) ; else if(action=='setalt') segments[s0].data[s1].h = actions[ano][3] ; else if(action=='combine') uncombine(actions[ano]) ; else if(action=='revseg') revsegwork(s0) ; else if(action=='interpolate') for(i=0;i<s0.length;i++) segments[0].data[s0[i]].h = null ; else if(action=='deltimes') for(i=0;i<s0.length;i++) segments[s0[i][0]].data[s0[i][1]].t = s0[i][2] ; else if(action=='optimise') // [ 'load' , s0 , data.slice() , loadno , props ] { for(ano--;ano>=0&&actions[ano][0]!='load';ano--) ; segments[s0].data = actions[ano][2] ; actions[loadno][4].optim.ndel = 0 ; redraw(s0) ; drawsel(1,[s0,0]) ; } else if(action=='editphoto') { ind = actions[ano][3] ; if(actions[ano][5]==null) // undo delete for(i=segments[s0].data[s1].photo.length;i>ind;i--) segments[s0].data[s1].photo[i] = segments[s0].data[s1].photo[i-1] ; if(ind>=segments[s0].data[s1].photo.length) segments[s0].data[s1].addphoto(actions[ano][4]) ; else segments[s0].data[s1].setphoto(ind,actions[ano][4]) ; } else if(action=='extra') for(selected=[s0,s1],i=actions[ano].length-1;i>=3;i--) { task = actions[ano][i] segments[task[0]].data.splice(task[1],task[2].length-2) ; } nactions -= 1 ; if(nactions<=1) greyout(undobtn) ; blackout(redobtn) ; if(actiontype(actions[nactions][0])!=0&&unsavedchanges.length>0) unsavedchanges.length -= 1 ; ; if(action=='optimise'||action=='dltimes') routeinfo() ; else if(action=='editphoto'||action=='editlabel') walkto(s0,s1) ; } /* --------------------------------- move ----------------------------------- */ function move(s0,s1,pos) { segments[s0].data[s1].setpos(pos) ; redrawconnect(s0,s1) ; drawsel(1) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* --------------------------------- redo ---------------------------------- */ function redo() { infowindow.close() ; var opts = active + '"confirmedredo()">Redo ' + actionname(actions[nactions])+'</span>' ; infowindow.open(opts,getbtnpos(6),'redo') ; } function confirmedredo() { var i,action=actions[nactions][0],s0=actions[nactions][1],s1,caption,a,b,c ; var task,ind,photo ; if(action!='bin'&&action!='revseg'&&action!='interpolate'&&action!='deltimes') s1 = actions[nactions][2] ; infowindow.close() ; if(action=='bin') binwork(s0) ; else if(action=='snip') snipwork(s0,s1) ; else if(action=='editlabel') // redo create/edit/delete label segments[s0].data[s1].setlabel(actions[nactions][6],actions[nactions][4]) ; else if(action=='edittitle') settitle(s1) ; else if(action=='editlongtitle') longtitle = s1 ; else if(action=='wpdel') wpdelwork(s0,s1) ; else if(action=='move') // ['move',s0,s1,oldpos,newpos,inserted] { if(actions[nactions][5]) insert(s0,s1,1) ; move(s0,s1,actions[nactions][4]) ; } else if(action=='recal') calwork(s0,s1) ; else if(action=='setalt') segments[s0].data[s1].h = actions[nactions][4] ; else if(action=='combine') recombine(actions[nactions]) ; else if(action=='revseg') revsegwork(s0) ; else if(action=='interpolate') interpolatework(segments[0].data) ; else if(action=='deltimes') for(i=0;i<s0.length;i++) segments[s0[i][0]].data[s0[i][1]].t = null ; else if(action=='optimise') { result = optimise(segments[s0].data,actions[nactions][2]) ; actions[loadno][4].optim.ndel = segments[s0].data.length - result.length ; segments[s0].data = result ; redraw(s0) ; drawsel(1,[s0,0]) ; routeinfo() ; } else if(action=='editphoto') { ind = actions[nactions][3] ; photo = actions[nactions][5] ; if(actions[nactions][4]==null) segments[s0].data[s1].addphoto(photo) ; else segments[s0].data[s1].setphoto(ind,photo) ; } else if(action=='extra') for(selected=[s0,s1],i=3;i<actions[nactions].length;i++) { task = actions[nactions][i] ; a = segments[task[0]].data.slice(0,task[1]) ; b = task[2].slice(1,task[2].length-1) ; c = segments[task[0]].data.slice(task[1]) ; segments[task[0]].data = a.concat(b,c) ; } nactions += 1 ; if(nactions==actions.length) greyout(redobtn) ; blackout(undobtn) ; if(actiontype(actions[nactions-1][0])!=0) unsavedchanges.push(action) ; if(action=='editphoto'||action=='editlabel') walkto(s0,s1) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ---------------------------------- dl ----------------------------------- */ function dl(opt) { var props=new propstype(),str,ndel,origlen,ano,i,npix,noalt,filename ; infowindow.close() ; if(opt==undefined) opt = 0 ; // filename i = routetitle.indexOf(' ') ; if(i<=0) filename = routetitle ; else filename = routetitle.substring(0,i) ; if(filename==''||filename==null) filename = 'Untitled' ; filename += '.tcx' ; // check for photos and altitudeless points for(noalt=npix=i=0;i<segments[0].data.length;i++) { npix += segments[0].data[i].photo.length ; if(segments[0].data[i].h==null) noalt += 1 ; } // photo list if(npix>0&&imginfo.status=='ready') { if(imginfo.type=='tcx') props.list = imginfo.uri ; // vice 'uri' else { props.list = document.URL ; if((i=props.list.lastIndexOf('?'))>=0) props.list = props.list.substring(0,i) ; props.list = reluri(props.list,imginfo.uri) ; } } if(opt) // write overview and return { str = writeoverview(segments,routetitle,props.list) ; saveAs(new Blob([str],{type: "text/plain;charset=utf-8"}),filename) ; return ; } // decide what to do if some points have no altitudes if(noalt&&!confirm(noalt+' waypoints have no associated altitudes.\n'+ 'You can hit [OK] and I will interpolate altitudes (not guaranteed), or\n'+ 'you can hit [Cancel] and try again later when the altitudes may be '+ 'available.')) return null ; if(noalt) interpolate() ; // record optimisation for(ano=-1,ndel=origlen=0,i=loadno;i>=0;i=actions[i][3]) { ndel += actions[i][4].optim.ndel ; origlen += actions[i][4].optim.origlen ; if(actions[i][4].optim.parms!=null&&ano==-1) ano = i ; } if(ano>=0) props.optim.parms = actions[ano][4].optim.parms ; props.optim.origlen = origlen ; props.optim.ndel = ndel ; // title props.title = routetitle ; props.longtitle = longtitle ; str = writetcx(props,segments[0].data) ; if(str==null) return ; unsavedchanges = [] ; saveAs(new Blob([str],{type: "text/plain;charset=utf-8"}),filename) ; } /* ------------------------------ interpolate ------------------------------- */ function interpolate() { infowindow.close() ; var response = interpolatework(segments[0].data) ; if(response.length>0) done([ 'interpolate' , response ]) ; } function interpolatework(a) { var i,j,k,response,x,y,distance,sum,len=a.length ; for(response=[],i=0;i<len;i=j) { for(;i<len&&a[i].h!=null;i++) ; // advance to null for(j=i+1;j<len&&a[j].h==null;j++) response.push(j) ; // advance to non-null if(i==0) { for(y=a[j].h;i<j;i++) a[i].h = y ; continue ; } if(j==len) { for(x=a[i-1].h;i<j;i++) a[i].h = x ; continue ; } distance = new Array(1+j-i) ; for(sum=k=0;k<=j-i;k++) sum = distance[k] = sum + dist(a[i+k-1].pos,a[i+k].pos) ; for(x=a[i-1].h,y=a[j].h,k=0;k<j-i;k++) a[i+k].h = ( x*(sum-distance[i]) + y*distance[i] ) / sum ; } return response ; } /* -------------------------------------------------------------------------- */

Archived from routemaster.html

var segments=[],selected,actions,nactions,sel,dragging,loadno ; var pending,xpending,mouseopt=0,curhandle,elevator,resuri ; var routetitle,body,altdiv,curdiv,curcan,mapdiv,cmap,smap,prox,proa,pron ; var imgdiv,imghandle,imginfo,imgind ; var cursorbtn,scissorsbtn,binbtn,undobtn,redobtn,penbtn,setbtn,dlbtn ; var defparms = {tol:10,maxsep:95,wppenalty:1000,vweight:1} ; var flagsign,turnleft,straighton,turnright,shriek,arrow,concircle,camera ; var neutral='<span style="font-family:helvetica">' ; var active='<span style="cursor:pointer;color:#0000bd" onclick=' ; var inactive='<span style="color:silver">' ; var textbox='<div style="margin-bottom:2px;border-bottom:solid 1px silver;' + 'padding-bottom:2px;font-family:helvetica">' ; var finalbox='<div style="font-family:helvetica">' ; var parser = new DOMParser() ; var map = null , clickhandle = null ; var unsavedchanges = [] ; var infowindow = { handle: null , type: null , open: function(s,pos,type) { this.handle = new google.maps.InfoWindow({content:s,position:pos}) ; this.handle.open(map) ; google.maps.event.addListener(this.handle,'closeclick', function() { infowindow.handle = infowindow.type = null ; } ) ; this.type = type ; } , close: function() { if(this.handle==null) return null ; var response = this.type ; this.handle.close() ; this.handle = this.type = null ; return response ; } } ; /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------------------------------------------------- */ /* CONSTRUCTORS */ /* -------------------------------------------------------------------------- */ function dotpath(a,b) { this.path = [a,b] ; this.cursor = 'default' ; this.geodesic = true ; this.strokeOpacity = 0 ; this.icons = [ { icon: { path: 'M 0 0 L 1 0',strokeOpacity:1,scale:1 } , offset: '1px' , repeat: '4px' } ] ; this.zIndex = 0 ; } /* -------------------------------------------------------------------------- */ function linepath(s0,start,end,colour) { var i,len=(start<0?segments[s0].data.length:end-start) ; this.path = new Array(len) ; if(start<0) for(i=0;i<len;i++) this.path[i] = segments[s0].data[i].pos ; else for(i=0;i<len;i++) this.path[i] = segments[s0].data[start+i].pos ; this.clickable = 'false' ; this.cursor = 'default' ; this.geodesic = true ; this.strokeColor = colour ; this.strokeOpacity = 1.0 ; this.strokeWeight = 2 ; this.zIndex = 0 ; } /* -------------------------------------------------------------------------- */ function listinfo() { this.list = [] ; this.sizes = [] ; this.uri = null ; this.thumbind = this.scale = this.status = this.type = this.pixpage = null ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ------------------------------- data structure --------------------------- */ // I found the following logic quite hard to get right. A (non-null) label // satisfies the following constraints: // o. the marker is non-null // o. the map may be null, and if it is null the title may also be null and the // icon may be arbitrary // o. if the type is null, the map is null // o. the map is null if and only if the clickhandler is inactive // the same constraints apply (mutatis mutandis) to the photo, so it follows // that the label may have a null map and the photo non-null (and vice versa) // we therefore conclude that a label must be in one of 3 states: // o. type null, map null, handlers inactive, but marker non-null // o. type non-null, map null, handlers inactive, marker non-null // o. type non-null, map non-null, handlers active, marker non-null // the state in which type is non-null and map is null is applied to all // labels in a segment being deleted (we preserve the information in the // action list but don't want the label to be displayed) function datatype(pos,h) { this.pos = pos ; this.h = h ; this.marker = this.photomarker = this.type = this.t = null ; this.photo = [] ; this.caption = '' ; this.clickhandler = this.righthandler = this.photohandler = null ; } // member functions datatype.prototype.geticon = function() { if(this.type=='Left') return turnleft ; else if(this.type=='Straight') return straighton ; else if(this.type=='Right') return turnright ; else if(this.type=='Danger') return shriek ; else return flagsign ; } ; datatype.prototype.setlabelmap = function(m) { if(m==null||this.type==null) m = null ; else m = map ; if(m==null&&this.marker==null) return ; this.marker.setMap(m) ; if(m==null&&this.clickhandler!=null) { google.maps.event.removeListener(this.clickhandler) ; google.maps.event.removeListener(this.righthandler) ; this.clickhandler = this.righthandler = null ; } if(m!=null&&this.clickhandler==null) { this.clickhandler = this.marker.addListener('click',selpoint) ; this.righthandler = this.marker.addListener('rightclick',labelcycle) ; } } ; datatype.prototype.setphotomap = function(m) { if(m==null||this.photo.length==0) m = null ; else m = map ; if(m==null&&this.photomarker==null) return ; this.photomarker.setMap(m) ; if(m==null&&this.photohandler!=null) { google.maps.event.removeListener(this.photohandler) ; this.photohandler = null ; } if(m!=null&&this.photohandler==null) this.photohandler = this.photomarker.addListener('click',selpoint) ; } ; datatype.prototype.setlabel = function(t,c) { this.type = t ; if(t==null) { if(this.marker!=null) this.setlabelmap(null) ; return ; } if(this.marker==null) this.marker = new google.maps.Marker ({ position:this.pos,map:map,icon:this.geticon(),title:c,zIndex:1 }) ; else { this.marker.setIcon(this.geticon()) ; this.marker.setTitle(c) ; } this.setlabelmap(map) ; } ; datatype.prototype.setphoto = function(ind,p) { var i ; if(p==null) { for(i=ind;i<this.photo.length-1;i++) this.photo[i] = this.photo[i+1] ; this.photo.length -= 1 ; if(this.photo.length==0&&this.photomarker!=null) this.setphotomap(null) ; return ; } else { this.photo[ind] = p ; if(ind==0) this.photomarker.setTitle(p) ; } } ; datatype.prototype.addphoto = function(p) { this.photo.push(p) ; if(this.photomarker==null) this.photomarker = new google.maps.Marker ({ position:this.pos,map:map,icon:camera,title:p,zIndex:1 }) ; this.setphotomap(map) ; } ; datatype.prototype.setpos = function(p) { this.pos = p ; if(this.type!=null) this.marker.setPosition(p) ; if(this.photo.length>0) this.photomarker.setPosition(p) ; } ; datatype.prototype.setmap = function(m) { this.setlabelmap(m) ; this.setphotomap(m) ; } ; datatype.prototype.settype = function(t) { this.type = t ; this.marker.setIcon(this.geticon()) ; } ; /* -------------------------------------------------------------------------- */ /* UTILITY FUNCTIONS */ /* -------------------------------------------------------------------------- */ function xmlfloat(x) { return parseFloat(x.childNodes[0].textContent) ; } function dist(x,y) { return google.maps.geometry.spherical.computeDistanceBetween(x,y) ; } function interp(x,y,lamda) { return google.maps.geometry.spherical.interpolate(x,y,lamda) ; } function bearing(x,y) { return google.maps.geometry.spherical.computeHeading(x,y) ; } function angle(x,y) { return google.maps.geometry.spherical.computeHeading(x,y)*Math.PI/180 ; } function isvaliddate(d) { if(Object.prototype.toString.call(d)!=="[object Date]") return false ; else return !isNaN(d.getTime()) ; } /* --------------------------- button handlers ----------------------------- */ function greyout(btn) { if(btn.active==0) return 0 ; btn.btn.setAttribute('src',btn.greyimg) ; btn.ui.removeEventListener('click',btn.handler) ; if(btn==penbtn) btn.ui.removeEventListener('contextmenu',photoprompt) ; btn.ui.style.cursor = 'default' ; btn.active = 0 ; return 1 ; } function blackout(btn) { if(btn.active) return ; btn.btn.setAttribute('src',btn.blackimg) ; btn.ui.addEventListener('click',btn.handler) ; if(btn==penbtn) btn.ui.addEventListener('contextmenu',photoprompt) ; btn.ui.style.cursor = 'pointer' ; btn.active = 1 ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ------------------------ enter/exit full screen -------------------------- */ // most of the code is available from pixlib function enterFullscreen() { infowindow.close() ; enterfullscreen() ; } function exitFullscreen() { infowindow.close() ; if(document.exitFullscreen) document.exitFullscreen() ; else if(document.mozCancelFullScreen) document.mozCancelFullScreen() ; else if(document.webkitExitFullscreen) document.webkitExitFullscreen() ; } /* -------------------------------------------------------------------------- */ function findimg(id) { var i ; for(i=0;i<imginfo.list.length;i++) if(imginfo.list[i].name!=undefined&&imginfo.list[i].name==id) return i ; return -1 ; } /* ------------------- message warning of unsaved changes ------------------- */ function unsavedmsg(ok) { var msg , len = unsavedchanges.length , i ; if(len==0) return null ; msg = 'You have ' + len + ' unsaved change' + (len==1?"":'s') ; if(len<=3) for(i=0;i<len;i++) msg += (i?',':' (') + unsavedchanges[i] + (i==len-1?')':'') ; msg += '\nIf you hit ' + (ok?'[OK]':'[Leave page]') ; return msg + (len==1?' this change':' these changes') + ' will be lost.' ; } /* --------------- selpoint: choose the clicked waypoint ------------------- */ function selpoint(event) { var i,j,closest,d,mindist,s0=selected[0],s1=segments[s0].data.length ; if(dragging) return ; var flag = (infowindow.close()=='wpinfo') && (event.shiftKey==0) ; if(event.shiftKey) { insert(s0,s1,1) ; segments[s0].data[s1].setpos(event.latLng) ; lookupalt(s0,s1) ; redrawconnect(s0,s1) ; done(['move',s0,s1,event.latLng,event.latLng,1]) ; } else for(s1=s0=-1,i=0;i<segments.length;i++) for(j=0;j<segments[i].data.length;j++) { d = dist(segments[i].data[j].pos,event.latLng) ; if(s0<0||d<mindist) { s0 = i ; s1 = j ; mindist = d ; } } walkto(s0,s1,flag) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* --------------------------------- walkto --------------------------------- */ // draw a selection point (and possibly an info box) at [s0,s1], bringing up // a wpinfo window if flag != 0 function walkto(s0,s1,flag) { var s='',i,ind,excuse,imgname=null,phind=-1,list=imginfo.list ; var datum = segments[s0].data[s1] , pos = datum.pos ; selected = [s0,s1] ; map.panToBounds(new google.maps.LatLngBounds(pos,pos)) ; drawsel(0) ; if(flag||(datum.type==null&&datum.photo.length==0)) { if(flag) wpinfo() ; return ; } if(datum.type!=null) { if(datum.photo.length>0) s = textbox ; else s = finalbox ; if(datum.type!='Generic') s += datum.type + ': ' ; s += datum.marker.title + ' [' + active + '"labelprompt()">Edit</span>]' ; s += '</div>' ; } for(ind=0;ind<datum.photo.length;ind++) { s += textbox ; if(imginfo.status=='ready'&&(phind=findimg(datum.photo[ind]))>=0) s += '<img src="' + jpg(list[phind],imginfo.sizes,imginfo.thumbind) + '" width=' + list[phind].thumbshape[0] + ' height=' + list[phind].thumbshape[1] + '><br>' + '<b>' + list[phind].name + '</b>: ' ; else { if(imginfo.status=='null') excuse = 'no list provided' ; else if(imginfo.status=='ready') { imgname = imginfo.uri ; i = imgname.lastIndexOf('/') ; if(i>=0) imgname = imgname.substring(i+1) ; excuse = 'not present in ' + imgname ; } else if(imginfo.status=='waiting') excuse = imgname + ' is not available' ; else excuse = 'imginfo.status = ' + imginfo.status ; s += 'Photo: ' + datum.photo[ind] + ' (' + excuse + ') ' ; } s += '['+active+ '"photoedit('+ind+')">Edit</span>'+']' ; if(phind>=0) s += ' : ['+active+ '"phinfo('+phind+')">Info</span>'+']' + ' : ['+active+ '"display('+ind+')">Enlarge</span>'+']' ; s += '</div>' ; } if(datum.photo.length>0) s += finalbox + '[' + active + '"photoprompt' + '(null)">Add photo</span>' + ']</div>' ; infowindow.open(s,pos,'walking') ; } /* -------------------------- keystroke handler ---------------------------- */ function walk(e) { var s0=selected[0],s1=selected[1],slast,flag ; if(e.keyCode==40) { map.panTo(segments[s0].data[s1].pos) ; return ; } if(infowindow.close()=='wpinfo') flag = 1 ; else flag = 0 ; if(e.keyCode==32) { selclick() ; return ; } // space if(e.keyCode==13) // return { if(dragging) undraggit() ; else if(s0>=0) draggit(0) ; return ; } if(dragging) return ; if(e.keyCode==8||e.keyCode==46) // delete/backspace { e.preventDefault() ; if(binbtn.active) discard() ; return ; } if(e.keyCode==9) { e.preventDefault() ; inswp(1) ; return ; } // tab if(e.keyCode==39) // forwards { e.preventDefault() ; if(s1<segments[s0].data.length-1) s1 += 1 ; else { s0 += 1 ; if(s0==segments.length) s0 = 0 ; s1 = 0 ; } } else if(e.keyCode==37) // backwards { e.preventDefault() ; if(s1>0) s1 -= 1 ; else { s0 -= 1 ; if(s0<0) s0 = segments.length-1 ; s1 = segments[s0].data.length-1 ; } } else return ; walkto(s0,s1,flag) ; } /* ---------------------------- relative uri ------------------------------- */ function reluri(u1,u2) { var last = u1.lastIndexOf('/') ; if(last<0) return u2 ; u1 = u1.substring(0,last) ; while(u2.substring(0,3)=='../') { last = u1.lastIndexOf('/') ; if(last<0) return u2 ; u2 = u2.substring(3) ; u1 = u1.substring(0,last) ; } return u1 + '/' + u2 ; } /* ------------------------------- getbtnpos -------------------------------- */ function getbtnpos(btnno) { var bounds=map.getBounds(),sw,ne,lat,lon,lam ; sw = bounds.getSouthWest() ; ne = bounds.getNorthEast() ; lam = 52.0 / window.innerHeight ; lat = lam*ne.lat() + (1-lam)*sw.lat() ; lam = 0.5 + (btnno*32-112.0)/window.innerWidth ; lon = lam*ne.lng() + (1-lam)*sw.lng() ; return new google.maps.LatLng(lat,lon) ; } /* ----- unambig: does the selected waypoint determine a unique segment? ---- */ function unambig() // does the selected waypoint determine a unique segment? { var s0=selected[0],s1=selected[1] ; if(segments.length==1) return 1 ; if( ( s0==segments.length-1 || s1!=segments[s0].data.length-1 || ! segments[s0].data[s1].pos.equals(segments[s0+1].data[0].pos) ) && ( s0==0 || s1!=0 || ! segments[s0].data[s1].pos.equals (segments[s0-1].data[segments[s0-1].data.length-1].pos) ) ) return 1 ; else return 0 ; } /* --------------------- undraw & redraw segments -------------------------- */ function undraw(i) { segments[i].route.setMap(null) ; if(segments[i].clickhandler!=null) { google.maps.event.removeListener(segments[i].clickhandler) ; segments[i].clickhandler = null ; } } function redraw(i) { undraw(i) ; draw(i) ; } function recolour(i) { if(i&1) segments[i].route.setOptions({strokeColor:"#ff9999"}) ; else segments[i].route.setOptions({strokeColor:"#ff0000"}) ; } function obliterate(s0) // undraw route and all labels { var i ; for(i=0;i<segments[s0].data.length;i++) segments[s0].data[i].setmap(null) ; undraw(s0) ; disconnect(s0-1) ; disconnect(s0) ; } /* ----------------------------- draw segments ------------------------------ */ function draw(i) { var colour ; if(i&1) colour = "#ff9999" ; else colour = "#ff0000" ; segments[i].route = new google.maps.Polyline(new linepath(i,-1,0,colour)) ; segments[i].route.setMap(map) ; segments[i].clickhandler = google.maps.event.addListener(segments[i].route,"click",selpoint) ; } /* ----------------------- connect and disconnect segments ------------------ */ function disconnect(i) { if(i<0||i>=segments.length-1||segments[i].dots==null) return ; segments[i].dots.setMap(null) ; if(segments[i].dothandler!=null) { google.maps.event.removeListener(segments[i].dothandler) ; segments[i].dothandler = null ; } } function reconnect(i) { disconnect(i) ; connect(i) ; } function connect(i) { if(i<0||i>=segments.length-1) return ; var opos = segments[i].data[segments[i].data.length-1].pos ; var npos = segments[i+1].data[0].pos ; if(opos.equals(npos)) return ; segments[i].dots = new google.maps.Polyline(new dotpath(opos,npos)) ; segments[i].dots.setMap(map) ; segments[i].dothandler = google.maps.event.addListener(segments[i].dots,"click",selpoint) ; } function redrawconnect(s0,s1) { redraw(s0) ; if(s1==0) reconnect(s0-1) ; if(s1=segments[s0].data.length-1) reconnect(s0) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ---------------------- draw the selection point -------------------------- */ // note: there's no point in allowing clicking on a marker because the // event position is always the marker position rather than the click position function drawsel(opt) { var ind,clen,s0=selected[0],s1=selected[1],pos=segments[s0].data[s1].pos ; if(opt) reprofile() ; clen = segments[s0].data.length ; if(clen==1) arrow.rotation = 90 ; else { if(s1==clen-1) ind = s1-1 ; else ind = s1 ; arrow.rotation = bearing(segments[s0].data[ind].pos,segments[s0].data[ind+1].pos) ; } if(sel.marker==null) sel.marker = new google.maps.Marker ( { position:pos, map:map, cursor:'default', icon:arrow , zIndex:2 } ) ; else // avoid unnecessary redraws { if(arrow.rotation!=sel.orientation) sel.marker.setIcon(arrow) ; if(!pos.equals(sel.marker.getPosition())) sel.marker.setPosition(pos) ; } sel.orientation = arrow.rotation ; procur() ; blackout(penbtn) ; if(segments.length>1&&unambig()) blackout(binbtn) ; else greyout(binbtn) ; if(s1!=0&&s1!=clen-1) blackout(scissorsbtn) ; else greyout(scissorsbtn) ; } /* ------------- selclick: respond to click of cursor button --------------- */ function selclick() { mouseopt = 1-mouseopt ; infowindow.close() ; if(mouseopt) { map.setOptions({draggable:false, draggableCursor:'default'}) ; cursorbtn.btn.setAttribute('src','hand.png') ; clickhandle = google.maps.event.addListener(map,"click",selpoint) ; } else { map.setOptions({draggable:true, draggableCursor:''}) ; cursorbtn.btn.setAttribute('src','arrow.png') ; google.maps.event.removeListener(clickhandle) ; } } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------------------------------------------------- */ function genhead(uri,key) { resuri = uri + '/' ; document.write ('<script src="http://maps.google.com/maps/api/js?' + ((key==null||key==undefined)?'':('key='+key+'&')) + 'libraries=geometry"></scr' + 'ipt>' + '<script src="' + resuri + 'dms.js"></scr' + 'ipt>' + '<script src="' + resuri + 'vector3d.js"></scr' + 'ipt>' + '<script src="' + resuri + 'latlon-ellipsoidal.js"></scr' + 'ipt>' + '<script src="' + resuri + 'utm.js"></scr' + 'ipt>' + '<script src="' + resuri + 'osgridref.js"></scr' + 'ipt>' + '<script src="' + resuri + 'FileSaver.js"></scr' + 'ipt>' + '<script src="' + resuri + 'Blob.js"></scr' + 'ipt>' + '<script src="' + resuri + 'pixlib.js"></scr' + 'ipt>' + '<style type="text/css">html, body {width: 100%; height: 100%}' + 'body {margin:0px}a:link{color:#66aaaa}' + 'a:visited{color:#cc3388}a:active{color:#404040}</style>' + '</style><title>Routemaster</title>' + '<link rel="shortcut icon" href=' + resuri + 'bus.gif>' ) ; } /* -------------------------------------------------------------------------- */ /* FUNCTIONS TO GENERATE THE INITIAL MAP */ /* -------------------------------------------------------------------------- */ function genpage() { var thispage=document.URL,xhttp,quotind,plusind,listxhttp ; imginfo = new listinfo() ; imgdiv = null ; elevator = new google.maps.ElevationService ; // coursepoint icons flagsign = { path: "M 0.5 20.5 L 0.5 0.5 12.5 6 0.5 11.5 ", fillColor: '#FCDFFF', fillOpacity: 0.7, strokeColor: 'purple', strokeWeight: 1.5, anchor: new google.maps.Point(0.5,20.5), } ; turnleft = { path: "M 18.5 20.5 L 16.5 11.5 A 2 2 0 0 0 14.5 9.5 "+ "L 11.5 10 11.5 13.5 "+ "6.5 7.5 11.5 1.5 11.5 5 16.5 5.5 A 3.5 3.5 0 0 1 20 9 z", fillColor: '#FCDFFF', fillOpacity: 0.7, strokeColor: 'purple', strokeWeight: 1.5, anchor: new google.maps.Point(18.5,20.5), } ; straighton = { path: "M 7.5 20.5 L 4.5 6.5 0.5 6.5 7.5 0.5 14.5 6.5 10 6.5 z", fillColor: '#FCDFFF', fillOpacity: 0.7, strokeColor: 'purple', strokeWeight: 1.5, anchor: new google.maps.Point(7.5,20.5), } ; turnright = { path: "M 3.5 20.5 L 5.5 11.5 A 2 2 0 0 1 7.5 9.5 L 10.5 10 10.5 13.5 "+ "15.5 7.5 10.5 1.5 10.5 5 5.5 5.5 A 3.5 3.5 0 0 0 2 9 z", fillColor: '#FCDFFF', fillOpacity: 0.7, strokeColor: 'purple', strokeWeight: 1.5, anchor: new google.maps.Point(3.5,20.5), } ; shriek = { path: "M 8.5 21.5 A 2.5 2.5 0 0 1 8.5 16.5 A 2.5 2.5 0 1 1 8.5 21.5 "+ "M 8.5 14.5 4.5 5.5 A 4.5 4.5 0 1 1 12.5 5.5 L 8.5 14.5" , fillColor: '#FCDFFF', fillOpacity: 0.7, strokeColor: 'purple', strokeWeight: 1.5, anchor: new google.maps.Point(8.5,21.5), } ; // icon for arrow representing current waypoint arrow = { path: "M 6 9 0 15 6 0 12 15 z", fillColor: 'black', fillOpacity: 1, strokeColor: 'black', strokeWeight: 0, anchor: new google.maps.Point(6,6), rotation: 0, clickable: false } ; // icon for concentric circles representing draggable waypoint concircle = { path: "M 6 0 A 6 6 0 1 0 6 12 A 6 6 0 1 0 6 0 M 6 3 " + "A 3 3 0 1 0 6 9 A 3 3 0 1 0 6 3", fillColor: 'black', fillOpacity: 0, strokeColor: 'black', strokeWeight: 1, strokeOpacity: 1, anchor: new google.maps.Point(6,6), clickable: false } ; // camera icon camera = { path: "M 0.5 4 A 1.5 1.5 0 0 1 2 2.5 L 5.5 2.5 7 0.5 11 0.5 " + "12.5 2.5 14 2.5 A 1.5 1.5 0 0 1 16 3 L 20 7 16 11 " + "A 1.5 1.5 0 0 1 15 11.5 L 2 11.5 A 1.5 1.5 0 0 1 0.5 10 z " + "M 9 4 A 3 3 0 0 1 9 10 A 3 3 0 0 1 9 4 " , fillColor: '#FCDFFF', fillOpacity: 0.7, strokeColor: 'purple', strokeWeight: 1.5, anchor: new google.maps.Point(21,7), clickable: false } ; window.onload = function() { window.addEventListener("beforeunload",function(e) { var msg = unsavedmsg(0) ; if(msg==null) return undefined ; (e || window.event).returnValue = msg ; //Gecko + IE return msg ; //Gecko + Webkit, Safari, Chrome etc. (msg is ignored by ffx) } ) ; } ; body = document.getElementsByTagName("body")[0] ; while(body.childNodes.length>0) body.removeChild(body.childNodes[body.childNodes.length-1]) ; mapdiv = document.createElement('div') ; mapdiv.setAttribute('id','map') ; mapdiv.setAttribute('style','width:100%;height:100%') ; body.appendChild(mapdiv) ; if((quotind=thispage.indexOf('?'))>=0) { thispage = thispage.substring(quotind+1) ; if((plusind=thispage.indexOf('+'))>0) { getlist(thispage.substring(plusind+1),'uri') ; thispage = thispage.substring(0,plusind) ; } else if(thispage.substring(thispage.length-3)=='.js') { getlist(thispage,'uri') ; mapdiv.appendChild(filedialogue(0)) ; return ; } xhttp = new XMLHttpRequest() ; xhttp.onreadystatechange = function() { if(xhttp.readyState==4) { if(xhttp.status==200) { var x = parser.parseFromString(xhttp.responseText,"application/xml") ; render(x,thispage,0) ; } else alert("Unable to read "+thispage+": error code "+xhttp.status) ; } } xhttp.open("GET",thispage,true) ; xhttp.send() ; } else mapdiv.appendChild(filedialogue(0)) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------- getlist --------------------------------- */ function getlist(uri,imgtype) { var xhttp = new XMLHttpRequest(),list=null,sizes=null,pixpage=null,i,r ; var imagedir=null,thumbshape = [] ; imginfo.status = 'waiting' ; imginfo.type = imgtype ; xhttp.onreadystatechange = function() { if(xhttp.readyState==4) { if(xhttp.status!=200) { alert("Unable to read "+uri+": error code "+xhttp.status) ; return ; } eval(xhttp.responseText) ; imginfo.uri = uri ; imginfo.list = list ; imginfo.sizes = sizes ; imginfo.pixpage = reluri(uri,pixpage) ; imginfo.thumbind = thumbind(sizes) ; setthumbshape(list,sizes,thumbshape,reluri(uri,imagedir)) ; for(i=0;i<list.length;i++) if(list[i].retpage!=undefined) list[i].retpage = reluri(uri,list[i].retpage) ; imginfo.status = 'ready' ; } } xhttp.open("GET",uri,true) ; xhttp.send() ; } /* ----------------------------- file dialogue ------------------------------ */ function filedialogue(overwrite) { var input = document.createElement('input') ; var para = document.createElement('p') ; para.appendChild(document.createTextNode ('\u00a0\u00a0\u00a0Select TCX/GPX file: ')) ; input.setAttribute('type','file') ; input.setAttribute('accept','.tcx,.gpx') ; input.addEventListener('change',function(e) { reader = new FileReader() ; reader.onload = function(e) { var xmldoc = parser.parseFromString(reader.result,"application/xml") ; render(xmldoc,input.files[0].name,overwrite) ; } reader.readAsText(input.files[0]) ; } ) ; para.appendChild(input) ; return para ; } /* -------------------------------------------------------------------------- */
/*page*/ /* --------------------- set up the map and buttons ------------------------- */ function render(xmldoc,filename,overwrite) { var i,opts,centre,lat,lon,minlon,maxlon,minlat,maxlat,newseg,s0,bounds,sw,ne ; infowindow.close() ; document.onkeydown = walk ; if(overwrite) { for(i=0;i<segments.length;i++) obliterate(i) ; unprofile() ; if(sel.marker!=null) sel.marker.setMap(null) ; segments = [] ; if(imginfo.type=='tcx') imginfo = new listinfo() ; } s0 = segments.length ; if(s0==0) { sel = { marker:null, orientation: null } ; pending = [] ; xpending = [] ; actions = [] ; unsavedchanges = [] ; nactions = dragging = 0 ; loadno = -1 ; prox = proa = pron = null ; altdiv = curcan = curdiv = cmap = smap = curhandle = null ; } // set up segments newseg = gensegment(xmldoc,filename) ; segments.push(newseg[0]) ; if(s0==0) { if(newseg[1].title!=null) settitle(newseg[1].title) ; else settitle('Untitled Route') ; } actions[nactions++] = [ 'load' , s0 , newseg[0].data.slice() , loadno , newseg[1] ] ; loadno = nactions-1 ; if(!newseg[1].optim.already) optimaction(segments.length-1,defparms,0) ; // find max and min lat and long for(i=0;i<segments[s0].data.length;i++) { lat = segments[s0].data[i].pos.lat() ; lon = segments[s0].data[i].pos.lng() ; if(i==0||lon<minlon) minlon = lon ; if(i==0||lon>maxlon) maxlon = lon ; if(i==0||lat<minlat) minlat = lat ; if(i==0||lat>maxlat) maxlat = lat ; } if(s0==0) centre = new google.maps.LatLng((minlat+maxlat)/2,(minlon+maxlon)/2) ; if(map==null) // all this only done on first call { opts = { zoom: 22, center: centre, scaleControl: true, rotateControl: false, streetViewControl: false, mapTypeId: google.maps.MapTypeId.TERRAIN, disableDoubleClickZoom: true, styles: [ { "featureType": "poi", "stylers": [{ "visibility": "off" }] } ], mapTypeControl:true, mapTypeControlOptions: { style:google.maps.MapTypeControlStyle.HORIZONTAL_BAR }, mapTypeIds: [ google.maps.MapTypeId.ROADMAP, google.maps.MapTypeId.TERRAIN, google.maps.MapTypeId.SATELLITE ] } ; map = new google.maps.Map(mapdiv,opts) ; // set up buttons setbtn = genbutton('settings') ; cursorbtn = genbutton('cursor') ; cursorbtn.ui.addEventListener('click',selclick) ; scissorsbtn = genbutton('scissors') ; binbtn = genbutton('bin') ; penbtn = genbutton('pen') ; undobtn = genbutton('undo') ; redobtn = genbutton('redo') ; dlbtn = genbutton('dl') ; selclick() ; } else if(s0!=0) { bounds = map.getBounds() ; sw = bounds.getSouthWest() ; ne = bounds.getNorthEast() ; if(sw.lat()<minlat) minlat = sw.lat() ; if(sw.lng()<minlon) minlon = sw.lng() ; if(ne.lat()>maxlat) maxlat = ne.lat() ; if(ne.lng()>maxlon) maxlon = ne.lng() ; } map.fitBounds(new google.maps.LatLngBounds( new google.maps.LatLng(minlat,minlon), new google.maps.LatLng(maxlat,maxlon))) ; for(i=0;i<segments[s0].data.length;i++) segments[s0].data[i].setmap(map) ; if(nactions>1) donesomething() ; // specifically, done loading & optimisation else actions.length = nactions ; // load with no optimisation hence no undo if(s0==0) { selected = [0,0] ; drawsel(1) ; } else greyout(dlbtn) ; draw(s0) ; connect(s0-1) ; connect(s0) ; reprofile() ; } /* ------------------------------- settitle --------------------------------- */ function settitle(newtitle) { routetitle = newtitle ; var h = document.getElementsByTagName('title')[0] ; while(h.firstChild) h.removeChild(h.firstChild) ; h.appendChild(document.createTextNode(routetitle)) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ------------------------------- retitle ---------------------------------- */ function retitle() { infowindow.close() ; var response = window.prompt("Modify title:",routetitle) ; if(response==null) return ; else response = response.substring(0,15) ; if(response==routetitle) return ; actions[nactions++] = ['edittitle',routetitle,response] ; actions.length = nactions ; blackout(undobtn) ; greyout(redobtn) ; settitle(response) ; } /* ------------------------------- genbutton -------------------------------- */ function genbutton(name) { var u,v,w,b,g,k,h=null,div=document.createElement('div'),act ; u = document.createElement('div') ; u.style.backgroundColor = '#ffffff' ; u.style.border = '2px solid #ffffff' ; u.style.borderRadius = '3px' ; u.style.boxShadow = '0 2px 6px rgba(0,0,0,.3)' ; if(name=='dl'||name=='settings'||name=='cursor') u.style.cursor = 'pointer' ; else u.style.cursor = 'default' ; u.style.marginBottom = '12px' ; if(name!='dl') u.style.marginRight = '4px' ; u.style.textAlign = 'center' ; div.appendChild(u) ; b = document.createElement('img') ; g = name + '.png' ; if(name=='scissors') { h = snip ; div.index = 3 ; } else if(name=='bin') { h = discard ; div.index = 4 ; } else if(name=='pen') { h = labelprompt ; div.index = 5 ; } else if(name=='undo') { h = undo ; div.index = 6 ; } else if(name=='redo') { h = redo ; div.index = 7 ; } else if(name=='dl') { h = dl ; div.index = 8 ; } else if(name=='settings') { h = popup ; div.index = 1 ; } else if(name=='cursor') { g = 'arrow.png' ; k = 'hand.png' ; div.index = 2 ; } if(name!='cursor') { k = 'black' + g ; g = 'grey' + g ; } g = resuri + g ; k = resuri + k ; if(name=='dl'||name=='settings'||name=='cursor') b.setAttribute('src',k) ; else b.setAttribute('src',g) ; b.setAttribute('width',24) ; b.setAttribute('height',24) ; u.appendChild(b) ; map.controls[google.maps.ControlPosition.BOTTOM_CENTER].push(div) ; if(name=='dl'||name=='settings') u.addEventListener('click',h) ; if(name=='dl'||name=='settings') act = 1 ; else act = 0 ; return { ui:u , btn:b , active:act , greyimg:g , blackimg:k , handler:h } ; } /* -------------------------------------------------------------------------- */
/*page*/ /* --------------- construct a segment from an xml document ----------------- */ function genseg(a) { this.data = a ; this.route = this.routehandler = this.dots = this.dothandler = null ; } /* -------------------------------------------------------------------------- */ function gensegment(xmldoc,filename) { var xmlcoords,nodeno,tcx,type,mindist,lat,lon,i,j,segment,node,alt,pos ; var ind,caption,data=[],alreadyoptimised=0,photo,time,valid ; var props = { title: null , inputlen: null , optim: { already: 0, ndel: 0, origlen: 0, parms: null } } tcx = filename.length ; if(filename.substring(tcx-4,tcx)=='.tcx') tcx = 1 ; else if(filename.substring(tcx-4,tcx)=='.gpx') tcx = 0 ; else { alert(filename+' is neither .tcx nor .gcx') ; throw '' ; } if(tcx) { // optimised? xmlcoords = xmldoc.getElementsByTagName('Optimised') ; if(xmlcoords.length) { props.optim.already = 1 ; props.optim.origlen = parseInt(xmlcoords[0].getAttribute('from')) ; props.optim.ndel = props.optim.origlen - parseInt(xmlcoords[0].getAttribute('to')) ; props.optim.parms = { tol: parseFloat(xmlcoords[0].getAttribute('tol')) , maxsep: parseFloat(xmlcoords[0].getAttribute('maxsep')) , wppenalty: parseFloat(xmlcoords[0].getAttribute('wppenalty')) , vweight: parseFloat(xmlcoords[0].getAttribute('vweight')) } ; } // photo list? if(imginfo.uri==null||imginfo.type=='tcx') { xmlcoords = xmldoc.getElementsByTagName('PhotoList') ; if(xmlcoords.length) { imginfo = new listinfo() ; getlist(xmlcoords[0].getAttribute('src'),'tcx') ; } } // route title xmlcoords = xmldoc.getElementsByTagName('Name') ; for(i=0;i<xmlcoords.length&&props.title==null;i++) if( xmlcoords[i].parentNode.nodeName=='Course' || xmlcoords[i].parentNode.nodeName=='Lap' ) props.title = xmlcoords[i].childNodes[0].textContent ; // loop over the track points to get the coords xmlcoords = xmldoc.getElementsByTagName('Trackpoint') ; for(i=0;i<xmlcoords.length;i++) { lat = lon = alt = time = null ; photo = [] ; for(valid=1,nodeno=0;nodeno<xmlcoords[i].childNodes.length;nodeno++) { node = xmlcoords[i].childNodes[nodeno] ; if(node.nodeName=='AltitudeMeters') alt = xmlfloat(node) ; else if(node.nodeName=='Time') // '1970-01-01T03:040:08Z' { time = new Date(node.childNodes[0].textContent) ; if(!isvaliddate(time)) time = null ; } else if(node.nodeName=='Position') for(j=0;j<node.childNodes.length;j++) { if(node.childNodes[j].nodeName=='LatitudeDegrees') lat = xmlfloat(node.childNodes[j]) ; else if(node.childNodes[j].nodeName=='LongitudeDegrees') lon = xmlfloat(node.childNodes[j]) ; } else if(node.nodeName=='Extensions') for(j=0;j<node.childNodes.length;j++) { if(node.childNodes[j].nodeName=='Photo') photo = node.childNodes[j].childNodes[0].textContent.split(' ') ; else if(node.childNodes[j].nodeName=='ValidTime') valid = 0 ; } } if(lat==null||lon==null) continue ; data[data.length] = new datatype(new google.maps.LatLng(lat,lon),alt) ; for(ind=0;ind<photo.length;ind++) data[data.length-1].addphoto(photo[ind]) ; if(valid) data[data.length-1].t = time ; } } else // gpx { if(xmldoc.getElementsByTagName('name').length>0) props.title = xmldoc.getElementsByTagName('name')[0].childNodes[0].textContent ; // loop over the track points to get the coords xmlcoords = xmldoc.getElementsByTagName('trkpt') ; if(xmlcoords.length==0) xmlcoords = xmldoc.getElementsByTagName('rtept') ; for(i=0;i<xmlcoords.length;i++) { lat = parseFloat(xmlcoords[i].getAttribute('lat')) ; lon = parseFloat(xmlcoords[i].getAttribute('lon')) ; for(time=alt=null,nodeno=0;nodeno<xmlcoords[i].childNodes.length;nodeno++) { node = xmlcoords[i].childNodes[nodeno] ; if(node.nodeName=='ele') alt = parseFloat(node.textContent) ; else if(node.nodeName=='time') time = new Date(node.childNodes[0].textContent) ; } data[data.length] = new datatype(new google.maps.LatLng(lat,lon),alt) ; data[data.length-1].t = time ; } } for(i=0;i<data.length;i++) if(data[i].h==null) { alert(data[i].pos+' has no altitude... unable to proceed') ; throw '' ; } props.inputlen = data.length ; for(i=0;i<data.length;i++) if((time=data[i].t)!=null) // nullify illegal times if(time.getTime()<365*24*3600000) data[i].t = null ; segment = new genseg(data) ; // loop over the course points to get the labels if(tcx) xmlcoords = xmldoc.getElementsByTagName('CoursePoint') ; else xmlcoords = xmldoc.getElementsByTagName('wpt') ; for(i=0;i<xmlcoords.length;i++) { caption = type = lat = lon = null ; if(tcx) for(nodeno=0;nodeno<xmlcoords[i].childNodes.length;nodeno++) { node = xmlcoords[i].childNodes[nodeno] ; if(node.nodeName=='Name') caption = node.childNodes[0].textContent ; else if(node.nodeName=='PointType') type = node.childNodes[0].textContent ; else if(node.nodeName=='Position') for(j=0;j<node.childNodes.length;j++) { if(node.childNodes[j].nodeName=='LatitudeDegrees') lat = xmlfloat(node.childNodes[j]) ; else if(node.childNodes[j].nodeName=='LongitudeDegrees') lon = xmlfloat(node.childNodes[j]) ; } } else { lat = parseFloat(xmlcoords[i].getAttribute('lat')) ; lon = parseFloat(xmlcoords[i].getAttribute('lon')) ; for(nodeno=0;nodeno<xmlcoords[i].childNodes.length;nodeno++) { node = xmlcoords[i].childNodes[nodeno] ; if(node.nodeName=='name') caption = node.childNodes[0].textContent ; else if(node.nodeName=='type') type = node.childNodes[0].textContent ; } } if(lat==null||lon==null||caption==null||type==null) { alert('Badly formatted course point' ) ; throw '' ; } pos = new google.maps.LatLng(lat,lon) ; for(j=0;j<data.length;j++) if(j==0||dist(pos,data[j].pos)<mindist) { mindist = dist(pos,data[j].pos) ; ind = j ; } segment.data[ind].setlabel(type,caption) ; } if(props.optim.origlen==0) props.optim.origlen = data.length ; return [ segment , props ] ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------------------------------------------------- */ /* OPTIMISATION */ /* -------------------------------------------------------------------------- */ function optimaction(segno,parms,force) { var s = segments[segno], result = optimise(s,parms) ; var ndel = s.data.length - result.length ; if((force==0&&ndel<s.data.length/2)||ndel==0) return 0 ; actions[loadno][4].optim.ndel = ndel ; actions[nactions++] = [ 'optimise' , segno , parms ] ; segments[segno] = new genseg(result) ; actions[loadno][4].optim.parms = { tol: parms.tol , maxsep: parms.maxsep , wppenalty: parms.wppenalty , vweight: parms.vweight } ; return 1 ; } /* -------------------------------------------------------------------------- */ function optimprompt() { var msg = 'Enter 4 optimisation parms (tol, maxsep, wppenalty and vweight)' ; var parmstr = defparms.tol + ' ' + defparms.maxsep.toFixed(0) + ' ' + defparms.wppenalty.toFixed(0) + ' ' + defparms.vweight.toFixed(1) ; var parms,i ; infowindow.close() ; for(i=0;;i++) { newparms = prompt(msg,parmstr) ; if(newparms==null) return ; if(newparms=='') { parms = defparms ; break ; } newparms = newparms.split(' ') ; if(newparms.length==0) { parms = defparms ; break ; } parms = { tol: parseFloat(newparms[0]) , maxsep: parseFloat(newparms[1]) , wppenalty: parseFloat(newparms[2]) , vweight: parseFloat(newparms[3]) } ; if( isNaN(parms.tol)==0 && isNaN(parms.maxsep)==0 && isNaN(parms.wppenalty)==0 && isNaN(parms.vweight)==0 ) break ; if(i==0) msg = '*** Illegal parms ***\n' + msg ; } if(optimaction(segments.length-1,parms,1)) { donesomething() ; draw(segments.length-1) ; } routeinfo() ; } /* -------------------------------------------------------------------------- */ function optimise(s,parms) { var stk,nstk,stk2,clen=s.data.length,i,j,m,step=new Array(clen-1) ; var opos,oalt,npos,nalt,ndatum,mpos,malt,arccentre,arctol,pathpos ; var legal,theta,omega,x,y,hyp,tdist,maxtheta,mintheta,d,dver,od,odver,odash ; var bearings,pi=Math.PI,tol=parms.tol ; stk = [ { data:[s.data[0]] , err:0 , pathpos:1 } ] ; for(i=0;i<clen-1;i++) step[i] = dist(s.data[i].pos,s.data[i+1].pos) ; while(stk[0].pathpos<clen) { pathpos = stk[0].pathpos ; opos = s.data[pathpos-1].pos ; oalt = s.data[pathpos-1].h ; // try extending to pathpos+i for(bearings=[],nstk=[],arctol=null,i=0;i<clen-pathpos;i++) { ndatum = s.data[pathpos+i] ; npos = ndatum.pos ; nalt = ndatum.h ; if(i==0) hyp = step[pathpos-1] ; else if((hyp=dist(opos,npos))>parms.maxsep) break ; omega = angle(opos,npos) ; // find the min and max legal bearing if(hyp>tol) { theta = Math.asin(tol/hyp) ; if(arctol==null) { arccentre = omega ; arctol = theta ; } else { odash = omega - arccentre ; while(odash>pi) odash -= 2*pi ; while(odash<-pi) odash += 2*pi ; maxtheta = Math.min(arctol,odash+theta) ; mintheta = Math.max(-arctol,odash-theta) ; if(maxtheta<mintheta) break ; arccentre += (maxtheta+mintheta) /2 ; arctol = (maxtheta-mintheta) /2 ; } } bearings[i] = { hyp:hyp , omega:omega } ; // see whether this breaches the max error on any intermediate point for(legal=1,od=odver=tdist=m=0;m<i;m++,od=d,odver=dver) { mpos = s.data[pathpos+m].pos ; malt = s.data[pathpos+m].h ; x = bearings[m].hyp ; theta = bearings[m].omega ; d = x * Math.sin(theta-omega) ; dver = 0 ; if(d*d<tol*tol&&oalt!=null&&nalt!=null&&malt!=null) { y = hyp - x*Math.cos(theta-omega) ; y = Math.sqrt(d*d+y*y) ; dver = parms.vweight * ( malt - (oalt*y+nalt*x)/(x+y) ) ; } if(d*d+dver*dver>tol*tol) { legal = 0 ; break ; } tdist += (step[pathpos-1+i]/3) * (d*d+d*od+od*od + dver*dver+odver*dver+odver*odver) ; } // if we emerge with 'legal' non-zero then we may advance to pathpos+i // and tdist is the sum of squared errors if(legal) nstk.push ( { data: stk[0].data.concat([ndatum]) , err: stk[0].err + pi*tdist + parms.wppenalty , pathpos: stk[0].pathpos+i+1 } ) ; if(ndatum.type!=null||ndatum.photo.length>0) break ; } // end loop over i for(stk2=[],i=1,j=0;i<stk.length||j<nstk.length;) if(i==stk.length) stk2.push(nstk[j++]) ; else if(j==nstk.length||stk[i].pathpos<nstk[j].pathpos) stk2.push(stk[i++]) ; else if(stk[i].pathpos>nstk[j].pathpos) stk2.push(nstk[j++]) ; else if(stk[i].err<nstk[j].err) { stk2.push(stk[i++]) ; j += 1 ; } else { stk2.push(nstk[j++]) ; i += 1 ; } stk = stk2 ; } return stk[0].data ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------------------------------------------------- */ /* THE MAIN POPUP MENU AND THE FUNCTIONS IT GOVERNS */ /* -------------------------------------------------------------------------- */ function popup() { var opts,pos,s0=selected[0],s1=selected[1] ; infowindow.close() ; if(dragging) { opts = finalbox + "Hit [return] when you've finished dragging.</div>" ; infowindow.open(opts,getbtnpos(0),'settings') ; return ; } // route options opts = textbox + active + '"routeinfo()">Route info</span><br>' ; if(altdiv==null) opts += active + '"profile()">Show altitude profile</span><br>' ; else opts += active + '"unprofile()">Hide altitude profile</span><br>' ; opts += active + '"addload(1)">Load new route</span></div>' + textbox ; // segment options if(unambig()) opts += active + '"revseg()">' ; else opts += inactive ; opts += 'Reverse segment</span><br>' ; opts += active + '"manualcal()">Calibrate segment altitudes</span><br>' ; opts += active + '"addload(0)">Load route as a new segment</span></div>' ; // waypoint options opts += textbox + active + '"wpinfo()">Waypoint info</span><br>' ; if(segments[selected[0]].data.length>1) opts += active + '"wpdel()">' ; else opts += inactive ; opts += 'Delete waypoint</span><br>' ; opts += active + '"draggit(0)">Make waypoint draggable</span><br>' ; opts += active + '"inswp(1)">Insert draggable waypoint ahead</span><br>' ; opts += active + '"inswp(-1)">Insert draggable waypoint behind</span>' ; opts += '</div>'+finalbox+active ; // tool options if(querycanfullscreen()) { if(queryfullscreen()==0) opts += '"enterFullscreen()">Enter full screen</span><br>' + active ; else opts += '"exitFullscreen()">Leave full screen</span><br>' + active ; } opts += '"help()">Help</span></div>' ; infowindow.open(opts,getbtnpos(0),'settings') ; } /* ------------------------------- calwork --------------------------------- */ function calwork(s0,y) { var i,s1 ; for(s1=0;s1<segments[s0].data.length;s1++) if(segments[s0].data[s1].h!=null) segments[s0].data[s1].h += y ; reprofile() ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ------------------------------ manualcal --------------------------------- */ function manualcal() { infowindow.close() ; var x,y,s0=selected[0] ; x = prompt('Enter offset in metres to add to altidudes:') ; if(x==null) return ; y = parseFloat(x) ; if(isNaN(y)) { alert(x+' is not a number') ; return ; } calwork(s0,y) ; done(['recal',s0,y]) ; } /* --------------------------------- help ----------------------------------- */ function help() { var str ; infowindow.close() ; str = textbox + '<table cellpadding=0 cellspacing=0>' ; str += '<tr><td style="padding-bottom:4px">' ; str += '<nobr><img src="'+cursorbtn.greyimg+'" width=24 height=24>&nbsp;' ; str += '<img src="'+cursorbtn.blackimg+'" width=24 height=24></nobr>' ; str += '<td rowspan=6>&nbsp;&nbsp;&nbsp;' ; str += '<td style="padding-bottom:4px">' ; str += 'toggle between using the mouse to select waypoints and to ' ; str += 'drag the map<br>(the space bar has the same function)' ; str += '<tr><td style="padding-bottom:4px">' ; str += '<nobr><img src="'+scissorsbtn.blackimg+'" width=24 height=24>&nbsp;' ; str += '<img src="'+binbtn.blackimg+'" width=24 height=24></nobr>' ; str += '<td style="padding-bottom:4px">' ; str += 'split the current segment at the selected point<br>' ; str += 'delete the currrent segment (or use the delete or backspace key)' ; str += '<tr><td style="padding-bottom:4px">' ; str += '<img src="'+penbtn.blackimg+'" width=24 height=24>' ; str += '<td style="padding-bottom:4px">' ; str += 'add a labelled coursepoint at the current position (1-10chars)<br>' ; str += 'click on flag to edit; right-click to change symbol; ' ; str += 'delete label to delete' ; str += '<tr><td style="padding-bottom:4px">' ; str += '<nobr><img src="'+dlbtn.blackimg+'" width=24 height=24>&nbsp;' ; str += '<img src="'+setbtn.blackimg+'" width=24 height=24></nobr>' ; str += '<td style="padding-bottom:4px">download route as .tcx<br>' ; str += 'access to miscellaneous tools and functions' ; str += '<tr><td valign=top>Keyboard: <td>' ; str += '\u2190/\u2192 move the current waypoint forwards or backwards;<br>' ; str += '\u2193 centres the map on the current waypoint;<br>' ; str += '[return] makes the current waypoint draggable;<br>' ; str += '[tab] inserts a draggable waypoint;<br>[space]=toggle cursor mode;' ; str += '<br>[del], [backspace]=delete segment (bin button).' ; str += '<tr><td valign=top>Mouse: <td>when the cursor is in selection' ; str += ' mode:<br>[shift click] extends the current segment by' ; str += ' the cursor position.</table></div>' ; str += finalbox + '<a style="cursor:pointer;color:#0000bd;text-'+ 'decoration:none" href="http://www.masterlyinactivity.com/software'+ '/routemaster.html" target="_blank">Technical documentation and '+ 'source code</a>'+neutral+' (opens in new tab/window)</span></div>' ; infowindow.open(str,getbtnpos(0),'help') ; } /* -------------------------------------------------------------------------- */
/*page*/ /* --------------------------------- wpdel ---------------------------------- */ function wpdelwork(s0,s1) { var i,response=segments[s0].data[s1],clen=segments[s0].data.length ; response.setmap(null) ; for(i=s1;i<clen-1;i++) segments[s0].data[i] = segments[s0].data[i+1] ; segments[s0].data.length = clen-1 ; selected = [s0,s1] ; if(s1==segments[s0].data.length) selected[1] -= 1 ; redrawconnect(s0,s1) ; drawsel(1) ; return response ; } function wpdel() { var s0=selected[0],s1=selected[1],i ; infowindow.close() ; done(['wpdel',s0,s1,wpdelwork(s0,s1)]) ; } /* --------------------------------- revseg --------------------------------- */ function revsegwork(s0) { var i,s=segments[s0],j,x,len=s.data.length ; disconnect(s0-1) ; disconnect(s0) ; for(i=0;i<len/2;i++) { j = (len-1) - i ; x = s.data[i] ; s.data[i] = s.data[j] ; s.data[j] = x ; } for(i=0;i<s.data.length;i++) if(s.data[i].type=='Right') s.data[i].settype('Left') ; else if(s.data[i].type=='Left') s.data[i].settype('Right') ; if(s0==selected[0]) selected[1] = (len-1) - selected[1] ; connect(s0-1) ; connect(s0) ; drawsel(1) ; } /* -------------------------------------------------------------------------- */ function revseg() { infowindow.close() ; revsegwork(selected[0]) ; done(['revseg',selected[0]]) ; } /* -------------------------------------------------------------------------- */ function addload(overwrite) { var msg ; infowindow.close() ; if(overwrite) { msg = unsavedmsg(1) ; if(msg!=null) if(!confirm(msg)) return ; } infowindow.open(filedialogue(overwrite),getbtnpos(0),'addload') ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------------------------------------------------- */ /* DRAGGING A (POSSIBLY NEWLY INSERTED) WAYPOINT IS QUITE A LOT OF WORK */ /* -------------------------------------------------------------------------- */ function insert(s0,s1,n) { var i ; for(i=segments[s0].data.length+n-1;i>s1;i--) segments[s0].data[i] = segments[s0].data[i-n] ; for(i=0;i<n;i++) segments[s0].data[s1+i] = new datatype(null,null) ; } /* --------------------------------- inswp ---------------------------------- */ function inswp(dir) { var s0=selected[0],s1=selected[1],bounds,del,pos,data=segments[s0].data ; var len = data.length ; if(len==1) pos = data[0].pos ; if(dir>=0) s1 = selected[1] += 1 ; insert(s0,s1,1) ; if(len==1) { bounds = map.getBounds() ; del = bounds.getNorthEast().lng() - bounds.getSouthWest().lng() ; pos = new google.maps.LatLng(pos.lat(),pos.lng()+dir*del/10) ; } else if(s1==0) pos = interp(data[2].pos,data[1].pos,1.5) ; else if(s1<len) pos = interp(data[s1-1].pos,data[s1+1].pos,0.5) ; else pos = interp(data[s1-2].pos,data[s1-1].pos,1.5) ; data[s1].setpos(pos) ; draggit(1) ; } /* -------------------------------- draggit --------------------------------- */ // draggit makes the current waypoint draggable var l1,l2,startpos,seg0,seg1,seg2,colour,inserted ; function draggit(insparm) { var s0=selected[0],s1=selected[1],start,end,i,len=segments[s0].data.length ; startpos = segments[s0].data[s1].pos ; inserted = insparm ; infowindow.close() ; greyout(scissorsbtn) ; greyout(binbtn) ; greyout(penbtn) ; greyout(undobtn) ; greyout(redobtn) ; greyout(dlbtn) ; map.panToBounds(new google.maps.LatLngBounds(startpos,startpos)) ; sel.marker.setMap(null) ; sel.marker = new google.maps.Marker( { position: segments[s0].data[s1].pos, map: map, cursor: 'default', icon: concircle , draggable: true , zIndex: 2 } ) ; if(s0&1) colour = "#ff9999" ; else colour = "#ff0000" ; segments[s0].route.setMap(null) ; if(segments[s0].clickhandler!=null) { google.maps.event.removeListener(segments[s0].clickhandler) ; segments[s0].clickhandler = null ; } seg0 = seg2 = null; if(s1>1) { seg0 = new google.maps.Polyline(new linepath(s0,0,s1,colour)) ; seg0.setMap(map) ; } if(s1==0) start = 0 ; else start = s1-1 ; if(s1==len-1) end = s1+1 ; else end = s1+2 ; seg1 = new google.maps.Polyline(new linepath(s0,start,end,colour)) ; seg1.setMap(map) ; if(s1<segments[s0].data.length-2) { seg2 = new google.maps.Polyline(new linepath(s0,s1+1,len,colour)) ; seg2.setMap(map) ; } l1 = google.maps.event.addListener(sel.marker,'drag',function() { segments[s0].data[s1].setpos(this.getPosition()) ; seg1.setMap(null) ; seg1 = new google.maps.Polyline(new linepath(s0,start,end,colour)) ; seg1.setMap(map) ; if(s1==0&&s0>0) { disconnect(s0-1) ; connect(s0-1) ; } if(s1==len-1&&s0<segments.length-1) { disconnect(s0) ; connect(s0) ; } } ) ; dragging = 1 ; } /* ------------------------------- undraggit -------------------------------- */ // undraggit is invoked by [return] to terminate waypoint dragging function undraggit() { var s0=selected[0],s1=selected[1],i,s1dash,pos=segments[s0].data[s1].pos ; var xpos ; google.maps.event.removeListener(l1) ; dragging = 0 ; if(seg0!=null) seg0.setMap(null) ; seg1.setMap(null) ; if(seg2!=null) seg2.setMap(null) ; segments[s0].route = new google.maps.Polyline(new linepath(s0,-1,0,colour)) ; segments[s0].route.setMap(map) ; segments[s0].data[s1].h = null ; lookupalt(s0,s1) ; sel.marker.setMap(null) ; sel.marker = null ; // force a redraw drawsel(1) ; if(inserted||dist(startpos,pos)>5) done(['move',s0,s1,startpos,pos,inserted]) ; if(segments.length==1) blackout(dlbtn) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------------------------------------------------- */ /* CODE TO GET ALTITUDES FOR NEWLY INSERTED POINTS */ /* -------------------------------------------------------------------------- */ function lookupalt(s0,s1) // set up a request for the alt of the new point { var hi,lo,segno=s0,ptno=s1,lopos,hipos,datum=segments[s0].data[s1] ; for(lo=null,s0=segno,s1=ptno-1;lo==null;s1--) { if(s1<0) { s0 -= 1 ; if(s0<0) break ; s1 = segments[s0].data.length-1 ; } if(segments[s0].data[s1].h!=null) lo = [s0,s1] ; } for(hi=null,s0=segno,s1=ptno+1;hi==null;s1++) { if(s1==segments[s0].data.length) { s0 += 1 ; if(s0==segments.length) break ; s1 = 0 ; } if(segments[s0].data[s1].h!=null) hi = [s0,s1] ; } if(lo==null&&hi==null) { alert('no points left with altitudes: unable to proceed') ; throw '' ; } if(lo!=null) lopos = segments[lo[0]].data[lo[1]].pos ; if(hi!=null) hipos = segments[hi[0]].data[hi[1]].pos ; if(lo==null||(hi!=null&&dist(lopos,datum.pos)>dist(hipos,datum.pos))) { lo = hi ; lopos = hipos ; } pending.push([datum,lopos,segments[lo[0]].data[lo[1]].h]) ; elevator.getElevationForLocations({locations:[datum.pos,lopos]},calibrate) ; } /* -------------------------------------------------------------------------- */ // pending is the list of inserted points for which google altitudes are needed // note a pitfall with the elevation service - it's hard to tell which // response corresponds to which request: the coordinates may not match // because google truncates to 0.00001 deg; hence the use of the dist function. function calibrate(results,status) { var pno,flag ; if(status===google.maps.ElevationStatus.OVER_QUERY_LIMIT) alert('No calibration data available: over Google query limit') ; else if(status!==google.maps.ElevationStatus.OK||results.length!=2) alert('Calibration error') ; // now find whether any of our elevation results allow us to fix an altitude for(pno=0;pno<pending.length;pno++) if( dist(pending[pno][0].pos,results[0].location)<5 && dist(pending[pno][1],results[1].location)<5 ) { if(pno>0) alert('Warning: Google elevation results out of sequence') ; diff = results[0].elevation - results[1].elevation ; pending[pno][0].h = pending[pno][2] + diff ; } for(i=pno=0;pno<pending.length;pno++) if(pending[pno][0].h==null) { if(pno!=i) pending[i] = pending[pno] ; i += 1 ; } pending.length = i ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------------------------------------------------- */ /* FUNCTIONS FOR COMPUTING & DISPLAYING THE ALTITUDE PROFILE */ /* -------------------------------------------------------------------------- */ function profile() { infowindow.close() ; var i,sum,s0,s1,len,oldpos,pos,alt,h,amin,amax,c,y,step,ctx ; var startpos,prevpos ; for(pron=s0=0;s0<segments.length;s0++) pron += segments[s0].data.length ; prox = new Array(pron) ; proa = new Array(pron) ; smap = new Array(segments.length) ; cmap = new Array(610) ; prox[0] = 0 ; for(amax=amin=null,sum=pron=s0=0;s0<segments.length;s0++) for(len=segments[s0].data.length,s1=0;s1<len;s1++,pron++) { proa[pron] = segments[s0].data[s1].h ; if(proa[pron]!=null) { if(amax==null||proa[pron]>amax) amax = proa[pron] ; if(amin==null||proa[pron]<amin) amin = proa[pron] ; } pos = segments[s0].data[s1].pos ; if(pron) sum = prox[pron] = sum + dist(pos,oldpos) ; oldpos = pos ; } if(pron==0||amin==amax) return ; if(amin>0) { if(amax>3*amin) amin = 0 ; else amin *= 1 - (amax/amin-1)/2 ; } altdiv = document.createElement('div') ; altdiv.setAttribute ('style','position:absolute;height:190px;right:0;top:0;width:620px') ; c = document.createElement('canvas') ; c.setAttribute('width',620) ; c.setAttribute('height',200) ; altdiv.appendChild(c) ; body.appendChild(altdiv) ; ctx = c.getContext("2d") ; ctx.font = "10px Helvetica" ; ctx.lineWidth = 0 ; ctx.globalAlpha = 0.6 ; ctx.fillStyle = 'lightgray' ; ctx.lineWidth = 0 ; ctx.rect(0,0,620,200) ; ctx.fill() ; // draw a profile of each segment for(i=pron=s0=0;s0<segments.length;s0++) { len = segments[s0].data.length ; smap[s0] = new Array(len) ; if(s0&1) ctx.fillStyle = "#ff9999" ; else ctx.fillStyle = "#ff0000" ; for(startpos=opos=null,s1=0;s1<len;prevpos=[s0,s1],s1++,pron++,opos=pos) { pos = 10 + 600 * prox[pron] / sum ; if(opos!=null) for(;i<(pos+opos)/2;i++) cmap[i-10] = prevpos ; smap[s0][s1] = 0.5 + Math.floor(pos) ; if(proa[pron]!=null) { y = 10 + 180 * (amax-proa[pron]) / (amax-amin) ; if(startpos==null) { ctx.beginPath() ; ctx.moveTo(pos,y) ; startpos = pos ; } else ctx.lineTo(pos,y) ; } } ctx.lineTo(pos,190) ; ctx.lineTo(startpos,190) ; ctx.closePath() ; ctx.fill() ; } for(;i<610;i++) cmap[i] = prevpos ; // lines if(amax-amin>2500) step = 1000 ; else if(amax-amin>1250) step = 500 ; else step = 100 ; for(i=step*Math.floor(amin/step+1);i<amax;i+=step) { y = 10.5 + Math.floor(180*(amax-i)/(amax-amin)) ; ctx.beginPath() ; ctx.lineWidth = 1 ; ctx.strokeStyle = '#555' ; ctx.moveTo(10,y) ; ctx.lineTo(610,y) ; ctx.stroke() ; ctx.strokeText(i,590,y-2) ; } // cursor curdiv = document.createElement('div') ; curdiv.setAttribute ('style','position:absolute;height:190px;right:0;top:0;width:620px') ; curhandle = curdiv.addEventListener("click",function(e) { var pos = e.clientX - (window.innerWidth-610) ; if((pos-594)*(pos-594)+(e.clientY-16)*(e.clientY-16)<200) { unprofile() ; return ; } if(pos<0) pos = 0 ; else if(pos>600) pos = 600 ; selected = cmap[pos] ; drawsel(0) ; } ) ; body.appendChild(curdiv) ; curcan = null ; procur() ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ------------------------------- unprofile -------------------------------- */ function unprofile() { var i,match,node ; infowindow.close() ; if(altdiv==null) return ; curdiv.removeEventListener('click',curhandle) ; for(match=0,i=body.childNodes.length-1;match==0&&i>=0;i--) { node = body.childNodes[i] ; match = (node==altdiv) ; body.removeChild(node) ; } prox = proa = altdiv = cmap = smap = curdiv = curcan = curhandle = null ; } function reprofile() { if(altdiv!=null) { unprofile() ; profile() ; } } /* -------------------------------- procur ---------------------------------- */ function procur() { if(altdiv==null) return ; var pos = smap[selected[0]][selected[1]] , ctx , i ; if(curcan!=null) curdiv.removeChild(curcan) ; curcan = document.createElement('canvas') ; curcan.setAttribute('width',620) ; curcan.setAttribute('height',200) ; curdiv.appendChild(curcan) ; ctx = curcan.getContext("2d") ; ctx.beginPath() ; ctx.lineWidth = 1 ; ctx.moveTo(pos,10) ; ctx.lineTo(pos,190) ; ctx.stroke() ; // the circle of the 'x' ctx.beginPath() ; ctx.strokeStyle = '#555' ; ctx.fillStyle = 'white' ; ctx.lineWidth = 3 ; ctx.arc(604,16,14.1,0,2*Math.PI,false) ; ctx.stroke() ; ctx.fill() ; for(i=6;i<=26;i+=20) // the two bars of the 'x' { ctx.beginPath() ; ctx.moveTo(594,i) ; ctx.lineTo(614,32-i) ; ctx.stroke() ; } } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------------------------------------------------- */ /* ROUTEINFO IS A MENU WHICH ITSELF SUPPORTS THE COMBINE FUNCTION */ /* -------------------------------------------------------------------------- */ function routeinfo() { var s0,s1,s,d,asc,des,oalt,alt,nlabels,nowpts,i,unsaved,props,spacing,npix ; var maxsep,sep,outoforder,tlast,otime,time,tdist,ttime,ntimes ; infowindow.close() ; props = actions[loadno][4] ; tlast = null ; tdist = ttime = outoforder = 0 ; maxsep = nlabels = npix = des = asc = d = nowpts = ntimes = 0 ; for(s0=0;s0<segments.length;s0++) { nowpts += segments[s0].data.length ; for(oalt=null,s1=0;s1<segments[s0].data.length;otime=time,s1++) { if((alt=segments[s0].data[s1].h)!=null) { if(oalt!=null) { if(alt>oalt) asc += alt-oalt ; else des += oalt - alt ; } oalt = alt ; } if(segments[s0].data[s1].type!=null) nlabels += 1 ; npix += segments[s0].data[s1].photo.length ; time = segments[s0].data[s1].t ; if(time!=null) { time = time.getTime() ; ntimes += 1 ; } if(tlast!=null&&time!=null&&time<tlast) outoforder = 1 ; // out of order if(time!=null) tlast = time ; if(s1) { sep = dist(segments[s0].data[s1-1].pos,segments[s0].data[s1].pos) ; d += sep ; if(sep>maxsep) maxsep = sep ; if(time!=null&&otime!=null) { tdist += sep/1000 ; ttime += (time-otime)/(3600*1000) ; } } } } s = finalbox +'<nobr>Title: <b>'+routetitle+'</b> [' ; s += active + '"retitle()">Edit</span>'+']</nobr><br>' ; if(loadno>0) { s += '<nobr>&nbsp;&nbsp;&nbsp;Last added route' ; if(props.title!=null) s += ' (' + props.title + ')' ; s += ':</nobr><br><nobr>&nbsp;&nbsp;&nbsp;' ; } else s += '<nobr>' ; s += 'Track points on input: ' + props.inputlen ; if(props.optim.already) { s += ' (previously optimised)</nobr>' ; if(nowpts!=props.inputlen) s += '<br>Now ' + nowpts + ' track points' ; } else if(props.optim.ndel==0) { if(nactions==loadno+1) s += ' [' + active + '"optimprompt()">' ; else s += ' [' + inactive ; s += 'Optimise' + neutral + ']</span></nobr>' ; } else s += ', optimised to ' + (props.inputlen-props.optim.ndel) + '</nobr>' ; /* ------------------------------------------------------------------------ */
/*page*/ /* ------------------------------------------------------------------------ */ if(!props.optim.already&&props.inputlen-props.optim.ndel!=nowpts) s += '<br>Now ' + nowpts + ' track points' ; s += '<br>' ; if(outoforder==0) { if(ntimes==0) s += 'No timings provided<br>' ; else { if(ntimes<nowpts) s += (nowpts-ntimes) + ' points have no associated timings ' ; s += '[' + active + '"deltimes()">Discard timings</span>' + neutral + ']<br>' ; } if(tdist>0&&ttime>0) s += 'Average speed = ' + (tdist/ttime).toFixed(1) + ' km/hr<br>' ; } else s += 'Times are out of sequence (will be discarded on download)<br>' ; if(nlabels>0) s += nlabels + ' labelled course point' + (nlabels>1?'s':'') + '<br>' ; if(npix>0) s += npix + ' photo' + (npix>1?'s':'') + '<br>' ; unsaved = unsavedchanges.length ; if(unsaved>0) s += unsaved + ' unsaved change' + (unsaved>1?'s':'') + '<br>' ; if(segments.length>1) s += segments.length + ' segments [' + active + '"combine()">Combine</span>' + neutral + ']<br>' + '<i>Note that segments must be combined before saving</i><br>' ; s += 'Max waypoint separation: '+maxsep.toFixed(0)+'m<br>' ; if(maxsep>=100) s += '<i>Note that separations &gt;100m are illegal on Garmin</i><br>' + '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[</span>' + active + '"extrapts()">' + 'Interpolate extra points</span>]<br>' + neutral ; s += 'Total distance: '+(d/1000).toFixed(3)+'km<br>' ; s += 'Total ascent: '+asc.toFixed(0)+'m<br>' ; s += 'Total descent: '+des.toFixed(0)+'m</div>' ; infowindow.open(s,getbtnpos(0),'routeinfo') ; } /* -------------------------------------------------------------------------- */ function deltimes() { var s0,s1,task=[] ; for(s0=0;s0<segments.length;s0++) for(s1=0;s1<segments[s0].data.length;s1++) if(segments[s0].data[s1].t!=null) { task.push([s0,s1,segments[s0].data[s1].t]) ; segments[s0].data[s1].t = null ; } infowindow.close() ; done(['deltimes',task]) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ------------------------- interpolate extra points ----------------------- */ function extrapts(opt) { var s0,s1,sep,data,n,opos,npos,i,lambda,lox,nlox,taskno,ind ; var task = [ 'extra' , selected[0] , selected[1] ] ; infowindow.close() ; for(nlox=s0=0;s0<segments.length;s0++) for(data=segments[s0].data,s1=1;s1<data.length;s1++) if((sep=dist(opos=data[s1-1].pos,npos=data[s1].pos))>100) { n = Math.floor(sep/95) ; insert(s0,s1,n) ; for(i=0;i<n;i++) { lambda = (i+1) / (n+1) ; data[s1+i].setpos(new google.maps. LatLng(lambda*npos.lat()+(1-lambda)*opos.lat(), lambda*npos.lng()+(1-lambda)*opos.lng())) ; } if(selected[0]==s0&&s1<=selected[1]) selected[1] += n ; task.push([s0,s1,data.slice(s1-1,s1+n+1)]) ; s1 += n ; nlox += n+2 ; } if(nlox==0) return ; done(task) ; xpending.push(task) ; lox = new Array(nlox) ; for(ind=0,taskno=3;taskno<task.length;taskno++) { n = task[taskno][2].length ; for(i=0;i<n;i++) lox[ind++] = task[taskno][2][i].pos ; } elevator.getElevationForLocations( {locations:lox} , function (results,status) { // assume that the results come in sequence, ie. correspond to xpending[0] var task=xpending.shift(),taskno,d0,dn,lambda ; if(status===google.maps.ElevationStatus.OVER_QUERY_LIMIT) alert('No calibration data available: over Google query limit') ; else if(status!==google.maps.ElevationStatus.OK) alert('Calibration error') ; for(ind=0,taskno=3;taskno<task.length;taskno++,ind+=n+2) { n = task[taskno][2].length-2 ; d0 = task[taskno][2][0].h - results[ind].elevation ; dn = task[taskno][2][n+1].h - results[ind+n+1].elevation ; if( dist(task[taskno][2][0].pos,results[ind].location)>5 || dist(task[taskno][2][n+1].pos,results[ind+n+1].location)>5 ) alert('Anomaly with Google elevation results') ; for(i=0;i<n;i++) { lambda = (i+1) / (n+1) ; task[taskno][2][1+i].h = results[ind+1+i].elevation + lambda*dn + (1-lambda)*d0 ; } } } ) ; routeinfo() ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ------------------------------- combine1 --------------------------------- */ function combine1(sa,sb) { var i,ca,cb,calen,cblen,la=null,lb=null,cdup=0 ; undraw(sb) ; disconnect(sb-1) ; calen = segments[sa].data.length ; cblen = segments[sb].data.length ; cb = segments[sb].data[0].pos ; cdup = ( ca = cb.equals(segments[sa].data[calen-1].pos) ) ; if(cdup) { la = segments[sa].data[calen-1] ; lb = segments[sb].data[0] ; segments[sa].data.length = ( calen -= 1 ) ; } if(selected[0]==sb) selected = [ sa , selected[1]+calen ] ; segments[sa].data = segments[sa].data.concat(segments[sb].data) ; return [ cblen , cdup , la , lb ] ; } function combinework() { var task,s0 ; for(task=['combine',segments.length],s0=1;s0<segments.length;s0++) task.push(combine1(0,s0)) ; segments.length = 1 ; return task ; } /* -------------------------------------------------------------------------- */ function combine() { infowindow.close() ; done(combinework()) ; drawsel(1) ; redraw(0) ; blackout(dlbtn) ; } function recombine() { var s0 ; for(s0=1;s0<segments.length;s0++) { undraw(s0) ; combine1(0,s0) ;} segments.length = 1 ; drawsel(1) ; redraw(0) ; blackout(dlbtn) ; } /* -------------------------------------------------------------------------- */ // combine returns [ cblen , cdup , la , lb ] ; function uncombine(task) { var i,j,llen,flag,subtask ; for(flag=0,s0=task[1]-1,i=task.length-1;i>=2;i--,s0--) { subtask = task[i] ; cblen = subtask[0] ; cdup = subtask[1] ; llen = segments[0].data.length ; segments[s0] = { data: segments[0].data.slice(llen-cblen,llen) , route: null , clickhandler: null } ; llen = segments[0].data.length = llen+cdup-cblen ; if(cdup) { segments[0].data[llen-1] = subtask[2] ; segments[s0].data[0] = subtask[3] ; } if(flag==0&&selected[1]>=llen) { selected = [ s0 , selected[1]-llen ] ; flag = 1 ; } } drawsel(1) ; undraw(0) ; for(i=0;i<segments.length;i++) { draw(i) ; connect(i) ; } greyout(dlbtn) ; } /* -------------------------------------------------------------------------- */ /* WPINFO IS A MENU GIVING ACCESS TO THE SETALT FUNCTION */ /* -------------------------------------------------------------------------- */ function wpinfo() { infowindow.close() ; var s0=selected[0],s1=selected[1],alt,nalt,s,x,lat,lng,grad,gradstr,time ; var datum = segments[s0].data[s1] , pos = datum.pos ; lat = pos.lat() ; lng = pos.lng() ; s = finalbox ; if(lat>=0) s += lat.toFixed(5) + '\u00b0 N, ' ; else { lat = -lat ; s += lat.toFixed(5) + '\u00b0 S, ' ; } if(lng>=0) s += lng.toFixed(5) + '\u00b0 E<br>' ; else { lng = -lng ; s += lng.toFixed(5) + '\u00b0 W<br>' ; } x = new LatLon(lat,lng) ; if(lat>49.9&&lat<62&&lng>-12&&lng<2.5&&lat-1.5*lng<75) s += 'OS grid ref: ' + OsGridRef.latLonToOsGrid(x) ; else s += 'UTM coords = ' + x.toUtm() ; s += '<br>' ; alt = segments[s0].data[s1].h ; if(alt!=null) s += 'Altitude: ' + alt.toFixed(0) + 'm ' + active + '"setalt(1)">[Edit]' ; else s += active + '"setalt(0)">Set altitude' ; s += '</span><br>' ; time = segments[s0].data[s1].t ; if(time!=null&&time.getFullYear()>1980) s += 'Date: ' + time.toDateString() + '<br>' + 'Time: ' + time.toTimeString() + '<br>' ; if(datum.type!=null) s += datum.type + ': ' + datum.marker.title + ' [' + active + '"labelprompt()">Edit</span>]<br>' ; if(alt==null||s1==segments[s0].data.length-1) nalt = null ; else { nalt = segments[s0].data[s1+1].h ; if(nalt!=null) x = dist(pos,segments[s0].data[s1+1].pos) ; } if(nalt!=null&&Math.abs(nalt-alt)<x) { grad = 100*Math.asin((nalt-alt)/x) ; gradstr = Math.abs(grad).toFixed(0) ; if(gradstr=='0') s += 'Flat<br>' ; else if(grad>0) s += 'Climb '+gradstr+'%<br>' ; else s += 'Descend: '+gradstr+'%<br>' ; } s += '<span style="font-size:80%">' ; if(segments.length>1) s += 'Segment '+s0+' p' ; else s += 'P' ; s += 'oint ' + s1 + '</span></div>' ; infowindow.open(s,pos,'wpinfo') ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------- setalt ---------------------------------- */ function setalt(edit) { infowindow.close() ; var s0=selected[0],s1=selected[1],x,y=null,oldalt ; oldalt = segments[s0].data[s1].h.toFixed(0) ; if(edit) x = prompt('Enter altitude (m):',oldalt) ; else x = prompt('Enter altitude (m):') ; if(x==null) return ; if(x!='') { y = parseFloat(x) ; if(isNaN(y)) { alert(x+' is not a number') ; return ; } } if(y==null&&oldalt==null) return ; if(y!=null&&Math.abs(y-oldalt)<0.1) return ; segments[s0].data[s1].h = y ; done(['setalt',s0,s1,oldalt,y]) ; reprofile() ; wpinfo() ; } /* -------------------------------------------------------------------------- */ /* THE LABELS ARE ACCESSED FROM THE PEN BUTTON OR BY CLICKING ON THE MAP */ /* -------------------------------------------------------------------------- */ function labelprompt() { var oldcaption='',oldtype,type='Generic',s0=selected[0],s1=selected[1] ; var str , flag = (infowindow.close()=='wpinfo') ; var datum = segments[s0].data[s1] ; oldtype = datum.type ; if(oldtype!=null) oldcaption = datum.marker.title ; if(oldcaption==null) oldcaption = '' ; if(oldtype!=null) type = oldtype ; if(oldtype==null) str = 'Enter' ; else str = 'Modify or delete' ; var caption = window.prompt(str+' label:',oldcaption) ; if(caption==null) { if(flag) wpinfo() ; else walkto(s0,s1,0) ; return ; } else if(caption=='') type = null ; else caption = caption.substring(0,10) ; if(caption==oldcaption) { if(flag) wpinfo() ; else walkto(s0,s1,0) ; return ; } segments[s0].data[s1].setlabel(type,caption) ; done(['editlabel',s0,s1,oldcaption,caption,oldtype,type]) ; if(flag) wpinfo() ; else walkto(s0,s1,0) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ------------------------------ labelcycle -------------------------------- */ function labelcycle() { var s0,s1,datum,oldtype,caption,type,flag=(infowindow.close()=='wpinfo') ; ; for(s0=0;s0<segments.length;s0++) for(s1=0;s1<segments[s0].data.length;s1++) { datum = segments[s0].data[s1] ; if(datum.marker!=this) continue ; oldtype = datum.type ; caption = datum.marker.title ; if(oldtype=='Generic') type = 'Left' ; else if(oldtype=='Left') type = 'Straight' ; else if(oldtype=='Straight') type = 'Right' ; else if(oldtype=='Right') type = 'Danger' ; else type = 'Generic' ; datum.settype(type) ; selected = [s0,s1] ; done(['editlabel',s0,s1,caption,caption,oldtype,type]) ; if(flag) wpinfo() ; return ; } } function photoprompt(e) { var s0=selected[0],s1=selected[1] ; if(e!=null) e.preventDefault() ; var flag = (infowindow.close()=='wpinfo') ; var datum = segments[s0].data[s1] ; var photo = window.prompt('Enter photo name:','') ; if(photo!=null&&photo!='') { done(['editphoto',s0,s1,datum.photo.length,null,photo]) ; datum.addphoto(photo) ; } if(flag) wpinfo() ; else walkto(s0,s1,0) ; } function photoedit(ind) { var s0=selected[0],s1=selected[1],i ; var flag = (infowindow.close()=='wpinfo') ; var datum = segments[s0].data[s1] ; var photo = window.prompt('New photo name:',datum.photo[ind]) ; if(photo!=null&&photo!='') for(i=0;i<datum.photo.length;i++) if(datum.photo[i]==photo) { photo = null ; break ; } if(photo!=null) { if(photo=='') photo = null ; done(['editphoto',s0,s1,ind,datum.photo[ind],photo]) ; datum.setphoto(ind,photo) ; } if(flag) wpinfo() ; else walkto(s0,s1,0) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ----------------------------- display photo ------------------------------ */ var lmove,rmove ; function advance(s0,s1,ind) { for(ind++;;ind++) { if(ind>=segments[s0].data[s1].photo.length) { ind = 0 ; s1 += 1 ; } if(s1==segments[s0].data.length) { s0 += 1 ; if(s0==segments.length) return null ; s1 = 0 ; } if(ind<segments[s0].data[s1].photo.length) return [s0,s1,ind] ; } } function retreat(s0,s1,ind) { for(ind--;;ind--) { if(ind<0) { s1 -= 1 ; ind = null ; } if(s1<0) { if(s0==0) return null ; else s0 -= 1 ; s1 = segments[s0].data.length-1 ; } if(ind==null) ind = segments[s0].data[s1].photo.length - 1 ; if(ind>=0) return [s0,s1,ind] ; } } function prev() { dodisplay(lmove[0],lmove[1],lmove[2],-1) ; } function backtogps() { document.onkeydown = walk ; window.removeEventListener('resize',resize) ; body.removeChild(imgdiv) ; } function next() { dodisplay(rmove[0],rmove[1],rmove[2],1) ; } function display(ind) { var phind ; document.onkeydown = imgwalk ; window.addEventListener('resize',resize) ; infowindow.close() ; imgdiv = document.createElement('div') ; imgdiv.setAttribute('style','position:fixed;width:100%;height:100%;'+ 'left:0;top:0;background:black') ; dodisplay(selected[0],selected[1],ind,1) ; body.appendChild(imgdiv) ; } function dodisplay(s0,s1,ind,dir) { var phind=findimg(segments[s0].data[s1].photo[ind]) , pre=null ; selected[0] = s0 ; selected[1] = s1 ; lmove = retreat(s0,s1,ind) ; rmove = advance(s0,s1,ind) ; if(dir<0&&lmove!=null) pre = findimg(segments[lmove[0]].data[lmove[1]].photo[lmove[2]]) ; else if(dir>=0&&rmove!=null) pre = findimg(segments[rmove[0]].data[rmove[1]].photo[rmove[2]]) ; if(pre!=null) pre = imginfo.list[pre] ; gendisplay(imgdiv,imginfo.list[findimg(segments[s0].data[s1].photo[ind])], imginfo.sizes,'javascript:prev()','javascript:backtogps()', 'javascript:next()','GPS track',pre) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ------------------------------ image walk -------------------------------- */ function imgwalk(e) { e.preventDefault() ; if(e.keyCode==39) { if(rmove!=null) next() ; return ; } else if(e.keyCode==37) { if(lmove!=null) prev() ; return ; } else if(e.keyCode==40) reduce() ; else if(e.keyCode==38) enlarge() ; else if(e.keyCode==70) enterfullscreen() ; else backtogps() ; } /* --------------------------------- photo info ----------------------------- */ function phinfo(i) { infowindow.close() ; var s0=selected[0],s1=selected[1],s,shape,ind,hind,r,k ; var list=imginfo.list,sizes=imginfo.sizes ; s = finalbox + 'Name: ' + list[i].name + '<br>Title: ' + list[i].title ; for(hind=null,ind=0;ind<i;ind++) if(list[ind].name==null) hind = ind ; if(hind!=null) s += "<br>Under \u201c" + list[hind].title + "\u201d" ; // how many sizes? for(r=ind=0;ind<sizes.length;ind++) if(sizes[ind].type==undefined) r += 1 ; s += '<br>Available in ' + r + ' size' + (r>1?'s: ':': ') ; // print the sizes for(k=ind=0;ind<sizes.length;ind++) if(sizes[ind].type==undefined) { if(k>0) { if(k==r-1) s += ' and ' ; else s += ', ' ; } shape = imgsize(list[i],sizes,ind) ; s += shape[0] + 'x' + shape[1] ; k += 1 ; } shape = list[i].thumbshape ; s += '<br>Thumb: ' + shape[0] + 'x' + shape[1] ; for(ind=0;ind<sizes.length;ind++) if(sizes[ind].type=='raw') { shape = imgsize(list[i],sizes,ind) ; s += '<br>Raw: ' + shape[0] + 'x' + shape[1] ; } if(imginfo.pixpage!=null) s += '<br><a style="cursor:pointer;color:#0000bd;text-decoration:none" '+ 'href="'+imginfo.pixpage+'" target="_blank">'+ 'Full photo set</a>'+neutral+' (opens in new tab/window)</span>' ; if(list[i].retid!=null) { for(hind=null,ind=0;ind<=i;ind++) if(list[ind].retpage!=undefined&&list[ind].retpage!=null) hind = list[ind].retpage + '.html#' + list[i].retid ; if(hind!=null) s += '<br><a style="cursor:pointer;color:#0000bd;'+ 'text-decoration:none" href="' + hind + '" target="_blank">'+ 'Route notes</a>'+neutral+' (opens in new tab/window)</span>' ; } infowindow.open(s,segments[s0].data[s1].pos,'phinfo') ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ------------------------- snip: apply scissors -------------------------- */ function snipwork(s0,s1) { var i,k,newlen ; undraw(s0) ; segments.length += 1 ; for(i=segments.length-1;i>s0+1;i--) segments[i] = segments[i-1] ; newlen = segments[s0].data.length - s1 ; segments[s0+1] = new genseg(segments[s0].data.slice(s1)) ; segments[s0+1].dots = segments[s0].dots ; segments[s0+1].dothandler = segments[s0].dothandler ; segments[s0].dots = segments[s0].dothandler = null ; segments[s0].data.length = s1 + 1 ; segments[s0].data[s1] = new datatype(segments[s0].data[s1].pos,segments[s0].data[s1].h) ; draw(s0) ; draw(s0+1) ; for(i=s0+2;i<segments.length;i++) recolour(i) ; selected = [s0+1,0] ; drawsel(1) ; greyout(dlbtn) ; } function snip() { var i,s0=selected[0],s1=selected[1] ; infowindow.close() ; done(['snip',s0,s1]) ; snipwork(s0,s1) ; } /* ------------------------ discard: bin a segment ------------------------- */ function binwork(s0) { var i ; obliterate(s0) ; for(i=s0;i<segments.length-1;i++) segments[i] = segments[i+1] ; segments.length -= 1 ; for(i=s0;i<segments.length;i++) recolour(i) ; connect(s0-1) ; selected[1] = 0 ; if(selected[0]==segments.length) selected[0] = 0 ; drawsel(1) ; if(segments.length==1) blackout(dlbtn) ; } function discard() { var i,s0=selected[0] ; infowindow.close() ; done(['bin',s0,segments[s0]]) ; binwork(s0) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------- actionname ------------------------------ */ function actionname(x) { if(x[0]=='bin') return 'delete segment' ; if(x[0]=='snip') return 'split segment' ; if(x[0]=='editlabel') { if(x[4]=='') return 'delete label' ; else if(x[3]=='') return 'label waypoint' ; else return 'edit label' ; } if(x[0]=='edittitle') return 'edit title' ; if(x[0]=='wpdel') return 'delete waypoint' ; if(x[0]=='move') { if(x[5]) return 'insert waypoint' ; else return 'drag waypoint' ; } if(x[0]=='recal') return 'recalibrate altitudes' ; if(x[0]=='setalt') return 'set waypoint altitude' ; if(x[0]=='resign') return 'change label symbol' ; if(x[0]=='combine') return 'combine '+x[1]+' segments' ; if(x[0]=='revseg') return 'reverse segment' ; if(x[0]=='interpolate') return 'interpolate missing altitudes' ; if(x[0]=='optimise') return 'optimisation' ; if(x[0]=='deltimes') return 'delete times' ; if(x[0]=='editphoto') { if(x[5]==null) return 'delete photo' ; else if(x[4]==null) return 'add photo' ; else return 'change photo' ; } if(x[0]=='extra') return 'interpolate extra points' ; } function actiontype(x) { if( x=='snip'||x=='combine'||x=='interpolate' || x=='optimise'||x=='load' ) return 0 ; else return 1 ; } /* -------------------------------------------------------------------------- */ function done(something) { if( nactions>0 && unsavedchanges.length>0 && something[0]=='editlabel' && actions[nactions-1][0]==something[0] && actions[nactions-1][1]==something[1] // don't merge change with delete && actions[nactions-1][2]==something[2] && something[6]!=null ) { actions[nactions-1][4] = something[4] ; // caption actions[nactions-1][6] = something[6] ; // type } else { actions[nactions++] = something ; donesomething() ; } } function donesomething() { actions.length = nactions ; blackout(undobtn) ; greyout(redobtn) ; if(actiontype(actions[nactions-1][0])!=0) { if(unsavedchanges.length>=3) unsavedchanges.push(null) ; else unsavedchanges.push(actionname(actions[nactions-1])) ; } } /* -------------------------------------------------------------------------- */
/*page*/ /* --------------------------------- undo ---------------------------------- */ function undo() { infowindow.close() ; var opts = active + '"confirmedundo()">Undo ' + actionname(actions[nactions-1])+'</span>' ; infowindow.open(opts,getbtnpos(5),'undo') ; } function confirmedundo() { var i,ano=nactions-1,action=actions[ano][0],s0=actions[ano][1],s1,caption ; var oldcaption,task,ind ; infowindow.close() ; if(action!='revseg'&&action!='interpolate'&&action!='deltimes') s1 = actions[ano][2] ; if(action=='bin') { disconnect(s0-1) ; for(i=segments.length;i>s0;i--) { segments[i] = segments[i-1] ; recolour(i) ; } segments[s0] = s1 ; for(s1=0;s1<segments[s0].data.length;s1++) segments[s0].data[s1].setmap(map) ; draw(s0) ; connect(s0-1) ; connect(s0) ; if(selected[0]>=s0) selected[0] += 1 ; drawsel(1) ; greyout(dlbtn) ; } else if(action=='snip') // undo snip { selected = [ s0 , segments[s0].data.length-1 ] ; combine1(s0,s0+1) ; for(i=s0+1;i<segments.length-1;i++) { segments[i] = segments[i+1] ; recolour(i) ; } segments.length -= 1 ; if(segments.length==1) blackout(dlbtn) ; draw(s0) ; drawsel(1) ; } else if(action=='editlabel') // undo create/edit/delete label segments[s0].data[s1].setlabel(actions[ano][5],actions[ano][3]) ; else if(action=='edittitle') settitle(s0) ; else if(action=='wpdel') // ['wpdel',s0,s1,wpdelwork(s0,s1)] { insert(s0,s1,1) ; segments[s0].data[s1] = actions[ano][3] ; segments[s0].data[s1].setmap(map) ; selected = [s0,s1] ; redrawconnect(s0,s1) ; drawsel(1) ; } else if(action=='move') { if(actions[ano][5]) wpdelwork(s0,s1) ; else move(s0,s1,actions[ano][3]) ; } else if(action=='recal') calwork(s0,-s1) ; else if(action=='setalt') segments[s0].data[s1].h = actions[ano][3] ; else if(action=='combine') uncombine(actions[ano]) ; else if(action=='revseg') revsegwork(s0) ; else if(action=='interpolate') for(i=0;i<s0.length;i++) segments[0].data[s0[i]].h = null ; else if(action=='deltimes') for(i=0;i<s0.length;i++) segments[s0[i][0]].data[s0[i][1]].t = s0[i][2] ; else if(action=='optimise') // [ 'load' , s0 , data.slice() , loadno , props ] { for(ano--;ano>=0&&actions[ano][0]!='load';ano--) ; segments[s0].data = actions[ano][2] ; actions[loadno][4].optim.ndel = 0 ; selected = [s0,0] ; redraw(s0) ; drawsel(1) ; } else if(action=='editphoto') { ind = actions[ano][3] ; if(actions[ano][5]==null) // undo delete for(i=segments[s0].data[s1].photo.length;i>ind;i--) segments[s0].data[s1].photo[i] = segments[s0].data[s1].photo[i-1] ; if(ind>=segments[s0].data[s1].photo.length) segments[s0].data[s1].addphoto(actions[ano][4]) ; else segments[s0].data[s1].setphoto(ind,actions[ano][4]) ; } else if(action=='extra') for(selected=[s0,s1],i=actions[ano].length-1;i>=3;i--) { task = actions[ano][i] segments[task[0]].data.splice(task[1],task[2].length-2) ; } nactions -= 1 ; if(nactions<=1) greyout(undobtn) ; blackout(redobtn) ; if(actiontype(actions[nactions][0])!=0&&unsavedchanges.length>0) unsavedchanges.length -= 1 ; ; if(action=='optimise'||action=='dltimes') routeinfo() ; else if(action=='editphoto'||action=='editlabel') walkto(s0,s1,0) ; } /* --------------------------------- move ----------------------------------- */ function move(s0,s1,pos) { segments[s0].data[s1].setpos(pos) ; redrawconnect(s0,s1) ; drawsel(1) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* --------------------------------- redo ---------------------------------- */ function redo() { infowindow.close() ; var opts = active + '"confirmedredo()">Redo ' + actionname(actions[nactions])+'</span>' ; infowindow.open(opts,getbtnpos(6),'redo') ; } function confirmedredo() { var i,action=actions[nactions][0],s0=actions[nactions][1],s1,caption,a,b,c ; var task,ind,photo ; if(action!='bin'&&action!='revseg'&&action!='interpolate'&&action!='deltimes') s1 = actions[nactions][2] ; infowindow.close() ; if(action=='bin') binwork(s0) ; else if(action=='snip') snipwork(s0,s1) ; else if(action=='editlabel') // redo create/edit/delete label segments[s0].data[s1].setlabel(actions[nactions][6],actions[nactions][4]) ; else if(action=='edittitle') settitle(s1) ; else if(action=='wpdel') wpdelwork(s0,s1) ; else if(action=='move') // ['move',s0,s1,oldpos,newpos,inserted] { if(actions[nactions][5]) insert(s0,s1,1) ; move(s0,s1,actions[nactions][4]) ; } else if(action=='recal') calwork(s0,s1) ; else if(action=='setalt') segments[s0].data[s1].h = actions[nactions][4] ; else if(action=='combine') recombine(actions[nactions]) ; else if(action=='revseg') revsegwork(s0) ; else if(action=='interpolate') interpolatework() ; else if(action=='deltimes') for(i=0;i<s0.length;i++) segments[s0[i][0]].data[s0[i][1]].t = null ; else if(action=='optimise') { result = optimise(segments[s0],actions[nactions][2]) ; actions[loadno][4].optim.ndel = segments[s0].data.length - result.length ; segments[s0].data = result ; selected = [s0,0] ; redraw(s0) ; drawsel(1) ; routeinfo() ; } else if(action=='editphoto') { ind = actions[nactions][3] ; photo = actions[nactions][5] ; if(actions[nactions][4]==null) segments[s0].data[s1].addphoto(photo) ; else segments[s0].data[s1].setphoto(ind,photo) ; } else if(action=='extra') for(selected=[s0,s1],i=3;i<actions[nactions].length;i++) { task = actions[nactions][i] ; a = segments[task[0]].data.slice(0,task[1]) ; b = task[2].slice(1,task[2].length-1) ; c = segments[task[0]].data.slice(task[1]) ; segments[task[0]].data = a.concat(b,c) ; } nactions += 1 ; if(nactions==actions.length) greyout(redobtn) ; blackout(undobtn) ; if(actiontype(actions[nactions-1][0])!=0) unsavedchanges.push(action) ; if(action=='editphoto'||action=='editlabel') walkto(s0,s1,0) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ---------------------------------- dl ----------------------------------- */ function dl() { var i,j,k,noalt,xmldoc,course,lap,datum,filename,blob,track,time,routelen ; var trackpoint,coursepoint,str,flag,clen=segments[0].data.length,tlast,di,dk ; var origlen,ndel,ano,photo,npix,thisuri,maxsep,sep,tdist,ttime,time,otime ; var distance = new Array(clen) , msecs = new Array(clen) ; var valid = new Array(clen) ; for(tlast=null,maxsep=noalt=tdist=ttime=i=flag=0;i<clen;otime=time,i++) { if(segments[0].data[i].h==null) noalt += 1 ; time = segments[0].data[i].t ; if(time!=null) time = time.getTime() ; if(tlast!=null&&time!=null&&time<tlast) flag = 1 ; // out of order if(time!=null) tlast = time ; if(i) { sep = dist(segments[0].data[i-1].pos,segments[0].data[i].pos) ; distance[i] = distance[i-1] + sep ; if(sep>maxsep) maxsep = sep ; if(time!=null&&otime!=null) { tdist += sep ; ttime += time - otime ; } } else distance[i] = 0 ; } routelen = distance[clen-1] ; if(maxsep>100&&!confirm('Some gaps between waypoints are >100m.\n'+ 'This will cause problems if used for navigation in a Garmin 500.\n'+ 'You can hit [OK] and I will proceed anyway, or\n'+ 'you can hit [Cancel] to interpolate extra points\n'+ '(recommended - go to Route Info under the cogwheel).')) return ; if(noalt&&!confirm(noalt+' waypoints have no associated altitudes.\n'+ 'You can hit [OK] and I will interpolate altitudes (not guaranteed), or\n'+ 'you can hit [Cancel] and try again later when the altitudes may be '+ 'available.')) return ; if(routetitle=='Untitled Route') filename = '' ; else { i = routetitle.indexOf(' ') ; if(i<=0) filename = routetitle ; else filename = routetitle.substring(0,i) ; } filename = prompt("Enter filename: ",filename) ; if(filename==null) return ; else if(routetitle=='Untitled Route') settitle(filename) ; i = filename.length ; if(i==0) { filename = 'route' ; i = 5 ; } if(filename.substring(i-4)!='.tcx') filename += '.tcx' ; if(noalt) interpolate() ; unsavedchanges = [] ; // interpolate/extrapolate times in msec for(i=0;i<clen;i++) { valid[i] = 1 ; if(segments[0].data[i].t==null) msecs[i] = null ; else msecs[i] = segments[0].data[i].t.getTime() ; } if(tdist==0||flag!=0) for(i=0;i<clen;i++) msecs[i] = distance[i] * 333 ; else for(i=0;i<clen;i=k) { for(;i<clen&&segments[0].data[i].t!=null;i++) ; // advance to null if(i==clen) break ; for(k=i+1;k<clen&&segments[0].data[k].t==null;k++) ; // advance to non-null for(j=i;j<k;j++) valid[i] = 0 ; if(i==0) for(time=msecs[k],j=i;j<k;j++) msecs[j] = time - (distance[k]-distance[j])*ttime/tdist ; else if(k==clen) for(time=msecs[i-1],j=i;j<clen;j++) msecs[j] = time + (distance[j]-distance[i-1])*ttime/tdist ; else for(j=i,di=distance[i-1],dk=distance[k];j<k;j++) msecs[j] = ( msecs[i-1]*(dk-distance[j]) + msecs[k]*(distance[j]-di) ) / (dk-di) ; } str = '<?xml version="1.0" encoding="UTF-8" standalone="no" ?>\n' + '<TrainingCenterDatabase xmlns="http://www.garmin.com/xmlschemas/' + 'TrainingCenterDatabase/v2"\n' + ' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"\n' + ' xsi:schemaLocation="http://www.garmin.com/' + 'xmlschemas/TrainingCenterDatabase/v2 ' + 'http://www.garmin.com/xmlschemas/TrainingCenterDatabasev2.xsd">\n' ; str += ' <Folders><Courses><CourseFolder Name="Courses">\n' ; str += ' <CourseNameRef><Id>'+routetitle+'</Id></CourseNameRef>\n' ; str += ' </CourseFolder></Courses></Folders>\n<Courses><Course>\n' ; str += ' <Name>'+routetitle+'</Name>\n <Lap>\n' + adddist(routelen) ; time = (msecs[clen-1]-msecs[0]) / 1000 ; str += ' <TotalTimeSeconds>' + time.toFixed(0) + '</TotalTimeSeconds>\n' ; str += addpos('Begin',segments[0].data[0].pos) ; str += addpos('End',segments[0].data[clen-1].pos) ; str += ' <Intensity>Active</Intensity>\n' + ' </Lap>\n <Track>\n' ; // loop over trackpoints for(npix=i=0;i<segments[0].data.length;i++) { str += ' <Trackpoint>\n' + addpos('',segments[0].data[i].pos) ; str += adddist(distance[i]) + addalt(segments[0].data[i].h) ; time = new Date(msecs[i]) ; str += ' <Time>' + time.toISOString() + '</Time>\n' ; if(segments[0].data[i].photo.length>0) { str += ' <Extensions><Photo>' ; for(k=0;k<segments[0].data[i].photo.length;k++) { if(k) str += ' ' ; str += segments[0].data[i].photo[k] ; } str += '</Photo></Extensions>\n' ; npix += segments[0].data[i].photo.length ; } if(valid[i]==0) str += ' <Extensions><ValidTime>False</ValidTime></Extensions>\n' ; str += ' <SensorState>Absent</SensorState>\n </Trackpoint>\n' ; } str += ' </Track>\n\n' ; // record optimisation and photo list for(ano=-1,ndel=origlen=0,i=loadno;i>=0;i=actions[i][3]) { ndel += actions[i][4].optim.ndel ; origlen += actions[i][4].optim.origlen ; if(actions[i][4].optim.parms!=null&&ano==-1) ano = i ; } if(ndel||(npix>0&&imginfo.status=='ready')) str += ' <Extensions>\n' ; if(ndel) { str += ' <Optimised from="'+origlen+'" to="'+(origlen-ndel) ; if(ano>=0) str += '" tol="'+actions[ano][4].optim.parms.tol.toFixed(0)+ '" maxsep="'+actions[ano][4].optim.parms.maxsep.toFixed(0)+ '" wppenalty="'+actions[ano][4].optim.parms.wppenalty.toFixed(0)+ '" vweight="'+actions[ano][4].optim.parms.vweight.toFixed(1) ; str += '"/>\n' } if(npix>0&&imginfo.status=='ready') { thisuri = imginfo.uri ; if(imginfo.type!='tcx') // vice 'uri' { thisuri = document.URL ; if((i=thisuri.lastIndexOf('?'))>=0) thisuri = thisuri.substring(0,i) ; thisuri = reluri(thisuri,imginfo.uri) ; } str += ' <PhotoList src="'+thisuri+'"/>\n' ; } if(ndel||(npix>0&&imginfo.status=='ready')) str += ' </Extensions>\n\n' ; // finally loop over coursepoints for(i=0;i<segments[0].data.length;i++) { datum = segments[0].data[i] ; if(datum.type==null) continue ; str += ' <CoursePoint><Name>'+datum.marker.title+'</Name>\n' ; str += ' <PointType>'+datum.type+'</PointType>\n' ; str += addpos('',segments[0].data[i].pos) + addalt(segments[0].data[i].h) ; time = new Date(msecs[i]) ; str += ' <Time>' + time.toISOString() + '</Time>\n </CoursePoint>\n' ; } str += '</Course></Courses></TrainingCenterDatabase>\n' ; blob = new Blob([str],{type: "text/plain;charset=utf-8"}) ; saveAs(blob,filename) ; } /* -------------------------------------------------------------------------- */ function addpos(tag,pos) { var str = ' <'+tag+'Position>\n <LatitudeDegrees>' ; str += pos.lat().toFixed(5)+'</LatitudeDegrees>\n <LongitudeDegrees>' ; str += pos.lng().toFixed(5)+'</LongitudeDegrees>\n </'+tag+'Position>\n' ; return str ; } function addalt(x) { return ' <AltitudeMeters>' + x.toFixed(0) + '</AltitudeMeters>\n' ; } function adddist(x) { return ' <DistanceMeters>' + x.toFixed(0) + '</DistanceMeters>\n' ; } /* ------------------------------ interpolate ------------------------------- */ function interpolate() { infowindow.close() ; var response = interpolatework() ; if(response.length>0) done([ 'interpolate' , response ]) ; } function interpolatework() { var i,j,k,response=[],x,y,distance,sum ; var a=segments[0].data,len=a.length ; for(i=0;i<len;i=j) { for(;i<len&&a[i].h!=null;i++) ; // advance to null for(j=i+1;j<len&&a[j].h==null;j++) response.push(j) ; // advance to non-null if(i==0) { for(y=a[j].h;i<j;i++) a[i].h = y ; continue ; } if(j==len) { for(x=a[i-1].h;i<j;i++) a[i].h = x ; continue ; } distance = new Array(1+j-i) ; for(sum=k=0;k<=j-i;k++) sum = distance[k] = sum + dist(a[i+k-1].pos,a[i+k].pos) ; for(x=a[i-1].h,y=a[j].h,k=0;k<j-i;k++) a[i+k].h = ( x*(sum-distance[i]) + y*distance[i] ) / sum ; } } /* -------------------------------------------------------------------------- */

Archived from pix.html

// www.masterlyinactivity.com/software/pix.html // var pixlib = 1 ; /* -------------------------------------------------------------------------- */ function enterfullscreen() { if(document.documentElement.requestFullscreen) document.documentElement.requestFullscreen() ; else if(document.documentElement.mozRequestFullScreen) document.documentElement.mozRequestFullScreen() ; else if(document.documentElement.webkitRequestFullscreen) document.documentElement.webkitRequestFullscreen() ; else if(document.documentElement.msRequestFullscreen) document.documentElement.msRequestFullscreen() ; } function queryfullscreen() { if(document.fullScreen||document.mozFullScreen||document.webkitIsFullScreen) return 1 ; else return 0 ; } function querycanfullscreen() { if ( document.documentElement.requestFullscreen || document.documentElement.mozRequestFullScreen || document.documentElement.webkitRequestFullscreen || document.documentElement.msRequestFullscreen ) return 1 ; else return 0 ; } /* -------------------------------------------------------------------------- */ function thumbind(sizes) { var ind ; for(ind=0;ind<sizes.length&&sizes[ind].type!='thumb';ind++) ; if(ind==sizes.length) { alert('no sizes entry of type "thumb"') ; throw '' ; } return ind ; } /* -------------------------------------------------------------------------- */ function retlink(list,ind) { var i,k,blink ; for(i=ind;i>=0&&list[i].retpage==undefined;i--) ; if(i<0) return null ; ; blink = list[i].retpage+'.html' ; for(k=ind;k>=i&&list[k].retid==undefined;k--) ; if(k>=i) blink += '#' + list[k].retid ; return blink ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ---- fill in missing thumbs and raws, return maximum thumb dimensions ---- */ function setthumbshape(list,sizes,thumbshape,imagedir,hithumb) { var ind,r0,r1,maxthumb,k,umax,doraw ; if(typeof thumbshape=='undefined'||thumbshape==undefined) thumbshape = null ; if(typeof imagedir=='undefined'||imagedir==undefined) imagedir = null ; if(typeof hithumb=='undefined'||hithumb==undefined) hithumb = null ; ind = thumbind(sizes) ; if(thumbshape==null||thumbshape.length!=2) { if(sizes[ind].scale>0) r0 = sizes[ind].scale / sizes[0].scale ; else { alert('thumbs have size '+sizes[ind].scale) ; throw '' ; } } for(doraw=r1=ind=0;ind<sizes.length&&sizes[ind].type!='raw';ind++) ; if(ind<sizes.length) { r1 = sizes[ind].scale / sizes[0].scale ; doraw = 1 ; } for(maxthumb=[0,0],ind=0;ind<list.length;ind++) if(list[ind].name!=undefined) { if(list[ind].thumbshape==undefined) { if(thumbshape!=null&&thumbshape.length==2) list[ind].thumbshape = thumbshape ; else for(list[ind].thumbshape=[0,0],k=0;k<2;k++) list[ind].thumbshape[k] = Math.floor(0.5+list[ind].shape[k]*r0) ; } else if(list[ind].hithumb==undefined) list[ind].hithumb = hithumb ; for(k=0;k<2;k++) if(list[ind].thumbshape[k]>maxthumb[k]) maxthumb[k] = list[ind].thumbshape[k] ; if(doraw&&list[ind].rawshape==undefined) for(list[ind].rawshape=[0,0],k=0;k<2;k++) list[ind].rawshape[k] = Math.floor(0.5+list[ind].shape[k]*r1) ; if(imagedir==null) list[ind].filename = list[ind].name ; else list[ind].filename = imagedir + '/' + list[ind].name ; } // fill in the fontsize field in sizes for(R=k=ind=0;ind<sizes.length;ind++) if(sizes[ind].type==undefined) { R += Math.log(sizes[ind].scale) ; k += 1 ; } R /= k ; for(ind=0;ind<sizes.length;ind++) if(sizes[ind].type==undefined&&sizes[ind].fontsize==undefined) sizes[ind].fontsize = 16 * Math.exp((Math.log(sizes[ind].scale)-R)/3) ; for(umax=null,ind=0;ind<sizes.length;ind++) if(sizes[ind].type==undefined) if(umax==null||sizes[ind].fontsize>umax) umax = sizes[ind].fontsize ; for(ind=0;ind<sizes.length;ind++) if(sizes[ind].type=='raw'&&sizes[ind].fontsize==undefined) sizes[ind].fontsize = umax ; return maxthumb ; } /* ----------------------------- image functions ---------------------------- */ function imgshape(item,sizes,sizeno) { if(sizes[sizeno].type=='thumb') return item.thumbshape ; else if(sizes[sizeno].type=='raw') return item.rawshape ; return [ Math.floor(0.5+item.shape[0]*sizes[sizeno].scale/sizes[0].scale) , Math.floor(0.5+item.shape[1]*sizes[sizeno].scale/sizes[0].scale) ] ; } function imgsize(item,sizes,sizeno) { return imgshape(item,sizes,sizeno) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ----- sparepix finds the margins left if item is displayed at usesize ---- */ function sparepix(item,sizes,sizeno) { var shape=imgsize(item,sizes,sizeno),w=shape[0],h=shape[1],r ; h += Math.floor(0.5+1.25*sizes[sizeno].fontsize) + 2 ; if(item.caption!=undefined) h += Math.floor(0.5+sizes[sizeno].fontsize) + 2 ; r = [ window.innerHeight-50-h , window.innerHeight-h ] ; if(window.innerWidth-w<r[0]) r[0] = window.innerWidth-w ; if(window.innerWidth-w-50<r[1]) r[1] = window.innerWidth-w-50 ; return r ; } /* ---------------------- construct the jpg path name ----------------------- */ function jpg(item,sizes,sizeno) { return item.filename + sizes[sizeno].suffix + '.jpg' ; } /* ------------------- generate the srcset for an image --------------------- */ function srcset(item,sizes,sizeno) { var i,s="",hith,scale=sizes[sizeno].scale ; if(sizes[sizeno].type=='raw') return '' ; if(sizes[sizeno].type=='thumb') { hith = sizes[sizeno].hithumb ; if(hith!=undefined&&hith!=null ) return item.filename + hith.suffix + '.jpg ' + hith.scale + 'x, ' ; if( item.thumbshape[0]*item.shape[1] != item.thumbshape[1]*item.shape[0] ) return '' ; scale *= item.thumbshape[0] / item.shape[0] ; } for(i=0;i<sizes.length;i++) if(sizes[i].type==undefined) if(sizes[i].scale>scale) s += jpg(item,sizes,i) + ' ' + (sizes[i].scale/scale).toFixed(1) + "x, " ; if(s!='') s = s.substring(0,s.length-2) ; return s ; } /* -------------------------------------------------------------------------- */ function preload(item,sizes,sizeno,loadaction) { return genimage(item,sizes,sizeno,loadaction) ; } /* ------- getsize finds the largest image size which fits the screen ------- */ function getsize(item,sizes,loadstatus,thresh) { var i,ibest,ismall,spare ; for(ismall=ibest=null,i=0;i<sizes.length;i++) if(sizes[i].type==undefined&&(loadstatus==undefined||loadstatus[i]>=thresh)) { if(ismall==null||sizes[i].scale<sizes[ismall].scale) ismall = i ; spare = sparepix(item,sizes,i) ; if(spare[0]>=0||spare[1]>=0) if(ibest==null||sizes[i].scale>sizes[ibest].scale) ibest = i ; } if(ibest==null) return ismall ; else return ibest ; } /* -------------------------------------------------------------------------- */
/*page*/ /* --------------------------------- genimage ------------------------------- */ function genimage(item,sizes,sizeno,loadfunc) { var img=document.createElement('img') , shape , s ; if(sizeno==undefined||sizeno==null||sizeno<0) sizeno = getsize(item,sizes) ; shape = imgshape(item,sizes,sizeno) ; img.setAttribute('width',shape[0]) ; img.setAttribute('height',shape[1]) ; if((s=srcset(item,sizes,sizeno))!='') img.setAttribute("srcset",s) ; if(loadfunc!=undefined&&loadfunc!=null) img.onload = loadfunc ; img.setAttribute("src",jpg(item,sizes,sizeno)) ; // console.log(img.src+(s==""?"":' ['+s+']')) ; return img ; } /* ------- setimgpos sets the image position according to window size ------- */ function setimgpos(parms,shape) { var imgw=shape[0],imgh=shape[1],x,y,s,overflowx,overflowy ; var pad=parms.headh + parms.caph ; // set space to the amount of room we've got for the image div var space = [ window.innerWidth , window.innerHeight-pad ] ; if(parms.portrait) space[0] -= 50 ; else space[1] -= 50 ; // set imgw and imgh to the desired dimensions of the image div if(imgh>space[1]) imgw += 20 ; // generous scrollbar if(imgw>space[0]) imgh += 20 ; // set (x,y) to the coords of the main div x = (window.innerWidth-imgw)/2 ; y = (window.innerHeight-imgh)/2 - parms.headh; if(parms.portrait) { if(x<50) x = 50 ; if(y<0) y = 0 ; } else { if(x<0) x = 0 ; if(y<50) y = 50 ; } x = Math.floor(x) ; y = Math.floor(y) ; // now constrain imgw, imgh by the window size if(imgw>window.innerWidth-x) imgw = window.innerWidth - x ; if(imgh>window.innerHeight-y-pad) imgh = window.innerHeight-y-pad ; s = 'position:absolute;left:'+x+'px;width:'+(window.innerWidth-x)+'px;top:' ; s += y+'px;height:'+(pad+imgh)+'px;overflow:hidden' ; parms.maindiv.setAttribute('style',s) ; s = 'font-family:arial;color:silver;position:absolute;' ; s += 'left:0;width:'+(window.innerWidth-x)+'px;top:0;' ; s += 'height:'+parms.headh+'px;overflow:hidden;font-size:'+parms.headf+'px' ; parms.headdiv.setAttribute('style',s) ; if(parms.capdiv!=null) { s = 'font-family:arial;color:silver;position:absolute;' ; s += 'left:0;width:'+(window.innerWidth-x)+'px;bottom:0;' ; s += 'height:'+parms.caph+'px;overflow:hidden;font-size:'+parms.capf+'px' ; parms.capdiv.setAttribute('style',s) ; } if(imgw<shape[0]) overflowx = 'scroll' ; else overflowx = 'hidden' ; if(imgh<shape[1]) overflowy = 'scroll' ; else overflowy = 'hidden' ; s = 'position:absolute;left:0;width:'+imgw+'px;top:'+parms.headh+'px;' ; s += 'height:'+imgh+'px;overflow-x:'+overflowx+';overflow-y:'+overflowy ; parms.imgdiv.setAttribute('style',s) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------- find the list ind with a given name ----------------- */ function findimage(list,name) { var ind ; for(ind=0;ind<list.length;ind++) if(list[ind].name==name) return ind ; return null ; } /* ---- adjust element attributes in accordance with a change of img size --- */ function setimg(parms,item,sizes,sizeno,fetchitem,preloadstatus) { var font,spare,shape,img,s,i,fetcher ; if(sizeno==null) return ; enredcell(parms.enlink,sizes,sizeno,2) ; enredcell(parms.redlink,sizes,sizeno,-1) ; spare = sparepix(item,sizes,sizeno) ; shape = imgsize(item,sizes,sizeno) ; font = sizes[sizeno].fontsize ; parms.headh = 2 + Math.floor(0.5+1.25*font) ; parms.headf = Math.floor(0.5+font) ; if(item.caption==undefined) parms.caph = parms.capf = 0 ; else { parms.caph = 2+Math.floor(0.5+font) ; parms.capf = Math.floor(0.5+0.8*font) ; } parms.portrait = spare[0]<spare[1] ; // create the image for the new size fetcher = null ; if(fetchitem!=undefined&&fetchitem!=null) if(preloadstatus[i=getsize(fetchitem,sizes)]==0) { preloadstatus[i] = 1 ; fetcher = function() { genimage(fetchitem,sizes,i) ; } ; } img = genimage(item,sizes,sizeno,fetcher) ; if(parms.img==null) parms.imgdiv.appendChild(img) ; else parms.imgdiv.replaceChild(img,parms.img) ; parms.img = img ; setimgpos(parms,shape) ; return sizeno ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ------------ rescale finds the next larger/next smaller image ------------ */ function rescale(sizes,sizeno,dir) { var i,ind ; if(dir<0) { for(ind=null,i=0;i<sizes.length;i++) if(sizes[i].type==undefined) if(sizes[i].scale<sizes[sizeno].scale||sizes[sizeno].type=='raw') if(ind==null||sizes[i].scale>sizes[ind].scale) ind = i ; return ind ; } if(sizes[sizeno].type=='raw') return null ; for(ind=null,i=0;i<sizes.length;i++) if(sizes[i].type==undefined||(sizes[i].type=='raw'&&dir==2)) if( sizes[i].scale>sizes[sizeno].scale || sizes[i].type=='raw' ) { if(ind==null) ind = i ; else if(sizes[ind].type=='raw') ind = i ; else if(sizes[i].scale<sizes[ind].scale&&sizes[i].type!='raw') ind = i ; } return ind ; } /* --------------- reposition image in response to a window resize ---------- */ function resizesub(iparms,dparms) { var k2,k0,dir,flag=0,shape,fetch,item=iparms.item,sizes=iparms.sizes ; var sizeno=iparms.sizeno ; k2 = getsize(item,sizes,iparms.loadstatus,2) ; k0 = getsize(item,sizes) ; // there's a better size already loaded if(iparms.holdsize==0&&k2!=sizeno) { setimg(dparms,item,sizes,k2,iparms.fetchitem,iparms.preloadstatus) ; iparms.sizeno = k2 ; return ; } // there's a better size not yet loaded else if(iparms.holdsize==0&&k0!=sizeno&&iparms.loadstatus[k0]==0) flag = 1 ; else if(iparms.holdsize!=0&&k0==sizeno) iparms.holdsize = 0 ; else if(iparms.holdsize==0&&iparms.window!=null) { if( window.innerWidth>=iparms.window[0] && window.innerHeight>=iparms.window[1] ) dir = 1 ; else if( window.innerWidth<=iparms.window[0] && window.innerHeight<=iparms.window[1] ) dir = -1 ; else dir = null ; if(dir!=null&&(k0=rescale(sizes,sizeno,dir))!=null) if(iparms.loadstatus[k0]==0) flag = 1 ; } if(flag) { iparms.window = null ; iparms.loadstatus[k0] = 1 ; genimage(item,sizes,k0,genloadhandler(iparms.loadstatus,k0)) ; } // redisplay if portrait<->landscape makes a fit possible, else redraw shape = imgsize(item,sizes,sizeno) ; spare = sparepix(item,sizes,sizeno) ; if(dparms.portrait!=(spare[0]<spare[1])&&(spare[0]<0)!=(spare[1]<0)) dparms.portrait = 1-dparms.portrait ; setimgpos(dparms,imgsize(item,sizes,sizeno)) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ---------------- create a div containing an image title ------------------ */ function maketextdiv(title) { var div=document.createElement('div') ; div.appendChild(document.createTextNode(title)) ; div.setAttribute('style','font-family:arial;color:silver') ; return div ; } /* --------------------- create/reset enlarge/reduce icon ------------------- */ function enredcell(td,sizes,sizeno,dir) { var a,astyle ; astyle = 'font-family:arial;font-weight:normal;text-decoration:none;' ; while(td.firstChild) td.removeChild(td.firstChild) ; if(rescale(sizes,sizeno,dir)!=null) { a = document.createElement('a') ; a.setAttribute('href',dir>0?'javascript:enlarge()':'javascript:reduce()') ; a.setAttribute("title",dir>0?"enlarge [\u2191 key]":"reduce [\u2193 key]") ; a.setAttribute("style",astyle) ; a.appendChild(document.createTextNode(dir>0?'\u2295':'\u2296')) ; td.appendChild(a) ; } } /* --------------------- create a cell for navigation icon ------------------ */ function navcell(link,dir,string) { var a,td=document.createElement('td'),s='center',astyle ; if(dir=='l') s = 'left' ; else if (dir=='r') s = 'right' ; astyle = 'font-family:arial;font-weight:normal;text-decoration:none;' ; s = "text-align:" + s + (dir==0?'':';font-size:20px;') ; s += 'text-align:center;vertical-align:middle;font-size:16px;' + 'line-height:16px;width:16px;height:16px' ; td.setAttribute("style",s) ; if(dir!=0) td.setAttribute("rowspan",3) ; if(link!=null) { a = document.createElement('a') ; a.setAttribute('href',link) ; if(dir>0) s = "next [\u2192 key]" ; else if(dir==0) s = "back to " + string + " [\u21b5 key]" ; else s = "prev [\u2190 key]" ; a.setAttribute("title",s) ; a.setAttribute("style",astyle) ; if(dir>0) s = '>' ; else if(dir==0) s = '\u21b5' ; else s = '<' ; a.appendChild(document.createTextNode(s)) ; td.appendChild(a) ; } return td ; } /* -------------------------------------------------------------------------- */ var maxthumb,thumbshape,imagedir,hithumb,thind=null,sfx=null ; function thumb(name) { var ind,s ; if(maxthumb==undefined||maxthumb==null) maxthumb = setthumbshape(list,sizes,thumbshape,imagedir,hithumb) ; if(thind==null) thind = thumbind(sizes) ; if(sfx==null) sfx = sizes[thind].suffix ; for(ind=0;ind<list.length;ind++) if(list[ind].name==name) { s = srcset(list[ind],sizes,thind) ; document.write('<a href="'+pixpage+'?'+name+'+n"><img class=pix src="'+ (imagedir==null?"":(imagedir+'/'))+name+sfx+'.jpg" width='+ list[ind].thumbshape[0]+' height='+list[ind].thumbshape[1]+ ' title="'+list[ind].title+ ((srcset==''||srcset==null)?'':('" srcset="'+s)) + '"></a>') ; return ; } alert('Missing image: '+name) ; } /* -------------------------------------------------------------------------- */ var drawparms=null,itemparms=null ; function setsize(k) { if(itemparms==null||(k=rescale(itemparms.sizes,itemparms.sizeno,k))==null) return ; itemparms.sizeno = setimg(drawparms,itemparms.item,itemparms.sizes,k) ; itemparms.holdsize = 1 ; } function genloadhandler(loadstatus,k) { return function() { if(itemparms!=null) { loadstatus[k] = 2 ; resize() ; } } ; } function resize() { if(itemparms!=null) resizesub(itemparms,drawparms) ; } function enlarge() { setsize(2) ; } function reduce() { setsize(-1) ; } /* -------------------------------------------------------------------------- */ function gendisplay(element,item,sizes,llink,blink,rlink,fromstring,fetchitem) { var i,sizeno,captioned,table,tr,td,a,utd,dtd ; if(element==undefined||element==null) { itemparms = drawparms = null ; return ; } sizeno = getsize(item,sizes) ; if(item.caption==undefined) captioned = 0 ; else captioned = 1 ; itemparms = { item: item, sizes: sizes, sizeno: sizeno, holdsize: 0, loadstatus: new Array(sizes.length), fetchitem: fetchitem, preloadstatus: new Array(sizes.length), window: [ window.innerWidth , window.innerHeight ] } ; for(i=0;i<sizes.length;i++) itemparms.loadstatus[i] = itemparms.preloadstatus[i] = 0 ; itemparms.loadstatus[sizeno] = 2 ; // make the navigation table table = document.createElement('table') ; table.setAttribute('cellpadding',0) ; table.setAttribute('cellspacing',0) ; // the '<' link tr = document.createElement('tr') ; tr.appendChild(navcell(llink,-1)) ; // the enlarge link utd = document.createElement('td') ; utd.setAttribute("style",'text-align:center;font-size:16px;' + 'line-height:16px;width:16px;height:16px') ; tr.appendChild(utd) ; table.appendChild(tr) ; // the '>' link tr.appendChild(navcell(rlink,1)) ; table.appendChild(tr) ; // the return link tr = document.createElement('tr') ; tr.appendChild(navcell(blink,0,fromstring)) ; table.appendChild(tr) ; // the reduce link tr = document.createElement('tr') ; dtd = document.createElement('td') ; dtd.setAttribute("style",'text-align:center;font-size:16px;' + 'line-height:16px;width:16px;height:16px') ; tr.appendChild(dtd) ; table.appendChild(tr) ; drawparms = { headh: null, caph: null, headf: null, capf: null, enlink: utd, redlink: dtd, img: null, portrait: null, maindiv: document.createElement('div'), headdiv: maketextdiv(item.title), imgdiv: document.createElement('div'), capdiv: captioned==0?null:maketextdiv(item.caption) } ; setimg(drawparms,item,sizes,sizeno,fetchitem,itemparms.preloadstatus) ; drawparms.imgdiv.appendChild(drawparms.img) ; drawparms.maindiv.appendChild(drawparms.headdiv) ; drawparms.maindiv.appendChild(drawparms.imgdiv) ; if(captioned) drawparms.maindiv.appendChild(drawparms.capdiv) ; while(element.firstChild) element.removeChild(element.firstChild) ; element.appendChild(table) ; element.appendChild(drawparms.maindiv) ; } /* -------------------------------------------------------------------------- */

Archived from pix.html

var ind,r,k,sfx ; // prepare to set the thumb shapes for(ind=0;ind<sizes.length&&sizes[ind].type!='thumb';ind++) ; if(ind==sizes.length) { alert('no "sizes" entry of type "thumb"') ; throw '' ; } sfx = sizes[ind].suffix ; if(typeof thumbshape=="undefined"||thumbshape.length!=2) { if(sizes[ind].scale>0) r = sizes[ind].scale / sizes[0].scale ; else { alert('thumbs have size '+sizes[ind].scale) ; throw '' ; } thumbshape = [] ; } // set thumbshapes and set maxthumb to the width of the widest thumbnail for(ind=0;ind<list.length;ind++) if(list[ind].name!=undefined) { if(list[ind].thumbshape==undefined) { if(thumbshape.length==2) list[ind].thumbshape = thumbshape ; else for(list[ind].thumbshape=[0,0],k=0;k<2;k++) list[ind].thumbshape[k] = Math.floor(0.5+list[ind].shape[k]*r) ; } } function thumb(name) { var ind ; for(ind=0;ind<list.length;ind++) if(list[ind].name==name) { document.write('<a href="'+pixpage+'?'+name+'+n"><img class=pix src="'+ (imagedir==null?"":(imagedir+'/'))+name+sfx+'.jpg" width='+ list[ind].thumbshape[0]+' height='+list[ind].thumbshape[1]+ ' title="'+list[ind].title+'"></a>') ; return ; } alert('No image: '+name) ; }

Archived from pix.html

// www.masterlyinactivity.com/software/pix.html // document.write( '<style>a:link{color:#66aaaa}' + 'a:visited{color:#cc3388}a:active{color:#404040}</style>' ) ; if((typeof pixlib)=="undefined") document.write ('<scri'+'pt src="http://www.masterlyinactivity.com/pixlib.js"></scri'+'pt>'); var thispage,full=0,nload=0,here,lind,rind,blink,pagetitle,retpage ; var nitem=[],thumbshape=[],maxthumb,imagedir=null ; var llink,rlink,ulink,dlink,prevind,fromnotes,fullname ; /* -------------------------------------------------------------------------- */ // pixresize responds to a window resize as follows: // o. update 'full' if necessary // o. for a table view, if the number of columns can change, change it; // o. for an image view, if the new size permits a larger or smaller image, // enlarge or reduce; // o. or if the window is expanding or contracting but hasn't changed size // enough to change the desired image, anticipatively preload a larger or // smaller one. function pixresize() { if((full=queryfullscreen())==0&&here.full!=0&&fullname!=here.name) location.href = thispage + '?' + here.name + (fromnotes?'+n':'') ; if(here.name!=null) resize() else if(full!=here.full||here.ncol!=(k=getncol())) tabulate() ; } /* ------------------- construct a link to an image page -------------------- */ function piclink(ind) { if(full) return 'javascript:display("' + list[ind].name + '")' ; else return thispage + '?' + list[ind].name + (fromnotes?'+n':'') ; } /* ----- getncol finds the number of table columns which fit the screen ----- */ function getncol() { var maxcol,ncol,k,ind ; // 17 pix for scrollbar, 19 pix for margin, border... maxcol = Math.floor((window.innerWidth-17)/(maxthumb[0]+19)) ; if(maxcol<1) maxcol = 1 ; for(ncol=ind=0;ind<nitem.length;ind++) { k = Math.ceil(nitem[ind]/Math.ceil(nitem[ind]/maxcol)) ; if(k>ncol) ncol = k ; } return ncol ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------- create links line of the table page ----------------- */ function linkp() { var p,a,span,astyle,i,flag=0 ; astyle = 'font-family:arial;font-weight:normal;text-decoration:none;' ; p = document.createElement("p") ; p.setAttribute('style',"text-align:center;font-size:100%;margin:6px;"+ "font-family:arial;color:silver") ; if(retpage!=null) { a = document.createElement('a') ; a.setAttribute('href',list[retpage].retpage+'.html') ; a.setAttribute('style',astyle) ; a.appendChild(document.createTextNode('notes')) ; p.appendChild(a) ; flag = 1 ; } if(links!=null) for(i=0;i<links.length;flag=1,i++) { if(flag) p.appendChild(document.createTextNode(' : ')) ; a = document.createElement('a') ; a.setAttribute('href',links[i].href) ; a.setAttribute('style',astyle) ; a.appendChild(document.createTextNode(links[i].name)) ; p.appendChild(a) ; } if(full==0&&querycanfullscreen()) { if(flag) p.appendChild(document.createTextNode(' : ')) ; a = document.createElement('a') ; a.setAttribute('style',astyle) ; a.setAttribute('href','javascript:{}') ; a.setAttribute('onclick','enterfullscreen()') ; a.appendChild(document.createTextNode('full screen')) ; p.appendChild(a) ; span = document.createElement('span') ; span.setAttribute('style',"color:gray") ; span.appendChild(document.createTextNode(' [f key] ')) ; p.appendChild(span) ; } return p ; } /* -------------------------------------------------------------------------- */ function display(name) { var titlestr,body,title,i,k,fetchitem,s,ind=findimage(list,name) ; body = document.getElementsByTagName("body")[0] ; if(name!=here.name) { title = document.getElementsByTagName("title")[0] ; while(title.childNodes.length>1) title.removeChild(title.firstChild) ; titlestr = document.createTextNode(list[ind].title) if(title.childNodes.length==0) title.appendChild(titlestr) ; else title.replaceChild(titlestr,title.childNodes[0]) ; } here = { name:name , ind:ind , ncol:0 , full:full } ; // navigation links: < for(lind=ind-1; lind>=0&&(list[lind].name==undefined||list[lind].display=='none'); lind--) ; if(lind<0) llink = null ; else llink = piclink(lind) ; // navigation links: > for(rind=ind+1; rind<list.length && (list[rind].name==undefined||list[rind].display=='none'); rind++) ; if(rind==list.length) rlink = null ; else rlink = piclink(rind) ; // navigation links: return if(fromnotes==0) { if(full) blink = 'javascript:tabulate()' ; else blink = thispage ; s = "table" ; } else { for(i=ind;i>=0&&list[i].retpage==undefined;i--) ; if(i<0) alert('no return page for '+list[ind].name) ; blink = list[i].retpage+'.html' ; for(k=ind;k>=i&&list[k].retid==undefined;k--) ; if(k>=i) blink += '#' + list[k].retid ; s = "notes" ; } // decide which image if any to prefetch if(prevind!=null&&prevind>ind&&lind>=0) k = lind ; else if((prevind==null||prevind<=ind)&&rind<list.length) k = rind ; else k = null ; if(k==null) fetchitem = null ; else fetchitem = list[k] ; gendisplay(body,list[ind],sizes,llink,blink,rlink,s,fetchitem) ; } /* ------------------------ navigate using the arrow keys ------------------- */ function navigate(e) { if(e.keyCode==37&&lind>=0) // left arrow key { e.preventDefault() ; if(full==0) location.href= llink ; else { prevind = lind+1 ; display(list[lind].name) ; } } else if(e.keyCode==39&&rind<list.length) // right arrow key { e.preventDefault() ; if(full==0) location.href= rlink ; else { prevind = rind-1 ; display(list[rind].name) ; } } else if(e.keyCode==13&&here.ncol==0) // return { e.preventDefault() ; if(full&&fromnotes==0) tabulate() ; else location.href = blink ; } else if(e.keyCode==40&&here.ncol==0) { e.preventDefault() ; reduce() ; } else if(e.keyCode==38&&here.ncol==0) { e.preventDefault() ; enlarge() ; } else if(e.keyCode==70&&full==0) // 'f' (full screen) { e.preventDefault() ; fullname = here.name ; enterfullscreen() ; full = 1 ; } } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------------------------------------------------- */ function tabulate() { var ind,ncol,p,body,table,tr,td,a,img,trflag,k,s,opts,thind=thumbind(sizes) ; var prefetch,nimg,padflag,k,num ; gendisplay() ; rind = list.length ; lind = -1 ; body = document.getElementsByTagName("body")[0] ; while(body.firstChild) body.removeChild(body.firstChild) ; ncol = getncol() ; here = { name:null , ind:null , ncol:ncol , full:full } ; // prefetch will be performed when nimg thumbs have been loaded for(nimg=ind=0;ind<list.length;ind++) if(list[ind].name!=undefined&&list[ind].display!='none') nimg += 1 ; p = document.createElement("p") ; p.setAttribute('style',"text-align:center;font-size:140%;margin:2px 0 6px;"+ "font-family:arial;color:silver;") ; p.appendChild(document.createTextNode(pagetitle)) ; body.appendChild(p) ; body.appendChild(linkp()) ; table = document.createElement('table') ; table.setAttribute('cellspacing','0') ; table.setAttribute('cellpadding','0') ; table.setAttribute('align','center') ; for(s='',ind=0;s==''&&ind<list.length;ind++) if(list[ind].name==undefined) s = "border-bottom:1px solid #444;padding-bottom:4px;" ; table.setAttribute('style',s+'margin:0px auto') ; for(prefetch=null,nload=trflag=colno=ind=0;ind<list.length;ind++) if(list[ind].display!='none') { if(0==colno%ncol||list[ind].name==undefined) { if(trflag>0) table.appendChild(tr) ; tr = document.createElement('tr') ; trflag = 0 ; } td = document.createElement('td') ; if(list[ind].name==undefined) { td.setAttribute("style","border-top:1px solid #444") ; td.setAttribute('align','left') ; td.setAttribute('colspan',ncol) ; p = document.createElement("p") ; p.setAttribute("style","font-size:110%;padding-top:6px;"+ "font-family:arial;color:silver") ; if(list[ind].gps==undefined) p.appendChild(document.createTextNode(list[ind].title)) ; else { p.appendChild(document.createTextNode(list[ind].title+' : ')) ; a = document.createElement('a') ; a.setAttribute('href',list[ind].gps) ; a.setAttribute('style','font-size:90%;text-decoration:none') ; a.appendChild(document.createTextNode('[GPS track]')) ; p.appendChild(a) ; } td.appendChild(p) ; tr.appendChild(td) ; table.appendChild(tr) ; tr = document.createElement('tr') ; colno = trflag = 0 ; continue ; } lind = ind ; llink = piclink(lind) ; if(rind==list.length) { rind = ind ; rlink = piclink(rind) ; } // extra padding at the bottom before a title row if(0==colno%ncol) { padflag = 4 ; for(num=0,k=ind;k<list.length&&num<ncol&&list[k].name!=undefined;k++) if(list[k].display!='none') num += 1 ; if(k<list.length&&num<=ncol) { padflag = 8 ; if(ncol>5) padflag += 2*(ncol-5) ; } } td.setAttribute('align','center') ; if(list[ind].display=='|'&&colno%ncol>0) td.setAttribute('style','border-left:1px solid #444') ; a = document.createElement('a') ; a.setAttribute('href',piclink(ind)) ; a.setAttribute("class","box") ; a.setAttribute("title",list[ind].title) ; img = genimage(list[ind],sizes,thind,function() { nload += 1 ; if(nload==nimg&&prefetch!=null) preload(list[prefetch],sizes) ; } ) ; img.setAttribute('border',1) ; // vertical bar: margin is t-r-b-l or t-lr-b if(colno%ncol>0&&list[ind].display!='|') img.setAttribute('style',"padding:4px;margin:4px 4px "+padflag+"px 5px") ; else img.setAttribute('style',"padding:4px;margin:4px 4px "+padflag+"px") ; a.appendChild(img) ; td.appendChild(a) ; tr.appendChild(td) ; trflag = 1 ; if(prefetch==null) prefetch = ind ; colno += 1 ; } table.appendChild(tr) ; body.appendChild(table) ; body.appendChild(linkp()) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------------------------------------------------- */ function pix(h) { var ind,query,opts="",k,r,prev,fullpage=location.href,body ; thispage = fullpage ; pagetitle = document.getElementsByTagName("title")[0].textContent ; body = document.getElementsByTagName("body")[0] ; body.setAttribute('style','background:black;margin:0') ; if((typeof links)=='undefined') links = null ; if(h!=undefined&&h!=null&&links==null) links = [ { name: 'home' , href: h } ] ; prevind = null ; here = { name:null , ind:null , ncol:0 , full:0 } ; if((ind=thispage.lastIndexOf('/'))>=0) thispage = thispage.substring(ind+1) ; // set up the array nitem containing the number of items in each block for(nitem=[],ind=-1;ind<list.length;ind=k) { for(k=ind+1;k<list.length&&list[k].name!=undefined;k++) ; if(k>ind+1) nitem.push(k-(ind+1)) ; } maxthumb = setthumbshape(list,sizes,thumbshape,imagedir) ; if((ind=thispage.indexOf('?'))>=0) { query = thispage.substring(ind+1) ; // 'garden+n' thispage = thispage.substring(0,ind) ; fullpage = fullpage.substring(0,fullpage.length-1-query.length) ; if((ind=query.indexOf('+'))>=0) { opts = query.substring(ind+1) ; query = query.substring(0,ind) ; } for(ind=0;ind<list.length&&(list[ind].name!=query);ind++) ; } else { query = null ; ind = -1 ; } for(k=0; k<list.length&&(list[k].name==undefined||list[k].retpage==undefined); k++) ; if(k<list.length) retpage = k ; else retpage = null ; if(ind>=0&&ind<list.length) // return to table if there is no retpage { // all the following code is finding whether we're stepping backwards if((k=document.referrer.lastIndexOf('/'))>=0) { prev = document.referrer.substring(k+1) ; k = prev.indexOf('?') ; } if(k>=0) { prev = prev.substring(k+1) ; k = prev.indexOf('+') ; if(k>=0) prev = prev.substring(0,k) ; for(k=0;k<list.length&&list[k].name!=prev;k++) ; if(k<list.length) prevind = k ; } else if(fullpage==document.referrer) { for(k=ind+1;k<list.length&&list[ind].name==undefined;k++) ; if(k==list.length) prevind = list.length ; } } window.onresize = pixresize ; document.onkeydown = navigate ; document.addEventListener('touchstart',startswipe,false) ; document.addEventListener('touchmove',midswipe,false) ; document.addEventListener('touchend',endswipe,false) ; if(ind<0||ind>=list.length) { fromnotes = 0 ; tabulate() ; } else { fromnotes = (opts.charAt(0)=='n'&&retpage!=null) ; display(query) ; } } /* -------------------------------------------------------------------------- */
/*page*/ /* ---------------------------------- swipes -------------------------------- */ function simulate(btn) { navigate({keyCode:btn,preventDefault:function(){}}) ; } var xloc=null,yloc=null,xstart=null,ystart=null,fingersep=null,startsep=null ; var swipetime=null ; function startswipe(e) { var x0=e.touches[0].clientX,y0=e.touches[0].clientY,x1,y1 ; if(e.touches.length==1) { xstart = x0 ; ystart = y0 ; swipetime = new Date().getTime() ; } else if(e.touches.length==2&&here.ncol==0) { e.preventDefault() ; // don't let iOS take control of pinches x1 = e.touches[1].clientX ; y1 = e.touches[1].clientY ; startsep = Math.sqrt((x1-x0)*(x1-x0)+(y1-y0)*(y1-y0)) ; } } function midswipe(e) { var x0=e.touches[0].clientX,y0=e.touches[0].clientY,x1,y1 ; if(e.touches.length==1) { xloc = x0 ; yloc = y0 ; fingersep = startsep = null ; } else if(e.touches.length==2&&here.ncol==0) { e.preventDefault() ; x1 = e.touches[1].clientX ; y1 = e.touches[1].clientY ; fingersep = Math.sqrt((x1-x0)*(x1-x0)+(y1-y0)*(y1-y0)) ; xloc = yloc = xstart = ystart = swipetime = null ; } } function endswipe(e) { var v ; if(fingersep!=null&&startsep!=null&&here.ncol==0) { e.preventDefault() ; if(fingersep>startsep+50) simulate(38) ; else if(startsep>fingersep+50) simulate(40) ; } else if(xloc!=null&&yloc!=null&&xstart!=null&&ystart!=null&&swipetime!=null) { swipetime = new Date().getTime() - swipetime ; v = Math.sqrt((xloc-xstart)*(xloc-xstart)+(yloc-ystart)*(yloc-ystart)) ; if(v>0.65*swipetime) { e.preventDefault() ; if(Math.abs(xloc-xstart)>100&&Math.abs(yloc-ystart)<100) { if(xloc>xstart) simulate(37) ; else simulate(39) ; } else if(yloc>ystart+100&&Math.abs(xloc-xstart)<100) simulate(13) ; } } xloc = yloc = xstart = ystart = fingersep = startsep = swipetime = null ; }

Archived from routemaster.html

var segments=[],selected,actions,nactions,sel,dragging,loadno,shift=0 ; var pending,xpending,mouseopt=0,curhandle,elevator,resuri ; var routetitle,body,altdiv,curdiv,curcan,mapdiv,cmap,smap,prox,proa,pron ; var imgdiv,imghandle,imgtable,imginfo,imgind,selinit ; var cursorbtn,scissorsbtn,binbtn,undobtn,redobtn,penbtn,setbtn,dlbtn ; var defparms = {tol:10,maxsep:95,wppenalty:1000,vweight:1} ; var flagsign,turnleft,straighton,turnright,shriek,arrow,concircle,camera ; var neutral='<span style="font-family:helvetica">' ; var active='<span style="cursor:pointer;color:#0000bd" onclick=' ; var inactive='<span style="color:silver">' ; var textbox='<div style="margin-bottom:2px;border-bottom:solid 1px silver;' + 'padding-bottom:2px;font-family:helvetica">' ; var finalbox='<div style="font-family:helvetica">' ; var parser = new DOMParser() ; var map = null , clickhandle = null ; var unsavedchanges = [] ; var infowindow = { handle: null , type: null , open: function(s,pos,type) { this.handle = new google.maps.InfoWindow({content:s,position:pos}) ; this.handle.open(map) ; google.maps.event.addListener(this.handle,'closeclick', function() { infowindow.handle = infowindow.type = null ; } ) ; this.type = type ; } , close: function() { if(this.handle==null) return null ; var response = this.type ; this.handle.close() ; this.handle = this.type = null ; return response ; } } ; /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------------------------------------------------- */ /* CONSTRUCTORS */ /* -------------------------------------------------------------------------- */ function dotpath(a,b) { this.path = [a,b] ; this.cursor = 'default' ; this.geodesic = true ; this.strokeOpacity = 0 ; this.icons = [ { icon: { path: 'M 0 0 L 1 0',strokeOpacity:1,scale:1 } , offset: '1px' , repeat: '4px' } ] ; this.zIndex = 0 ; } /* -------------------------------------------------------------------------- */ function linepath(s0,start,end,colour) { var i,len=(start<0?segments[s0].data.length:end-start) ; this.path = new Array(len) ; if(start<0) for(i=0;i<len;i++) this.path[i] = segments[s0].data[i].pos ; else for(i=0;i<len;i++) this.path[i] = segments[s0].data[start+i].pos ; this.clickable = 'false' ; this.cursor = 'default' ; this.geodesic = true ; this.strokeColor = colour ; this.strokeOpacity = 1.0 ; this.strokeWeight = 2 ; this.zIndex = 0 ; } /* -------------------------------------------------------------------------- */ function listinfo() { this.list = [] ; this.sizes = [] ; this.dir = thumbsfx = null ; this.uri = this.scale = this.status = this.type = this.pixpage = null ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ------------------------------- data structure --------------------------- */ // I found the following logic quite hard to get right. A (non-null) label // satisfies the following constraints: // o. the marker is non-null // o. the map may be null, and if it is null the title may also be null and the // icon may be arbitrary // o. if the type is null, the map is null // o. the map is null if and only if the clickhandler is inactive // the same constraints apply (mutatis mutandis) to the photo, so it follows // that the label may have a null map and the photo non-null (and vice versa) // we therefore conclude that a label must be in one of 3 states: // o. type null, map null, handlers inactive, but marker non-null // o. type non-null, map null, handlers inactive, marker non-null // o. type non-null, map non-null, handlers active, marker non-null // the state in which type is non-null and map is null is applied to all // labels in a segment being deleted (we preserve the information in the // action list but don't want the label to be displayed) function datatype(pos,h) { this.pos = pos ; this.h = h ; this.marker = this.photomarker = this.type = this.t = null ; this.photo = [] ; this.caption = '' ; this.clickhandler = this.righthandler = this.photohandler = null ; } // member functions datatype.prototype.geticon = function() { if(this.type=='Left') return turnleft ; else if(this.type=='Straight') return straighton ; else if(this.type=='Right') return turnright ; else if(this.type=='Danger') return shriek ; else return flagsign ; } ; datatype.prototype.setlabelmap = function(m) { if(m==null||this.type==null) m = null ; else m = map ; if(m==null&&this.marker==null) return ; this.marker.setMap(m) ; if(m==null&&this.clickhandler!=null) { google.maps.event.removeListener(this.clickhandler) ; google.maps.event.removeListener(this.righthandler) ; this.clickhandler = this.righthandler = null ; } if(m!=null&&this.clickhandler==null) { this.clickhandler = this.marker.addListener('click',selpoint) ; this.righthandler = this.marker.addListener('rightclick',labelcycle) ; } } ; datatype.prototype.setphotomap = function(m) { if(m==null||this.photo.length==0) m = null ; else m = map ; if(m==null&&this.photomarker==null) return ; this.photomarker.setMap(m) ; if(m==null&&this.photohandler!=null) { google.maps.event.removeListener(this.photohandler) ; this.photohandler = null ; } if(m!=null&&this.photohandler==null) this.photohandler = this.photomarker.addListener('click',selpoint) ; } ; datatype.prototype.setlabel = function(t,c) { this.type = t ; if(t==null) { if(this.marker!=null) this.setlabelmap(null) ; return ; } if(this.marker==null) this.marker = new google.maps.Marker ({ position:this.pos,map:map,icon:this.geticon(),title:c,zIndex:1 }) ; else { this.marker.setIcon(this.geticon()) ; this.marker.setTitle(c) ; } this.setlabelmap(map) ; } ; datatype.prototype.setphoto = function(ind,p) { var i ; if(p==null) { for(i=ind;i<this.photo.length-1;i++) this.photo[i] = this.photo[i+1] ; this.photo.length -= 1 ; if(this.photo.length==0&&this.photomarker!=null) this.setphotomap(null) ; return ; } else { this.photo[ind] = p ; if(ind==0) this.photomarker.setTitle(p) ; } } ; datatype.prototype.addphoto = function(p) { this.photo.push(p) ; if(this.photomarker==null) this.photomarker = new google.maps.Marker ({ position:this.pos,map:map,icon:camera,title:p,zIndex:1 }) ; this.setphotomap(map) ; } ; datatype.prototype.setpos = function(p) { this.pos = p ; if(this.type!=null) this.marker.setPosition(p) ; if(this.photo.length>0) this.photomarker.setPosition(p) ; } ; datatype.prototype.setmap = function(m) { this.setlabelmap(m) ; this.setphotomap(m) ; } ; datatype.prototype.settype = function(t) { this.type = t ; this.marker.setIcon(this.geticon()) ; } ; /* -------------------------------------------------------------------------- */ /* UTILITY FUNCTIONS */ /* -------------------------------------------------------------------------- */ function xmlfloat(x) { return parseFloat(x.childNodes[0].textContent) ; } function dist(x,y) { return google.maps.geometry.spherical.computeDistanceBetween(x,y) ; } function interp(x,y,lamda) { return google.maps.geometry.spherical.interpolate(x,y,lamda) ; } function bearing(x,y) { return google.maps.geometry.spherical.computeHeading(x,y) ; } function angle(x,y) { return google.maps.geometry.spherical.computeHeading(x,y)*Math.PI/180 ; } function isvaliddate(d) { if(Object.prototype.toString.call(d)!=="[object Date]") return false ; else return !isNaN(d.getTime()) ; } /* --------------------------- button handlers ----------------------------- */ function greyout(btn) { if(btn.active==0) return 0 ; btn.btn.setAttribute('src',btn.greyimg) ; btn.ui.removeEventListener('click',btn.handler) ; if(btn==penbtn) btn.ui.removeEventListener('contextmenu',photoprompt) ; btn.ui.style.cursor = 'default' ; btn.active = 0 ; return 1 ; } function blackout(btn) { if(btn.active) return ; btn.btn.setAttribute('src',btn.blackimg) ; btn.ui.addEventListener('click',btn.handler) ; if(btn==penbtn) btn.ui.addEventListener('contextmenu',photoprompt) ; btn.ui.style.cursor = 'pointer' ; btn.active = 1 ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ------------------------ enter/exit full screen -------------------------- */ // Find the right method, call on correct element function enterFullscreen() { infowindow.close() ; if(document.documentElement.requestFullscreen) document.documentElement.requestFullscreen() ; else if(document.documentElement.mozRequestFullScreen) document.documentElement.mozRequestFullScreen() ; else if(document.documentElement.webkitRequestFullscreen) document.documentElement.webkitRequestFullscreen() ; else if(document.documentElement.msRequestFullscreen) document.documentElement.msRequestFullscreen() ; } function exitFullscreen() { infowindow.close() ; if(document.exitFullscreen) document.exitFullscreen() ; else if(document.mozCancelFullScreen) document.mozCancelFullScreen() ; else if(document.webkitExitFullscreen) document.webkitExitFullscreen() ; } /* ---------------- find the index of a given photo name -------------------- */ function findimg(id) { var i ; for(i=0;i<imginfo.list.length;i++) if(imginfo.list[i].name!=undefined&&imginfo.list[i].name==id) return i ; return -1 ; } /* ------------------- message warning of unsaved changes ------------------- */ function unsavedmsg(ok) { var msg , len = unsavedchanges.length , i ; if(len==0) return null ; msg = 'You have ' + len + ' unsaved change' + (len==1?"":'s') ; if(len<=3) for(i=0;i<len;i++) msg += (i?',':' (') + unsavedchanges[i] + (i==len-1?')':'') ; msg += '\nIf you hit ' + (ok?'[OK]':'[Leave page]') ; return msg + (len==1?' this change':' these changes') + ' will be lost.' ; } /* --------------- selpoint: choose the clicked waypoint ------------------- */ function selpoint(event) { var i,j,closest,d,mindist,s0=selected[0],s1=segments[s0].data.length ; if(dragging) return ; var flag = (infowindow.close()=='wpinfo') && (shift==0) ; if(shift) { insert(s0,s1,1) ; segments[s0].data[s1].setpos(event.latLng) ; lookupalt(s0,s1) ; redrawconnect(s0,s1) ; done(['move',s0,s1,event.latLng,event.latLng,1]) ; } else for(s1=s0=-1,i=0;i<segments.length;i++) for(j=0;j<segments[i].data.length;j++) { d = dist(segments[i].data[j].pos,event.latLng) ; if(s0<0||d<mindist) { s0 = i ; s1 = j ; mindist = d ; } } walkto(s0,s1,flag) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* --------------------------------- walkto --------------------------------- */ // draw a selection point (and possibly an info box) at [s0,s1], bringing up // a wpinfo window if flag != 0 function walkto(s0,s1,flag) { var s='',i,ind,imguri=null,excuse,imgname ; var datum = segments[s0].data[s1] , pos = datum.pos ; selected = [s0,s1] ; map.panToBounds(new google.maps.LatLngBounds(pos,pos)) ; drawsel(0) ; if(flag||(datum.type==null&&datum.photo.length==0)) { if(flag) wpinfo() ; return ; } if(datum.type!=null) { if(datum.photo.length>0) s = textbox ; else s = finalbox ; if(datum.type!='Generic') s += datum.type + ': ' ; s += datum.marker.title + ' [' + active + '"labelprompt()">Edit</span>]' ; s += '</div>' ; } for(ind=0;ind<datum.photo.length;ind++) { s += textbox ; if(imginfo.status=='ready'&&(i=findimg(datum.photo[ind]))>=0) { if(imginfo.dir==null) imguri = imginfo.uri ; else imguri = reluri(imginfo.uri,imginfo.dir) + '/' ; imguri = reluri(imguri,datum.photo[ind]) + imginfo.thumbsfx + '.jpg' ; s += '<img src="' + imguri + '" width=' + imginfo.list[i].thumbshape[0] + ' height=' + imginfo.list[i].thumbshape[1] + '><br>' + '<b>'+datum.photo[ind]+'</b>: ' ; } else { if(imginfo.status=='null') excuse = 'no list provided' ; else if(imginfo.status=='ready') { imgname = imginfo.uri ; i = imgname.lastIndexOf('/') ; if(i>=0) imgname = imgname.substring(i+1) ; excuse = 'not present in ' + imgname ; } else if(imginfo.status=='waiting') excuse = imgname + ' is not available' ; else excuse = 'imginfo.status = ' + imginfo.status ; s += 'Photo: ' + datum.photo[ind] + ' (' + excuse + ') ' ; } s += '['+active+ '"photoedit('+ind+')">Edit</span>'+']' ; if(imguri!=null) s += ' : ['+active+ '"phinfo('+i+')">Info</span>'+']' + ' : ['+active+ '"enlarge('+ind+','+i+')">Enlarge</span>'+']' ; s += '</div>' ; } if(datum.photo.length>0) s += finalbox + '[' + active + '"photoprompt' + '(null)">Add photo</span>' + ']</div>' ; infowindow.open(s,pos,'walking') ; } /* -------------------------- keystroke handler ---------------------------- */ function walk(e) { var s0=selected[0],s1=selected[1],slast,flag ; if(e.keyCode==16) shift = 1 ; if(e.keyCode==40) { map.panTo(segments[s0].data[s1].pos) ; return ; } if(infowindow.close()=='wpinfo') flag = 1 ; else flag = 0 ; if(e.keyCode==32) { selclick() ; return ; } // space if(e.keyCode==13) // return { if(dragging) undraggit() ; else if(s0>=0) draggit(0) ; return ; } if(dragging) return ; if(e.keyCode==8||e.keyCode==46) // delete/backspace { e.preventDefault() ; if(binbtn.active) discard() ; return ; } if(e.keyCode==9) { e.preventDefault() ; inswp(1) ; return ; } // tab if(e.keyCode==39) // forwards { e.preventDefault() ; if(s1<segments[s0].data.length-1) s1 += 1 ; else { s0 += 1 ; if(s0==segments.length) s0 = 0 ; s1 = 0 ; } } else if(e.keyCode==37) // backwards { e.preventDefault() ; if(s1>0) s1 -= 1 ; else { s0 -= 1 ; if(s0<0) s0 = segments.length-1 ; s1 = segments[s0].data.length-1 ; } } else return ; walkto(s0,s1,flag) ; } /* ---------------------------- relative uri ------------------------------- */ function reluri(u1,u2) { var last = u1.lastIndexOf('/') ; if(last<0) return u2 ; u1 = u1.substring(0,last) ; while(u2.substring(0,3)=='../') { last = u1.lastIndexOf('/') ; if(last<0) return u2 ; u2 = u2.substring(3) ; u1 = u1.substring(0,last) ; } return u1 + '/' + u2 ; } /* ------------------------------- getbtnpos -------------------------------- */ function getbtnpos(btnno) { var bounds=map.getBounds(),sw,ne,lat,lon,lam ; sw = bounds.getSouthWest() ; ne = bounds.getNorthEast() ; lam = 52.0 / window.innerHeight ; lat = lam*ne.lat() + (1-lam)*sw.lat() ; lam = 0.5 + (btnno*32-112.0)/window.innerWidth ; lon = lam*ne.lng() + (1-lam)*sw.lng() ; return new google.maps.LatLng(lat,lon) ; } /* ----- unambig: does the selected waypoint determine a unique segment? ---- */ function unambig() // does the selected waypoint determine a unique segment? { var s0=selected[0],s1=selected[1] ; if(segments.length==1) return 1 ; if( ( s0==segments.length-1 || s1!=segments[s0].data.length-1 || ! segments[s0].data[s1].pos.equals(segments[s0+1].data[0].pos) ) && ( s0==0 || s1!=0 || ! segments[s0].data[s1].pos.equals (segments[s0-1].data[segments[s0-1].data.length-1].pos) ) ) return 1 ; else return 0 ; } /* --------------------- undraw & redraw segments -------------------------- */ function undraw(i) { segments[i].route.setMap(null) ; if(segments[i].clickhandler!=null) { google.maps.event.removeListener(segments[i].clickhandler) ; segments[i].clickhandler = null ; } } function redraw(i) { undraw(i) ; draw(i) ; } function recolour(i) { if(i&1) segments[i].route.setOptions({strokeColor:"#ff9999"}) ; else segments[i].route.setOptions({strokeColor:"#ff0000"}) ; } function obliterate(s0) // undraw route and all labels { var i ; for(i=0;i<segments[s0].data.length;i++) segments[s0].data[i].setmap(null) ; undraw(s0) ; disconnect(s0-1) ; disconnect(s0) ; } /* ----------------------------- draw segments ------------------------------ */ function draw(i) { var colour ; if(i&1) colour = "#ff9999" ; else colour = "#ff0000" ; segments[i].route = new google.maps.Polyline(new linepath(i,-1,0,colour)) ; segments[i].route.setMap(map) ; segments[i].clickhandler = google.maps.event.addListener(segments[i].route,"click",selpoint) ; } /* ----------------------- connect and disconnect segments ------------------ */ function disconnect(i) { if(i<0||i>=segments.length-1||segments[i].dots==null) return ; segments[i].dots.setMap(null) ; if(segments[i].dothandler!=null) { google.maps.event.removeListener(segments[i].dothandler) ; segments[i].dothandler = null ; } } function reconnect(i) { disconnect(i) ; connect(i) ; } function connect(i) { if(i<0||i>=segments.length-1) return ; var opos = segments[i].data[segments[i].data.length-1].pos ; var npos = segments[i+1].data[0].pos ; if(opos.equals(npos)) return ; segments[i].dots = new google.maps.Polyline(new dotpath(opos,npos)) ; segments[i].dots.setMap(map) ; segments[i].dothandler = google.maps.event.addListener(segments[i].dots,"click",selpoint) ; } function redrawconnect(s0,s1) { redraw(s0) ; if(s1==0) reconnect(s0-1) ; if(s1=segments[s0].data.length-1) reconnect(s0) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ---------------------- draw the selection point -------------------------- */ // note: there's no point in allowing clicking on a marker because the // event position is always the marker position rather than the click position function drawsel(opt) { var ind,clen,s0=selected[0],s1=selected[1],pos=segments[s0].data[s1].pos ; if(opt) reprofile() ; clen = segments[s0].data.length ; if(clen==1) arrow.rotation = 90 ; else { if(s1==clen-1) ind = s1-1 ; else ind = s1 ; arrow.rotation = bearing(segments[s0].data[ind].pos,segments[s0].data[ind+1].pos) ; } if(sel.marker==null) sel.marker = new google.maps.Marker ( { position:pos, map:map, cursor:'default', icon:arrow , zIndex:2 } ) ; else // avoid unnecessary redraws { if(arrow.rotation!=sel.orientation) sel.marker.setIcon(arrow) ; if(!pos.equals(sel.marker.getPosition())) sel.marker.setPosition(pos) ; } sel.orientation = arrow.rotation ; procur() ; blackout(penbtn) ; if(segments.length>1&&unambig()) blackout(binbtn) ; else greyout(binbtn) ; if(s1!=0&&s1!=clen-1) blackout(scissorsbtn) ; else greyout(scissorsbtn) ; } /* ------------- selclick: respond to click of cursor button --------------- */ function selclick() { mouseopt = 1-mouseopt ; infowindow.close() ; if(mouseopt) { map.setOptions({draggable:false, draggableCursor:'default'}) ; cursorbtn.btn.setAttribute('src','hand.png') ; clickhandle = google.maps.event.addListener(map,"click",selpoint) ; } else { map.setOptions({draggable:true, draggableCursor:''}) ; cursorbtn.btn.setAttribute('src','arrow.png') ; google.maps.event.removeListener(clickhandle) ; } } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------------------------------------------------- */ function genhead(uri,key) { resuri = uri + '/' ; document.write ('<script src="http://maps.google.com/maps/api/js?' + ((key==null||key==undefined)?'':('key='+key+'&')) + 'libraries=geometry"></scr' + 'ipt>' + '<script src="' + resuri + 'dms.js"></scr' + 'ipt>' + '<script src="' + resuri + 'vector3d.js"></scr' + 'ipt>' + '<script src="' + resuri + 'latlon-ellipsoidal.js"></scr' + 'ipt>' + '<script src="' + resuri + 'utm.js"></scr' + 'ipt>' + '<script src="' + resuri + 'osgridref.js"></scr' + 'ipt>' + '<script src="' + resuri + 'FileSaver.js"></scr' + 'ipt>' + '<script src="' + resuri + 'Blob.js"></scr' + 'ipt>' + '<style type="text/css">html, body {width: 100%; height: 100%}' + 'body {margin:0px}</style><title>Routemaster</title>' + '<link rel="shortcut icon" href=' + resuri + 'bus.gif>' ) ; } /* -------------------------------------------------------------------------- */ /* FUNCTIONS TO GENERATE THE INITIAL MAP */ /* -------------------------------------------------------------------------- */ function genpage() { var thispage=document.URL,xhttp,quotind,plusind,listxhttp ; imginfo = new listinfo() ; imgdiv = null ; elevator = new google.maps.ElevationService ; // coursepoint icons flagsign = { path: "M 0.5 20.5 L 0.5 0.5 12.5 6 0.5 11.5 ", fillColor: '#FCDFFF', fillOpacity: 0.7, strokeColor: 'purple', strokeWeight: 1.5, anchor: new google.maps.Point(0.5,20.5), } ; turnleft = { path: "M 18.5 20.5 L 16.5 11.5 A 2 2 0 0 0 14.5 9.5 "+ "L 11.5 10 11.5 13.5 "+ "6.5 7.5 11.5 1.5 11.5 5 16.5 5.5 A 3.5 3.5 0 0 1 20 9 z", fillColor: '#FCDFFF', fillOpacity: 0.7, strokeColor: 'purple', strokeWeight: 1.5, anchor: new google.maps.Point(18.5,20.5), } ; straighton = { path: "M 7.5 20.5 L 4.5 6.5 0.5 6.5 7.5 0.5 14.5 6.5 10 6.5 z", fillColor: '#FCDFFF', fillOpacity: 0.7, strokeColor: 'purple', strokeWeight: 1.5, anchor: new google.maps.Point(7.5,20.5), } ; turnright = { path: "M 3.5 20.5 L 5.5 11.5 A 2 2 0 0 1 7.5 9.5 L 10.5 10 10.5 13.5 "+ "15.5 7.5 10.5 1.5 10.5 5 5.5 5.5 A 3.5 3.5 0 0 0 2 9 z", fillColor: '#FCDFFF', fillOpacity: 0.7, strokeColor: 'purple', strokeWeight: 1.5, anchor: new google.maps.Point(3.5,20.5), } ; shriek = { path: "M 8.5 21.5 A 2.5 2.5 0 0 1 8.5 16.5 A 2.5 2.5 0 1 1 8.5 21.5 "+ "M 8.5 14.5 4.5 5.5 A 4.5 4.5 0 1 1 12.5 5.5 L 8.5 14.5" , fillColor: '#FCDFFF', fillOpacity: 0.7, strokeColor: 'purple', strokeWeight: 1.5, anchor: new google.maps.Point(8.5,21.5), } ; // icon for arrow representing current waypoint arrow = { path: "M 6 9 0 15 6 0 12 15 z", fillColor: 'black', fillOpacity: 1, strokeColor: 'black', strokeWeight: 0, anchor: new google.maps.Point(6,6), rotation: 0, clickable: false } ; // icon for concentric circles representing draggable waypoint concircle = { path: "M 6 0 A 6 6 0 1 0 6 12 A 6 6 0 1 0 6 0 M 6 3 " + "A 3 3 0 1 0 6 9 A 3 3 0 1 0 6 3", fillColor: 'black', fillOpacity: 0, strokeColor: 'black', strokeWeight: 1, strokeOpacity: 1, anchor: new google.maps.Point(6,6), clickable: false } ; // camera icon camera = { path: "M 0.5 4 A 1.5 1.5 0 0 1 2 2.5 L 5.5 2.5 7 0.5 11 0.5 " + "12.5 2.5 14 2.5 A 1.5 1.5 0 0 1 16 3 L 20 7 16 11 " + "A 1.5 1.5 0 0 1 15 11.5 L 2 11.5 A 1.5 1.5 0 0 1 0.5 10 z " + "M 9 4 A 3 3 0 0 1 9 10 A 3 3 0 0 1 9 4 " , fillColor: '#FCDFFF', fillOpacity: 0.7, strokeColor: 'purple', strokeWeight: 1.5, anchor: new google.maps.Point(21,7), clickable: false } ; window.onload = function() { window.addEventListener("beforeunload",function(e) { var msg = unsavedmsg(0) ; if(msg==null) return undefined ; (e || window.event).returnValue = msg ; //Gecko + IE return msg ; //Gecko + Webkit, Safari, Chrome etc. (msg is ignored by ffx) } ) ; } ; body = document.getElementsByTagName("body")[0] ; while(body.childNodes.length>0) body.removeChild(body.childNodes[body.childNodes.length-1]) ; mapdiv = document.createElement('div') ; mapdiv.setAttribute('id','map') ; mapdiv.setAttribute('style','width:100%;height:100%') ; body.appendChild(mapdiv) ; if((quotind=thispage.indexOf('?'))>=0) { thispage = thispage.substring(quotind+1) ; if((plusind=thispage.indexOf('+'))>0) { getlist(thispage.substring(plusind+1),'uri') ; thispage = thispage.substring(0,plusind) ; } else if(thispage.substring(thispage.length-3)=='.js') { getlist(thispage,'uri') ; mapdiv.appendChild(filedialogue(0)) ; return ; } xhttp = new XMLHttpRequest() ; xhttp.onreadystatechange = function() { if(xhttp.readyState==4) { if(xhttp.status==200) { var x = parser.parseFromString(xhttp.responseText,"application/xml") ; render(x,thispage,0) ; } else alert("Unable to read "+thispage+": error code "+xhttp.status) ; } } xhttp.open("GET",thispage,true) ; xhttp.send() ; } else mapdiv.appendChild(filedialogue(0)) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------- getlist --------------------------------- */ function getlist(uri,imgtype) { var xhttp = new XMLHttpRequest(),list=null,sizes=null,pixpage=null,ind,r ; var imagedir=null,thumbshape = [] ; imginfo.status = 'waiting' ; imginfo.uri = uri ; imginfo.type = imgtype ; xhttp.onreadystatechange = function() { if(xhttp.readyState==4) { if(xhttp.status!=200) { alert("Unable to read "+uri+": error code "+xhttp.status) ; return ; } eval(xhttp.responseText) ; imginfo.list = list ; imginfo.sizes = sizes ; imginfo.dir = imagedir ; imginfo.pixpage = pixpage ; imginfo.status = 'ready' ; // prepare to set the thumb shapes by finding the thumb index for(ind=0;ind<sizes.length&&sizes[ind].type!='thumb';ind++) ; if(ind==sizes.length) { alert('no "sizes" entry of type "thumb"') ; throw '' ; } imginfo.thumbsfx = sizes[ind].suffix ; if(thumbshape.length!=2) { if(sizes[ind].scale>0) r = sizes[ind].scale / sizes[0].scale ; else { alert('thumbs have size '+sizes[ind].scale) ; throw '' ; } } // set thumbshapes for(ind=0;ind<list.length;ind++) if(list[ind].name!=undefined) { if(list[ind].thumbshape==undefined) { if(thumbshape.length==2) list[ind].thumbshape = thumbshape ; else for(list[ind].thumbshape=[0,0],k=0;k<2;k++) list[ind].thumbshape[k] = Math.floor(0.5+list[ind].shape[k]*r) ; } } } } xhttp.open("GET",uri,true) ; xhttp.send() ; } /* ----------------------------- file dialogue ------------------------------ */ function filedialogue(overwrite) { var input = document.createElement('input') ; var para = document.createElement('p') ; para.appendChild(document.createTextNode ('\u00a0\u00a0\u00a0Select TCX/GPX file: ')) ; input.setAttribute('type','file') ; input.setAttribute('accept','.tcx,.gpx') ; input.addEventListener('change',function(e) { reader = new FileReader() ; reader.onload = function(e) { var xmldoc = parser.parseFromString(reader.result,"application/xml") ; render(xmldoc,input.files[0].name,overwrite) ; } reader.readAsText(input.files[0]) ; } ) ; para.appendChild(input) ; return para ; } /* -------------------------------------------------------------------------- */
/*page*/ /* --------------------- set up the map and buttons ------------------------- */ function render(xmldoc,filename,overwrite) { var i,opts,centre,lat,lon,minlon,maxlon,minlat,maxlat,newseg,s0,bounds,sw,ne ; infowindow.close() ; document.onkeydown = walk ; document.onkeyup = function(e) { if(e.keyCode==16) shift = 0 ; } if(overwrite) { for(i=0;i<segments.length;i++) obliterate(i) ; unprofile() ; if(sel.marker!=null) sel.marker.setMap(null) ; segments = [] ; if(imginfo.type=='tcx') imginfo = new listinfo() ; } s0 = segments.length ; if(s0==0) { sel = { marker:null, orientation: null } ; pending = [] ; xpending = [] ; actions = [] ; unsavedchanges = [] ; nactions = dragging = 0 ; loadno = -1 ; prox = proa = pron = null ; altdiv = curcan = curdiv = cmap = smap = curhandle = null ; } // set up segments newseg = gensegment(xmldoc,filename) ; segments.push(newseg[0]) ; if(s0==0) { if(newseg[1].title!=null) settitle(newseg[1].title) ; else settitle('Untitled Route') ; } actions[nactions++] = [ 'load' , s0 , newseg[0].data.slice() , loadno , newseg[1] ] ; loadno = nactions-1 ; if(!newseg[1].optim.already) optimaction(segments.length-1,defparms,0) ; // find max and min lat and long for(i=0;i<segments[s0].data.length;i++) { lat = segments[s0].data[i].pos.lat() ; lon = segments[s0].data[i].pos.lng() ; if(i==0||lon<minlon) minlon = lon ; if(i==0||lon>maxlon) maxlon = lon ; if(i==0||lat<minlat) minlat = lat ; if(i==0||lat>maxlat) maxlat = lat ; } if(s0==0) centre = new google.maps.LatLng((minlat+maxlat)/2,(minlon+maxlon)/2) ; if(map==null) // all this only done on first call { opts = { zoom: 22, center: centre, scaleControl: true, rotateControl: false, streetViewControl: false, mapTypeId: google.maps.MapTypeId.TERRAIN, disableDoubleClickZoom: true, styles: [ { "featureType": "poi", "stylers": [{ "visibility": "off" }] } ], mapTypeControl:true, mapTypeControlOptions: { style:google.maps.MapTypeControlStyle.HORIZONTAL_BAR }, mapTypeIds: [ google.maps.MapTypeId.ROADMAP, google.maps.MapTypeId.TERRAIN, google.maps.MapTypeId.SATELLITE ] } ; map = new google.maps.Map(mapdiv,opts) ; // set up buttons setbtn = genbutton('settings') ; cursorbtn = genbutton('cursor') ; cursorbtn.ui.addEventListener('click',selclick) ; scissorsbtn = genbutton('scissors') ; binbtn = genbutton('bin') ; penbtn = genbutton('pen') ; undobtn = genbutton('undo') ; redobtn = genbutton('redo') ; dlbtn = genbutton('dl') ; selclick() ; } else if(s0!=0) { bounds = map.getBounds() ; sw = bounds.getSouthWest() ; ne = bounds.getNorthEast() ; if(sw.lat()<minlat) minlat = sw.lat() ; if(sw.lng()<minlon) minlon = sw.lng() ; if(ne.lat()>maxlat) maxlat = ne.lat() ; if(ne.lng()>maxlon) maxlon = ne.lng() ; } map.fitBounds(new google.maps.LatLngBounds( new google.maps.LatLng(minlat,minlon), new google.maps.LatLng(maxlat,maxlon))) ; for(i=0;i<segments[s0].data.length;i++) segments[s0].data[i].setmap(map) ; if(nactions>1) donesomething() ; // specifically, done loading & optimisation else actions.length = nactions ; // load with no optimisation hence no undo if(s0==0) { selected = [0,0] ; drawsel(1) ; } else greyout(dlbtn) ; draw(s0) ; connect(s0-1) ; connect(s0) ; reprofile() ; } /* ------------------------------- settitle --------------------------------- */ function settitle(newtitle) { routetitle = newtitle ; var h = document.getElementsByTagName('title')[0] ; while(h.firstChild) h.removeChild(h.firstChild) ; h.appendChild(document.createTextNode(routetitle)) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ------------------------------- retitle ---------------------------------- */ function retitle() { infowindow.close() ; var response = window.prompt("Modify title:",routetitle) ; if(response==null) return ; else response = response.substring(0,15) ; if(response==routetitle) return ; actions[nactions++] = ['edittitle',routetitle,response] ; actions.length = nactions ; blackout(undobtn) ; greyout(redobtn) ; settitle(response) ; } /* ------------------------------- genbutton -------------------------------- */ function genbutton(name) { var u,v,w,b,g,k,h=null,div=document.createElement('div'),act ; u = document.createElement('div') ; u.style.backgroundColor = '#ffffff' ; u.style.border = '2px solid #ffffff' ; u.style.borderRadius = '3px' ; u.style.boxShadow = '0 2px 6px rgba(0,0,0,.3)' ; if(name=='dl'||name=='settings'||name=='cursor') u.style.cursor = 'pointer' ; else u.style.cursor = 'default' ; u.style.marginBottom = '12px' ; if(name!='dl') u.style.marginRight = '4px' ; u.style.textAlign = 'center' ; div.appendChild(u) ; b = document.createElement('img') ; g = name + '.png' ; if(name=='scissors') { h = snip ; div.index = 3 ; } else if(name=='bin') { h = discard ; div.index = 4 ; } else if(name=='pen') { h = labelprompt ; div.index = 5 ; } else if(name=='undo') { h = undo ; div.index = 6 ; } else if(name=='redo') { h = redo ; div.index = 7 ; } else if(name=='dl') { h = dl ; div.index = 8 ; } else if(name=='settings') { h = popup ; div.index = 1 ; } else if(name=='cursor') { g = 'arrow.png' ; k = 'hand.png' ; div.index = 2 ; } if(name!='cursor') { k = 'black' + g ; g = 'grey' + g ; } g = resuri + g ; k = resuri + k ; if(name=='dl'||name=='settings'||name=='cursor') b.setAttribute('src',k) ; else b.setAttribute('src',g) ; b.setAttribute('width',24) ; b.setAttribute('height',24) ; u.appendChild(b) ; map.controls[google.maps.ControlPosition.BOTTOM_CENTER].push(div) ; if(name=='dl'||name=='settings') u.addEventListener('click',h) ; if(name=='dl'||name=='settings') act = 1 ; else act = 0 ; return { ui:u , btn:b , active:act , greyimg:g , blackimg:k , handler:h } ; } /* -------------------------------------------------------------------------- */
/*page*/ /* --------------- construct a segment from an xml document ----------------- */ function genseg(a) { this.data = a ; this.route = this.routehandler = this.dots = this.dothandler = null ; } /* -------------------------------------------------------------------------- */ function gensegment(xmldoc,filename) { var xmlcoords,nodeno,tcx,type,mindist,lat,lon,i,j,segment,node,alt,pos ; var ind,caption,data=[],alreadyoptimised=0,photo,time,valid ; var props = { title: null , inputlen: null , optim: { already: 0, ndel: 0, origlen: 0, parms: null } } tcx = filename.length ; if(filename.substring(tcx-4,tcx)=='.tcx') tcx = 1 ; else if(filename.substring(tcx-4,tcx)=='.gpx') tcx = 0 ; else { alert(filename+' is neither .tcx nor .gcx') ; throw '' ; } if(tcx) { // optimised? xmlcoords = xmldoc.getElementsByTagName('Optimised') ; if(xmlcoords.length) { props.optim.already = 1 ; props.optim.origlen = parseInt(xmlcoords[0].getAttribute('from')) ; props.optim.ndel = props.optim.origlen - parseInt(xmlcoords[0].getAttribute('to')) ; props.optim.parms = { tol: parseFloat(xmlcoords[0].getAttribute('tol')) , maxsep: parseFloat(xmlcoords[0].getAttribute('maxsep')) , wppenalty: parseFloat(xmlcoords[0].getAttribute('wppenalty')) , vweight: parseFloat(xmlcoords[0].getAttribute('vweight')) } ; } // photo list? if(imginfo.uri==null||imginfo.type=='tcx') { xmlcoords = xmldoc.getElementsByTagName('PhotoList') ; if(xmlcoords.length) { imginfo = new listinfo() ; getlist(xmlcoords[0].getAttribute('src'),'tcx') ; } } // route title xmlcoords = xmldoc.getElementsByTagName('Name') ; for(i=0;i<xmlcoords.length&&props.title==null;i++) if( xmlcoords[i].parentNode.nodeName=='Course' || xmlcoords[i].parentNode.nodeName=='Lap' ) props.title = xmlcoords[i].childNodes[0].textContent ; // loop over the track points to get the coords xmlcoords = xmldoc.getElementsByTagName('Trackpoint') ; for(i=0;i<xmlcoords.length;i++) { lat = lon = alt = time = null ; photo = [] ; for(valid=1,nodeno=0;nodeno<xmlcoords[i].childNodes.length;nodeno++) { node = xmlcoords[i].childNodes[nodeno] ; if(node.nodeName=='AltitudeMeters') alt = xmlfloat(node) ; else if(node.nodeName=='Time') // '1970-01-01T03:040:08Z' { time = new Date(node.childNodes[0].textContent) ; if(!isvaliddate(time)) time = null ; } else if(node.nodeName=='Position') for(j=0;j<node.childNodes.length;j++) { if(node.childNodes[j].nodeName=='LatitudeDegrees') lat = xmlfloat(node.childNodes[j]) ; else if(node.childNodes[j].nodeName=='LongitudeDegrees') lon = xmlfloat(node.childNodes[j]) ; } else if(node.nodeName=='Extensions') for(j=0;j<node.childNodes.length;j++) { if(node.childNodes[j].nodeName=='Photo') photo = node.childNodes[j].childNodes[0].textContent.split(' ') ; else if(node.childNodes[j].nodeName=='ValidTime') valid = 0 ; } } if(lat==null||lon==null) continue ; data[data.length] = new datatype(new google.maps.LatLng(lat,lon),alt) ; for(ind=0;ind<photo.length;ind++) data[data.length-1].addphoto(photo[ind]) ; if(valid) data[data.length-1].t = time ; } } else // gpx { if(xmldoc.getElementsByTagName('name').length>0) props.title = xmldoc.getElementsByTagName('name')[0].childNodes[0].textContent ; // loop over the track points to get the coords xmlcoords = xmldoc.getElementsByTagName('trkpt') ; if(xmlcoords.length==0) xmlcoords = xmldoc.getElementsByTagName('rtept') ; for(i=0;i<xmlcoords.length;i++) { lat = parseFloat(xmlcoords[i].getAttribute('lat')) ; lon = parseFloat(xmlcoords[i].getAttribute('lon')) ; for(time=alt=null,nodeno=0;nodeno<xmlcoords[i].childNodes.length;nodeno++) { node = xmlcoords[i].childNodes[nodeno] ; if(node.nodeName=='ele') alt = parseFloat(node.textContent) ; else if(node.nodeName=='time') time = new Date(node.childNodes[0].textContent) ; } data[data.length] = new datatype(new google.maps.LatLng(lat,lon),alt) ; data[data.length-1].t = time ; } } for(i=0;i<data.length;i++) if(data[i].h==null) { alert(data[i].pos+' has no altitude... unable to proceed') ; throw '' ; } props.inputlen = data.length ; for(i=0;i<data.length;i++) if((time=data[i].t)!=null) // nullify illegal times if(time.getTime()<365*24*3600000) data[i].t = null ; segment = new genseg(data) ; // loop over the course points to get the labels if(tcx) xmlcoords = xmldoc.getElementsByTagName('CoursePoint') ; else xmlcoords = xmldoc.getElementsByTagName('wpt') ; for(i=0;i<xmlcoords.length;i++) { caption = type = lat = lon = null ; if(tcx) for(nodeno=0;nodeno<xmlcoords[i].childNodes.length;nodeno++) { node = xmlcoords[i].childNodes[nodeno] ; if(node.nodeName=='Name') caption = node.childNodes[0].textContent ; else if(node.nodeName=='PointType') type = node.childNodes[0].textContent ; else if(node.nodeName=='Position') for(j=0;j<node.childNodes.length;j++) { if(node.childNodes[j].nodeName=='LatitudeDegrees') lat = xmlfloat(node.childNodes[j]) ; else if(node.childNodes[j].nodeName=='LongitudeDegrees') lon = xmlfloat(node.childNodes[j]) ; } } else { lat = parseFloat(xmlcoords[i].getAttribute('lat')) ; lon = parseFloat(xmlcoords[i].getAttribute('lon')) ; for(nodeno=0;nodeno<xmlcoords[i].childNodes.length;nodeno++) { node = xmlcoords[i].childNodes[nodeno] ; if(node.nodeName=='name') caption = node.childNodes[0].textContent ; else if(node.nodeName=='type') type = node.childNodes[0].textContent ; } } if(lat==null||lon==null||caption==null||type==null) { alert('Badly formatted course point' ) ; throw '' ; } pos = new google.maps.LatLng(lat,lon) ; for(j=0;j<data.length;j++) if(j==0||dist(pos,data[j].pos)<mindist) { mindist = dist(pos,data[j].pos) ; ind = j ; } segment.data[ind].setlabel(type,caption) ; } if(props.optim.origlen==0) props.optim.origlen = data.length ; return [ segment , props ] ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------------------------------------------------- */ /* OPTIMISATION */ /* -------------------------------------------------------------------------- */ function optimaction(segno,parms,force) { var s = segments[segno], result = optimise(s,parms) ; var ndel = s.data.length - result.length ; if((force==0&&ndel<s.data.length/2)||ndel==0) return 0 ; actions[loadno][4].optim.ndel = ndel ; actions[nactions++] = [ 'optimise' , segno , parms ] ; segments[segno] = new genseg(result) ; actions[loadno][4].optim.parms = { tol: parms.tol , maxsep: parms.maxsep , wppenalty: parms.wppenalty , vweight: parms.vweight } ; return 1 ; } /* -------------------------------------------------------------------------- */ function optimprompt() { var msg = 'Enter 4 optimisation parms (tol, maxsep, wppenalty and vweight)' ; var parmstr = defparms.tol + ' ' + defparms.maxsep.toFixed(0) + ' ' + defparms.wppenalty.toFixed(0) + ' ' + defparms.vweight.toFixed(1) ; var parms,i ; infowindow.close() ; for(i=0;;i++) { newparms = prompt(msg,parmstr) ; if(newparms==null) return ; if(newparms=='') { parms = defparms ; break ; } newparms = newparms.split(' ') ; if(newparms.length==0) { parms = defparms ; break ; } parms = { tol: parseFloat(newparms[0]) , maxsep: parseFloat(newparms[1]) , wppenalty: parseFloat(newparms[2]) , vweight: parseFloat(newparms[3]) } ; if( isNaN(parms.tol)==0 && isNaN(parms.maxsep)==0 && isNaN(parms.wppenalty)==0 && isNaN(parms.vweight)==0 ) break ; if(i==0) msg = '*** Illegal parms ***\n' + msg ; } if(optimaction(segments.length-1,parms,1)) { donesomething() ; draw(segments.length-1) ; } routeinfo() ; } /* -------------------------------------------------------------------------- */ function optimise(s,parms) { var stk,nstk,stk2,clen=s.data.length,i,j,m,step=new Array(clen-1) ; var opos,oalt,npos,nalt,ndatum,mpos,malt,arccentre,arctol,pathpos ; var legal,theta,omega,x,y,hyp,tdist,maxtheta,mintheta,d,dver,od,odver,odash ; var bearings,pi=Math.PI,tol=parms.tol ; stk = [ { data:[s.data[0]] , err:0 , pathpos:1 } ] ; for(i=0;i<clen-1;i++) step[i] = dist(s.data[i].pos,s.data[i+1].pos) ; while(stk[0].pathpos<clen) { pathpos = stk[0].pathpos ; opos = s.data[pathpos-1].pos ; oalt = s.data[pathpos-1].h ; // try extending to pathpos+i for(bearings=[],nstk=[],arctol=null,i=0;i<clen-pathpos;i++) { ndatum = s.data[pathpos+i] ; npos = ndatum.pos ; nalt = ndatum.h ; if(i==0) hyp = step[pathpos-1] ; else if((hyp=dist(opos,npos))>parms.maxsep) break ; omega = angle(opos,npos) ; // find the min and max legal bearing if(hyp>tol) { theta = Math.asin(tol/hyp) ; if(arctol==null) { arccentre = omega ; arctol = theta ; } else { odash = omega - arccentre ; while(odash>pi) odash -= 2*pi ; while(odash<-pi) odash += 2*pi ; maxtheta = Math.min(arctol,odash+theta) ; mintheta = Math.max(-arctol,odash-theta) ; if(maxtheta<mintheta) break ; arccentre += (maxtheta+mintheta) /2 ; arctol = (maxtheta-mintheta) /2 ; } } bearings[i] = { hyp:hyp , omega:omega } ; // see whether this breaches the max error on any intermediate point for(legal=1,od=odver=tdist=m=0;m<i;m++,od=d,odver=dver) { mpos = s.data[pathpos+m].pos ; malt = s.data[pathpos+m].h ; x = bearings[m].hyp ; theta = bearings[m].omega ; d = x * Math.sin(theta-omega) ; dver = 0 ; if(d*d<tol*tol&&oalt!=null&&nalt!=null&&malt!=null) { y = hyp - x*Math.cos(theta-omega) ; y = Math.sqrt(d*d+y*y) ; dver = parms.vweight * ( malt - (oalt*y+nalt*x)/(x+y) ) ; } if(d*d+dver*dver>tol*tol) { legal = 0 ; break ; } tdist += (step[pathpos-1+i]/3) * (d*d+d*od+od*od + dver*dver+odver*dver+odver*odver) ; } // if we emerge with 'legal' non-zero then we may advance to pathpos+i // and tdist is the sum of squared errors if(legal) nstk.push ( { data: stk[0].data.concat([ndatum]) , err: stk[0].err + pi*tdist + parms.wppenalty , pathpos: stk[0].pathpos+i+1 } ) ; if(ndatum.type!=null||ndatum.photo.length>0) break ; } // end loop over i for(stk2=[],i=1,j=0;i<stk.length||j<nstk.length;) if(i==stk.length) stk2.push(nstk[j++]) ; else if(j==nstk.length||stk[i].pathpos<nstk[j].pathpos) stk2.push(stk[i++]) ; else if(stk[i].pathpos>nstk[j].pathpos) stk2.push(nstk[j++]) ; else if(stk[i].err<nstk[j].err) { stk2.push(stk[i++]) ; j += 1 ; } else { stk2.push(nstk[j++]) ; i += 1 ; } stk = stk2 ; } return stk[0].data ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------------------------------------------------- */ /* THE MAIN POPUP MENU AND THE FUNCTIONS IT GOVERNS */ /* -------------------------------------------------------------------------- */ function popup() { var opts,pos,s0=selected[0],s1=selected[1] ; infowindow.close() ; if(dragging) { opts = finalbox + "Hit [return] when you've finished dragging.</div>" ; infowindow.open(opts,getbtnpos(0),'settings') ; return ; } // route options opts = textbox + active + '"routeinfo()">Route info</span><br>' ; if(altdiv==null) opts += active + '"profile()">Show altitude profile</span><br>' ; else opts += active + '"unprofile()">Hide altitude profile</span><br>' ; opts += active + '"addload(1)">Load new route</span></div>' + textbox ; // segment options if(unambig()) opts += active + '"revseg()">' ; else opts += inactive ; opts += 'Reverse segment</span><br>' ; opts += active + '"manualcal()">Calibrate segment altitudes</span><br>' ; opts += active + '"addload(0)">Load route as a new segment</span></div>' ; // waypoint options opts += textbox + active + '"wpinfo()">Waypoint info</span><br>' ; if(segments[selected[0]].data.length>1) opts += active + '"wpdel()">' ; else opts += inactive ; opts += 'Delete waypoint</span><br>' ; opts += active + '"draggit(0)">Make waypoint draggable</span><br>' ; opts += active + '"inswp(1)">Insert draggable waypoint ahead</span><br>' ; opts += active + '"inswp(-1)">Insert draggable waypoint behind</span>' ; opts += '</div>'+finalbox+active ; // tool options if( document.documentElement.requestFullscreen || document.documentElement.mozRequestFullScreen || document.documentElement.webkitRequestFullscreen || document.documentElement.msRequestFullscreen) { if( (document.fullScreenElement && document.fullScreenElement !== null) || (!document.mozFullScreenElement && !document.webkitFullScreenElement) ) opts += '"enterFullscreen()">Enter full screen</span><br>' + active ; else opts += '"exitFullscreen()">Leave full screen</span><br>' + active ; } opts += '"help()">Help</span></div>' ; infowindow.open(opts,getbtnpos(0),'settings') ; } /* ------------------------------- calwork --------------------------------- */ function calwork(s0,y) { var i,s1 ; for(s1=0;s1<segments[s0].data.length;s1++) if(segments[s0].data[s1].h!=null) segments[s0].data[s1].h += y ; reprofile() ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ------------------------------ manualcal --------------------------------- */ function manualcal() { infowindow.close() ; var x,y,s0=selected[0] ; x = prompt('Enter offset in metres to add to altidudes:') ; if(x==null) return ; y = parseFloat(x) ; if(isNaN(y)) { alert(x+' is not a number') ; return ; } calwork(s0,y) ; done(['recal',s0,y]) ; } /* --------------------------------- help ----------------------------------- */ function help() { var str ; infowindow.close() ; str = textbox + '<table cellpadding=0 cellspacing=0>' ; str += '<tr><td style="padding-bottom:4px">' ; str += '<nobr><img src="'+cursorbtn.greyimg+'" width=24 height=24>&nbsp;' ; str += '<img src="'+cursorbtn.blackimg+'" width=24 height=24></nobr>' ; str += '<td rowspan=6>&nbsp;&nbsp;&nbsp;' ; str += '<td style="padding-bottom:4px">' ; str += 'toggle between using the mouse to select waypoints and to ' ; str += 'drag the map<br>(the space bar has the same function)' ; str += '<tr><td style="padding-bottom:4px">' ; str += '<nobr><img src="'+scissorsbtn.blackimg+'" width=24 height=24>&nbsp;' ; str += '<img src="'+binbtn.blackimg+'" width=24 height=24></nobr>' ; str += '<td style="padding-bottom:4px">' ; str += 'split the current segment at the selected point<br>' ; str += 'delete the currrent segment (or use the delete or backspace key)' ; str += '<tr><td style="padding-bottom:4px">' ; str += '<img src="'+penbtn.blackimg+'" width=24 height=24>' ; str += '<td style="padding-bottom:4px">' ; str += 'add a labelled coursepoint at the current position (1-10chars)<br>' ; str += 'click on flag to edit; right-click to change symbol; ' ; str += 'delete label to delete' ; str += '<tr><td style="padding-bottom:4px">' ; str += '<nobr><img src="'+dlbtn.blackimg+'" width=24 height=24>&nbsp;' ; str += '<img src="'+setbtn.blackimg+'" width=24 height=24></nobr>' ; str += '<td style="padding-bottom:4px">download route as .tcx<br>' ; str += 'access to miscellaneous tools and functions' ; str += '<tr><td valign=top>Keyboard: <td>' ; str += '\u2190/\u2192 move the current waypoint forwards or backwards;<br>' ; str += '\u2193 centres the map on the current waypoint;<br>' ; str += '[return] makes the current waypoint draggable;<br>' ; str += '[tab] inserts a draggable waypoint;<br>[space]=toggle cursor mode;' ; str += '<br>[del], [backspace]=delete segment (bin button).' ; str += '<tr><td valign=top>Mouse: <td>when the cursor is in selection' ; str += ' mode:<br>[shift click] extends the current segment by' ; str += ' the cursor position.</table></div>' ; str += finalbox + '<a style="cursor:pointer;color:#0000bd;text-'+ 'decoration:none" href="http://www.masterlyinactivity.com/software'+ '/routemaster.html" target="_blank">Technical documentation and '+ 'source code</a>'+neutral+' (opens in new tab/window)</span></div>' ; infowindow.open(str,getbtnpos(0),'help') ; } /* -------------------------------------------------------------------------- */
/*page*/ /* --------------------------------- wpdel ---------------------------------- */ function wpdelwork(s0,s1) { var i,response=segments[s0].data[s1],clen=segments[s0].data.length ; response.setmap(null) ; for(i=s1;i<clen-1;i++) segments[s0].data[i] = segments[s0].data[i+1] ; segments[s0].data.length = clen-1 ; selected = [s0,s1] ; if(s1==segments[s0].data.length) selected[1] -= 1 ; redrawconnect(s0,s1) ; drawsel(1) ; return response ; } function wpdel() { var s0=selected[0],s1=selected[1],i ; infowindow.close() ; done(['wpdel',s0,s1,wpdelwork(s0,s1)]) ; } /* --------------------------------- revseg --------------------------------- */ function revsegwork(s0) { var i,s=segments[s0],j,x,len=s.data.length ; disconnect(s0-1) ; disconnect(s0) ; for(i=0;i<len/2;i++) { j = (len-1) - i ; x = s.data[i] ; s.data[i] = s.data[j] ; s.data[j] = x ; } for(i=0;i<s.data.length;i++) if(s.data[i].type=='Right') s.data[i].settype('Left') ; else if(s.data[i].type=='Left') s.data[i].settype('Right') ; if(s0==selected[0]) selected[1] = (len-1) - selected[1] ; connect(s0-1) ; connect(s0) ; drawsel(1) ; } /* -------------------------------------------------------------------------- */ function revseg() { infowindow.close() ; revsegwork(selected[0]) ; done(['revseg',selected[0]]) ; } /* -------------------------------------------------------------------------- */ function addload(overwrite) { var msg ; infowindow.close() ; if(overwrite) { msg = unsavedmsg(1) ; if(msg!=null) if(!confirm(msg)) return ; } infowindow.open(filedialogue(overwrite),getbtnpos(0),'addload') ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------------------------------------------------- */ /* DRAGGING A (POSSIBLY NEWLY INSERTED) WAYPOINT IS QUITE A LOT OF WORK */ /* -------------------------------------------------------------------------- */ function insert(s0,s1,n) { var i ; for(i=segments[s0].data.length+n-1;i>s1;i--) segments[s0].data[i] = segments[s0].data[i-n] ; for(i=0;i<n;i++) segments[s0].data[s1+i] = new datatype(null,null) ; } /* --------------------------------- inswp ---------------------------------- */ function inswp(dir) { var s0=selected[0],s1=selected[1],bounds,del,pos,data=segments[s0].data ; var len = data.length ; if(len==1) pos = data[0].pos ; if(dir>=0) s1 = selected[1] += 1 ; insert(s0,s1,1) ; if(len==1) { bounds = map.getBounds() ; del = bounds.getNorthEast().lng() - bounds.getSouthWest().lng() ; pos = new google.maps.LatLng(pos.lat(),pos.lng()+dir*del/10) ; } else if(s1==0) pos = interp(data[2].pos,data[1].pos,1.5) ; else if(s1<len) pos = interp(data[s1-1].pos,data[s1+1].pos,0.5) ; else pos = interp(data[s1-2].pos,data[s1-1].pos,1.5) ; data[s1].setpos(pos) ; draggit(1) ; } /* -------------------------------- draggit --------------------------------- */ // draggit makes the current waypoint draggable var l1,l2,startpos,seg0,seg1,seg2,colour,inserted ; function draggit(insparm) { var s0=selected[0],s1=selected[1],start,end,i,len=segments[s0].data.length ; startpos = segments[s0].data[s1].pos ; inserted = insparm ; infowindow.close() ; greyout(scissorsbtn) ; greyout(binbtn) ; greyout(penbtn) ; greyout(undobtn) ; greyout(redobtn) ; greyout(dlbtn) ; map.panToBounds(new google.maps.LatLngBounds(startpos,startpos)) ; sel.marker.setMap(null) ; sel.marker = new google.maps.Marker( { position: segments[s0].data[s1].pos, map: map, cursor: 'default', icon: concircle , draggable: true , zIndex: 2 } ) ; if(s0&1) colour = "#ff9999" ; else colour = "#ff0000" ; segments[s0].route.setMap(null) ; if(segments[s0].clickhandler!=null) { google.maps.event.removeListener(segments[s0].clickhandler) ; segments[s0].clickhandler = null ; } seg0 = seg2 = null; if(s1>1) { seg0 = new google.maps.Polyline(new linepath(s0,0,s1,colour)) ; seg0.setMap(map) ; } if(s1==0) start = 0 ; else start = s1-1 ; if(s1==len-1) end = s1+1 ; else end = s1+2 ; seg1 = new google.maps.Polyline(new linepath(s0,start,end,colour)) ; seg1.setMap(map) ; if(s1<segments[s0].data.length-2) { seg2 = new google.maps.Polyline(new linepath(s0,s1+1,len,colour)) ; seg2.setMap(map) ; } l1 = google.maps.event.addListener(sel.marker,'drag',function() { segments[s0].data[s1].setpos(this.getPosition()) ; seg1.setMap(null) ; seg1 = new google.maps.Polyline(new linepath(s0,start,end,colour)) ; seg1.setMap(map) ; if(s1==0&&s0>0) { disconnect(s0-1) ; connect(s0-1) ; } if(s1==len-1&&s0<segments.length-1) { disconnect(s0) ; connect(s0) ; } } ) ; dragging = 1 ; } /* ------------------------------- undraggit -------------------------------- */ // undraggit is invoked by [return] to terminate waypoint dragging function undraggit() { var s0=selected[0],s1=selected[1],i,s1dash,pos=segments[s0].data[s1].pos ; var xpos ; google.maps.event.removeListener(l1) ; dragging = 0 ; if(seg0!=null) seg0.setMap(null) ; seg1.setMap(null) ; if(seg2!=null) seg2.setMap(null) ; segments[s0].route = new google.maps.Polyline(new linepath(s0,-1,0,colour)) ; segments[s0].route.setMap(map) ; segments[s0].data[s1].h = null ; lookupalt(s0,s1) ; sel.marker.setMap(null) ; sel.marker = null ; // force a redraw drawsel(1) ; if(inserted||dist(startpos,pos)>5) done(['move',s0,s1,startpos,pos,inserted]) ; if(segments.length==1) blackout(dlbtn) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------------------------------------------------- */ /* CODE TO GET ALTITUDES FOR NEWLY INSERTED POINTS */ /* -------------------------------------------------------------------------- */ function lookupalt(s0,s1) // set up a request for the alt of the new point { var hi,lo,segno=s0,ptno=s1,lopos,hipos,datum=segments[s0].data[s1] ; for(lo=null,s0=segno,s1=ptno-1;lo==null;s1--) { if(s1<0) { s0 -= 1 ; if(s0<0) break ; s1 = segments[s0].data.length-1 ; } if(segments[s0].data[s1].h!=null) lo = [s0,s1] ; } for(hi=null,s0=segno,s1=ptno+1;hi==null;s1++) { if(s1==segments[s0].data.length) { s0 += 1 ; if(s0==segments.length) break ; s1 = 0 ; } if(segments[s0].data[s1].h!=null) hi = [s0,s1] ; } if(lo==null&&hi==null) { alert('no points left with altitudes: unable to proceed') ; throw '' ; } if(lo!=null) lopos = segments[lo[0]].data[lo[1]].pos ; if(hi!=null) hipos = segments[hi[0]].data[hi[1]].pos ; if(lo==null||(hi!=null&&dist(lopos,datum.pos)>dist(hipos,datum.pos))) { lo = hi ; lopos = hipos ; } pending.push([datum,lopos,segments[lo[0]].data[lo[1]].h]) ; elevator.getElevationForLocations({locations:[datum.pos,lopos]},calibrate) ; } /* -------------------------------------------------------------------------- */ // pending is the list of inserted points for which google altitudes are needed // note a pitfall with the elevation service - it's hard to tell which // response corresponds to which request: the coordinates may not match // because google truncates to 0.00001 deg; hence the use of the dist function. function calibrate(results,status) { var pno,flag ; if(status===google.maps.ElevationStatus.OVER_QUERY_LIMIT) alert('No calibration data available: over Google query limit') ; else if(status!==google.maps.ElevationStatus.OK||results.length!=2) alert('Calibration error') ; // now find whether any of our elevation results allow us to fix an altitude for(pno=0;pno<pending.length;pno++) if( dist(pending[pno][0].pos,results[0].location)<5 && dist(pending[pno][1],results[1].location)<5 ) { if(pno>0) alert('Warning: Google elevation results out of sequence') ; diff = results[0].elevation - results[1].elevation ; pending[pno][0].h = pending[pno][2] + diff ; } for(i=pno=0;pno<pending.length;pno++) if(pending[pno][0].h==null) { if(pno!=i) pending[i] = pending[pno] ; i += 1 ; } pending.length = i ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------------------------------------------------- */ /* FUNCTIONS FOR COMPUTING & DISPLAYING THE ALTITUDE PROFILE */ /* -------------------------------------------------------------------------- */ function profile() { infowindow.close() ; var i,sum,s0,s1,len,oldpos,pos,alt,h,amin,amax,c,y,step,ctx ; var startpos,prevpos ; for(pron=s0=0;s0<segments.length;s0++) pron += segments[s0].data.length ; prox = new Array(pron) ; proa = new Array(pron) ; smap = new Array(segments.length) ; cmap = new Array(610) ; prox[0] = 0 ; for(amax=amin=null,sum=pron=s0=0;s0<segments.length;s0++) for(len=segments[s0].data.length,s1=0;s1<len;s1++,pron++) { proa[pron] = segments[s0].data[s1].h ; if(proa[pron]!=null) { if(amax==null||proa[pron]>amax) amax = proa[pron] ; if(amin==null||proa[pron]<amin) amin = proa[pron] ; } pos = segments[s0].data[s1].pos ; if(pron) sum = prox[pron] = sum + dist(pos,oldpos) ; oldpos = pos ; } if(pron==0||amin==amax) return ; if(amin>0) { if(amax>3*amin) amin = 0 ; else amin *= 1 - (amax/amin-1)/2 ; } altdiv = document.createElement('div') ; altdiv.setAttribute ('style','position:absolute;height:190px;right:0;top:0;width:620px') ; c = document.createElement('canvas') ; c.setAttribute('width',620) ; c.setAttribute('height',200) ; altdiv.appendChild(c) ; body.appendChild(altdiv) ; ctx = c.getContext("2d") ; ctx.font = "10px Helvetica" ; ctx.lineWidth = 0 ; ctx.globalAlpha = 0.6 ; ctx.fillStyle = 'lightgray' ; ctx.lineWidth = 0 ; ctx.rect(0,0,620,200) ; ctx.fill() ; // draw a profile of each segment for(i=pron=s0=0;s0<segments.length;s0++) { len = segments[s0].data.length ; smap[s0] = new Array(len) ; if(s0&1) ctx.fillStyle = "#ff9999" ; else ctx.fillStyle = "#ff0000" ; for(startpos=opos=null,s1=0;s1<len;prevpos=[s0,s1],s1++,pron++,opos=pos) { pos = 10 + 600 * prox[pron] / sum ; if(opos!=null) for(;i<(pos+opos)/2;i++) cmap[i-10] = prevpos ; smap[s0][s1] = 0.5 + Math.floor(pos) ; if(proa[pron]!=null) { y = 10 + 180 * (amax-proa[pron]) / (amax-amin) ; if(startpos==null) { ctx.beginPath() ; ctx.moveTo(pos,y) ; startpos = pos ; } else ctx.lineTo(pos,y) ; } } ctx.lineTo(pos,190) ; ctx.lineTo(startpos,190) ; ctx.closePath() ; ctx.fill() ; } for(;i<610;i++) cmap[i] = prevpos ; // lines if(amax-amin>2500) step = 1000 ; else if(amax-amin>1250) step = 500 ; else step = 100 ; for(i=step*Math.floor(amin/step+1);i<amax;i+=step) { y = 10.5 + Math.floor(180*(amax-i)/(amax-amin)) ; ctx.beginPath() ; ctx.lineWidth = 1 ; ctx.strokeStyle = '#555' ; ctx.moveTo(10,y) ; ctx.lineTo(610,y) ; ctx.stroke() ; ctx.strokeText(i,590,y-2) ; } // cursor curdiv = document.createElement('div') ; curdiv.setAttribute ('style','position:absolute;height:190px;right:0;top:0;width:620px') ; curhandle = curdiv.addEventListener("click",function(e) { var pos = e.clientX - (window.innerWidth-610) ; if((pos-594)*(pos-594)+(e.clientY-16)*(e.clientY-16)<200) { unprofile() ; return ; } if(pos<0) pos = 0 ; else if(pos>600) pos = 600 ; selected = cmap[pos] ; drawsel(0) ; } ) ; body.appendChild(curdiv) ; curcan = null ; procur() ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ------------------------------- unprofile -------------------------------- */ function unprofile() { var i,match,node ; infowindow.close() ; if(altdiv==null) return ; curdiv.removeEventListener('click',curhandle) ; for(match=0,i=body.childNodes.length-1;match==0&&i>=0;i--) { node = body.childNodes[i] ; match = (node==altdiv) ; body.removeChild(node) ; } prox = proa = altdiv = cmap = smap = curdiv = curcan = curhandle = null ; } function reprofile() { if(altdiv!=null) { unprofile() ; profile() ; } } /* -------------------------------- procur ---------------------------------- */ function procur() { if(altdiv==null) return ; var pos = smap[selected[0]][selected[1]] , ctx , i ; if(curcan!=null) curdiv.removeChild(curcan) ; curcan = document.createElement('canvas') ; curcan.setAttribute('width',620) ; curcan.setAttribute('height',200) ; curdiv.appendChild(curcan) ; ctx = curcan.getContext("2d") ; ctx.beginPath() ; ctx.lineWidth = 1 ; ctx.moveTo(pos,10) ; ctx.lineTo(pos,190) ; ctx.stroke() ; // the circle of the 'x' ctx.beginPath() ; ctx.strokeStyle = '#555' ; ctx.fillStyle = 'white' ; ctx.lineWidth = 3 ; ctx.arc(604,16,14.1,0,2*Math.PI,false) ; ctx.stroke() ; ctx.fill() ; for(i=6;i<=26;i+=20) // the two bars of the 'x' { ctx.beginPath() ; ctx.moveTo(594,i) ; ctx.lineTo(614,32-i) ; ctx.stroke() ; } } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------------------------------------------------- */ /* ROUTEINFO IS A MENU WHICH ITSELF SUPPORTS THE COMBINE FUNCTION */ /* -------------------------------------------------------------------------- */ function routeinfo() { var s0,s1,s,d,asc,des,oalt,alt,nlabels,nowpts,i,unsaved,props,spacing,npix ; var maxsep,sep,outoforder,tlast,otime,time,tdist,ttime,ntimes ; infowindow.close() ; props = actions[loadno][4] ; tlast = null ; tdist = ttime = outoforder = 0 ; maxsep = nlabels = npix = des = asc = d = nowpts = ntimes = 0 ; for(s0=0;s0<segments.length;s0++) { nowpts += segments[s0].data.length ; for(oalt=null,s1=0;s1<segments[s0].data.length;otime=time,s1++) { if((alt=segments[s0].data[s1].h)!=null) { if(oalt!=null) { if(alt>oalt) asc += alt-oalt ; else des += oalt - alt ; } oalt = alt ; } if(segments[s0].data[s1].type!=null) nlabels += 1 ; npix += segments[s0].data[s1].photo.length ; time = segments[s0].data[s1].t ; if(time!=null) { time = time.getTime() ; ntimes += 1 ; } if(tlast!=null&&time!=null&&time<tlast) outoforder = 1 ; // out of order if(time!=null) tlast = time ; if(s1) { sep = dist(segments[s0].data[s1-1].pos,segments[s0].data[s1].pos) ; d += sep ; if(sep>maxsep) maxsep = sep ; if(time!=null&&otime!=null) { tdist += sep/1000 ; ttime += (time-otime)/(3600*1000) ; } } } } s = finalbox +'<nobr>Title: <b>'+routetitle+'</b> [' ; s += active + '"retitle()">Edit</span>'+']</nobr><br>' ; if(loadno>0) { s += '<nobr>&nbsp;&nbsp;&nbsp;Last added route' ; if(props.title!=null) s += ' (' + props.title + ')' ; s += ':</nobr><br><nobr>&nbsp;&nbsp;&nbsp;' ; } else s += '<nobr>' ; s += 'Track points on input: ' + props.inputlen ; if(props.optim.already) { s += ' (previously optimised)</nobr>' ; if(nowpts!=props.inputlen) s += '<br>Now ' + nowpts + ' track points' ; } else if(props.optim.ndel==0) { if(nactions==loadno+1) s += ' [' + active + '"optimprompt()">' ; else s += ' [' + inactive ; s += 'Optimise' + neutral + ']</span></nobr>' ; } else s += ', optimised to ' + (props.inputlen-props.optim.ndel) + '</nobr>' ; /* ------------------------------------------------------------------------ */
/*page*/ /* ------------------------------------------------------------------------ */ if(!props.optim.already&&props.inputlen-props.optim.ndel!=nowpts) s += '<br>Now ' + nowpts + ' track points' ; s += '<br>' ; if(outoforder==0) { if(ntimes==0) s += 'No timings provided<br>' ; else { if(ntimes<nowpts) s += (nowpts-ntimes) + ' points have no associated timings ' ; s += '[' + active + '"deltimes()">Discard timings</span>' + neutral + ']<br>' ; } if(tdist>0&&ttime>0) s += 'Average speed = ' + (tdist/ttime).toFixed(1) + ' km/hr<br>' ; } else s += 'Times are out of sequence (will be discarded on download)<br>' ; if(nlabels>0) s += nlabels + ' labelled course point' + (nlabels>1?'s':'') + '<br>' ; if(npix>0) s += npix + ' photo' + (npix>1?'s':'') + '<br>' ; unsaved = unsavedchanges.length ; if(unsaved>0) s += unsaved + ' unsaved change' + (unsaved>1?'s':'') + '<br>' ; if(segments.length>1) s += segments.length + ' segments [' + active + '"combine()">Combine</span>' + neutral + ']<br>' + '<i>Note that segments must be combined before saving</i><br>' ; s += 'Max waypoint separation: '+maxsep.toFixed(0)+'m<br>' ; if(maxsep>=100) s += '<i>Note that separations &gt;100m are illegal on Garmin</i><br>' + '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[</span>' + active + '"extrapts()">' + 'Interpolate extra points</span>]<br>' + neutral ; s += 'Total distance: '+(d/1000).toFixed(3)+'km<br>' ; s += 'Total ascent: '+asc.toFixed(0)+'m<br>' ; s += 'Total descent: '+des.toFixed(0)+'m</div>' ; infowindow.open(s,getbtnpos(0),'routeinfo') ; } /* -------------------------------------------------------------------------- */ function deltimes() { var s0,s1,task=[] ; for(s0=0;s0<segments.length;s0++) for(s1=0;s1<segments[s0].data.length;s1++) if(segments[s0].data[s1].t!=null) { task.push([s0,s1,segments[s0].data[s1].t]) ; segments[s0].data[s1].t = null ; } infowindow.close() ; done(['deltimes',task]) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ------------------------- interpolate extra points ----------------------- */ function extrapts(opt) { var s0,s1,sep,data,n,opos,npos,i,lambda,lox,nlox,taskno,ind ; var task = [ 'extra' , selected[0] , selected[1] ] ; infowindow.close() ; for(nlox=s0=0;s0<segments.length;s0++) for(data=segments[s0].data,s1=1;s1<data.length;s1++) if((sep=dist(opos=data[s1-1].pos,npos=data[s1].pos))>100) { n = Math.floor(sep/95) ; insert(s0,s1,n) ; for(i=0;i<n;i++) { lambda = (i+1) / (n+1) ; data[s1+i].setpos(new google.maps. LatLng(lambda*npos.lat()+(1-lambda)*opos.lat(), lambda*npos.lng()+(1-lambda)*opos.lng())) ; } if(selected[0]==s0&&s1<=selected[1]) selected[1] += n ; task.push([s0,s1,data.slice(s1-1,s1+n+1)]) ; s1 += n ; nlox += n+2 ; } if(nlox==0) return ; done(task) ; xpending.push(task) ; lox = new Array(nlox) ; for(ind=0,taskno=3;taskno<task.length;taskno++) { n = task[taskno][2].length ; for(i=0;i<n;i++) lox[ind++] = task[taskno][2][i].pos ; } elevator.getElevationForLocations( {locations:lox} , function (results,status) { // assume that the results come in sequence, ie. correspond to xpending[0] var task=xpending.shift(),taskno,d0,dn,lambda ; if(status===google.maps.ElevationStatus.OVER_QUERY_LIMIT) alert('No calibration data available: over Google query limit') ; else if(status!==google.maps.ElevationStatus.OK) alert('Calibration error') ; for(ind=0,taskno=3;taskno<task.length;taskno++,ind+=n+2) { n = task[taskno][2].length-2 ; d0 = task[taskno][2][0].h - results[ind].elevation ; dn = task[taskno][2][n+1].h - results[ind+n+1].elevation ; if( dist(task[taskno][2][0].pos,results[ind].location)>5 || dist(task[taskno][2][n+1].pos,results[ind+n+1].location)>5 ) alert('Anomaly with Google elevation results') ; for(i=0;i<n;i++) { lambda = (i+1) / (n+1) ; task[taskno][2][1+i].h = results[ind+1+i].elevation + lambda*dn + (1-lambda)*d0 ; } } } ) ; routeinfo() ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ------------------------------- combine1 --------------------------------- */ function combine1(sa,sb) { var i,ca,cb,calen,cblen,la=null,lb=null,cdup=0 ; undraw(sb) ; disconnect(sb-1) ; calen = segments[sa].data.length ; cblen = segments[sb].data.length ; cb = segments[sb].data[0].pos ; cdup = ( ca = cb.equals(segments[sa].data[calen-1].pos) ) ; if(cdup) { la = segments[sa].data[calen-1] ; lb = segments[sb].data[0] ; segments[sa].data.length = ( calen -= 1 ) ; } if(selected[0]==sb) selected = [ sa , selected[1]+calen ] ; segments[sa].data = segments[sa].data.concat(segments[sb].data) ; return [ cblen , cdup , la , lb ] ; } function combinework() { var task,s0 ; for(task=['combine',segments.length],s0=1;s0<segments.length;s0++) task.push(combine1(0,s0)) ; segments.length = 1 ; return task ; } /* -------------------------------------------------------------------------- */ function combine() { infowindow.close() ; done(combinework()) ; drawsel(1) ; redraw(0) ; blackout(dlbtn) ; } function recombine() { var s0 ; for(s0=1;s0<segments.length;s0++) { undraw(s0) ; combine1(0,s0) ;} segments.length = 1 ; drawsel(1) ; redraw(0) ; blackout(dlbtn) ; } /* -------------------------------------------------------------------------- */ // combine returns [ cblen , cdup , la , lb ] ; function uncombine(task) { var i,j,llen,flag,subtask ; for(flag=0,s0=task[1]-1,i=task.length-1;i>=2;i--,s0--) { subtask = task[i] ; cblen = subtask[0] ; cdup = subtask[1] ; llen = segments[0].data.length ; segments[s0] = { data: segments[0].data.slice(llen-cblen,llen) , route: null , clickhandler: null } ; llen = segments[0].data.length = llen+cdup-cblen ; if(cdup) { segments[0].data[llen-1] = subtask[2] ; segments[s0].data[0] = subtask[3] ; } if(flag==0&&selected[1]>=llen) { selected = [ s0 , selected[1]-llen ] ; flag = 1 ; } } drawsel(1) ; undraw(0) ; for(i=0;i<segments.length;i++) { draw(i) ; connect(i) ; } greyout(dlbtn) ; } /* -------------------------------------------------------------------------- */ /* WPINFO IS A MENU GIVING ACCESS TO THE SETALT FUNCTION */ /* -------------------------------------------------------------------------- */ function wpinfo() { infowindow.close() ; var s0=selected[0],s1=selected[1],alt,nalt,s,x,lat,lng,grad,gradstr,time ; var datum = segments[s0].data[s1] , pos = datum.pos ; lat = pos.lat() ; lng = pos.lng() ; s = finalbox ; if(lat>=0) s += lat.toFixed(5) + '\u00b0 N, ' ; else { lat = -lat ; s += lat.toFixed(5) + '\u00b0 S, ' ; } if(lng>=0) s += lng.toFixed(5) + '\u00b0 E<br>' ; else { lng = -lng ; s += lng.toFixed(5) + '\u00b0 W<br>' ; } x = new LatLon(lat,lng) ; if(lat>49.9&&lat<62&&lng>-12&&lng<2.5&&lat-1.5*lng<75) s += 'OS grid ref: ' + OsGridRef.latLonToOsGrid(x) ; else s += 'UTM coords = ' + x.toUtm() ; s += '<br>' ; alt = segments[s0].data[s1].h ; if(alt!=null) s += 'Altitude: ' + alt.toFixed(0) + 'm ' + active + '"setalt(1)">[Edit]' ; else s += active + '"setalt(0)">Set altitude' ; s += '</span><br>' ; time = segments[s0].data[s1].t ; if(time!=null&&time.getFullYear()>1980) s += 'Date: ' + time.toDateString() + '<br>' + 'Time: ' + time.toTimeString() + '<br>' ; if(datum.type!=null) s += datum.type + ': ' + datum.marker.title + ' [' + active + '"labelprompt()">Edit</span>]<br>' ; if(alt==null||s1==segments[s0].data.length-1) nalt = null ; else { nalt = segments[s0].data[s1+1].h ; if(nalt!=null) x = dist(pos,segments[s0].data[s1+1].pos) ; } if(nalt!=null&&Math.abs(nalt-alt)<x) { grad = 100*Math.asin((nalt-alt)/x) ; gradstr = Math.abs(grad).toFixed(0) ; if(gradstr=='0') s += 'Flat<br>' ; else if(grad>0) s += 'Climb '+gradstr+'%<br>' ; else s += 'Descend: '+gradstr+'%<br>' ; } s += '<span style="font-size:80%">' ; if(segments.length>1) s += 'Segment '+s0+' p' ; else s += 'P' ; s += 'oint ' + s1 + '</span></div>' ; infowindow.open(s,pos,'wpinfo') ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------- setalt ---------------------------------- */ function setalt(edit) { infowindow.close() ; var s0=selected[0],s1=selected[1],x,y=null,oldalt ; oldalt = segments[s0].data[s1].h.toFixed(0) ; if(edit) x = prompt('Enter altitude (m):',oldalt) ; else x = prompt('Enter altitude (m):') ; if(x==null) return ; if(x!='') { y = parseFloat(x) ; if(isNaN(y)) { alert(x+' is not a number') ; return ; } } if(y==null&&oldalt==null) return ; if(y!=null&&Math.abs(y-oldalt)<0.1) return ; segments[s0].data[s1].h = y ; done(['setalt',s0,s1,oldalt,y]) ; reprofile() ; wpinfo() ; } /* -------------------------------------------------------------------------- */ /* THE LABELS ARE ACCESSED FROM THE PEN BUTTON OR BY CLICKING ON THE MAP */ /* -------------------------------------------------------------------------- */ function labelprompt() { var oldcaption='',oldtype,type='Generic',s0=selected[0],s1=selected[1] ; var str , flag = (infowindow.close()=='wpinfo') ; var datum = segments[s0].data[s1] ; oldtype = datum.type ; if(oldtype!=null) oldcaption = datum.marker.title ; if(oldcaption==null) oldcaption = '' ; if(oldtype!=null) type = oldtype ; if(oldtype==null) str = 'Enter' ; else str = 'Modify or delete' ; var caption = window.prompt(str+' label:',oldcaption) ; if(caption==null) { if(flag) wpinfo() ; else walkto(s0,s1,0) ; return ; } else if(caption=='') type = null ; else caption = caption.substring(0,10) ; if(caption==oldcaption) { if(flag) wpinfo() ; else walkto(s0,s1,0) ; return ; } segments[s0].data[s1].setlabel(type,caption) ; done(['editlabel',s0,s1,oldcaption,caption,oldtype,type]) ; if(flag) wpinfo() ; else walkto(s0,s1,0) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ------------------------------ labelcycle -------------------------------- */ function labelcycle() { var s0,s1,datum,oldtype,caption,type,flag=(infowindow.close()=='wpinfo') ; ; for(s0=0;s0<segments.length;s0++) for(s1=0;s1<segments[s0].data.length;s1++) { datum = segments[s0].data[s1] ; if(datum.marker!=this) continue ; oldtype = datum.type ; caption = datum.marker.title ; if(oldtype=='Generic') type = 'Left' ; else if(oldtype=='Left') type = 'Straight' ; else if(oldtype=='Straight') type = 'Right' ; else if(oldtype=='Right') type = 'Danger' ; else type = 'Generic' ; datum.settype(type) ; selected = [s0,s1] ; done(['editlabel',s0,s1,caption,caption,oldtype,type]) ; if(flag) wpinfo() ; return ; } } function photoprompt(e) { var s0=selected[0],s1=selected[1] ; if(e!=null) e.preventDefault() ; var flag = (infowindow.close()=='wpinfo') ; var datum = segments[s0].data[s1] ; var photo = window.prompt('Enter photo name:','') ; if(photo!=null&&photo!='') { done(['editphoto',s0,s1,datum.photo.length,null,photo]) ; datum.addphoto(photo) ; } if(flag) wpinfo() ; else walkto(s0,s1,0) ; } function photoedit(ind) { var s0=selected[0],s1=selected[1],i ; var flag = (infowindow.close()=='wpinfo') ; var datum = segments[s0].data[s1] ; var photo = window.prompt('New photo name:',datum.photo[ind]) ; if(photo!=null&&photo!='') for(i=0;i<datum.photo.length;i++) if(datum.photo[i]==photo) { photo = null ; break ; } if(photo!=null) { if(photo=='') photo = null ; done(['editphoto',s0,s1,ind,datum.photo[ind],photo]) ; datum.setphoto(ind,photo) ; } if(flag) wpinfo() ; else walkto(s0,s1,0) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ----------------------------- enlarge photo ------------------------------ */ function enlarge(ind,i) { document.onkeydown = imgwalk ; infowindow.close() ; imgind = ind ; selinit = [ selected[0] , selected[1] , ind ] ; imgdiv = document.createElement('div') ; imgdiv.setAttribute ('style','position:fixed;width:100%;height:100%;left:0;top:0') ; imghandle = imgdiv.addEventListener("click",imgwalk) ; imgtable = enlargetable(i) ; imgdiv.appendChild(imgtable) ; body.appendChild(imgdiv) ; } /* ------------------------------ image walk -------------------------------- */ function imgwalk(e) { var s0,s1,ind ; if(e.keyCode==16) shift = 1 ; else if(e.keyCode==39) // forwards { e.preventDefault() ; for(s0=selected[0],s1=selected[1],ind=imgind+1;;ind++) { if(ind>=segments[s0].data[s1].photo.length) { ind = 0 ; s1 += 1 ; } if(s1==segments[s0].data.length) { s0 += 1 ; if(s0==segments.length) s0 = 0 ; s1 = 0 ; } if(s0==selinit[0]&&s1==selinit[1]&&ind==selinit[2]) break ; if(imgreplace(s0,s1,ind)) return ; } } else if(e.keyCode==37) // backwards { e.preventDefault() ; for(s0=selected[0],s1=selected[1],ind=imgind-1;;ind--) { if(ind<0) { s1 -= 1 ; ind = null ; } if(s1<0) { if(s0==0) s0 = segments.length-1 ; else s0 -= 1 ; s1 = segments[s0].data.length-1 ; } if(ind==null) ind = segments[s0].data[s1].photo.length - 1 ; if(s0==selinit[0]&&s1==selinit[1]&&ind==selinit[2]) break ; if(imgreplace(s0,s1,ind)) return ; } } document.onkeydown = walk ; imgdiv.removeEventListener('click',imghandle) ; body.removeChild(imgdiv) ; walkto(selected[0],selected[1],0) ; } /* ----------------------------- image replace ------------------------------ */ function imgreplace(s0,s1,ind) { var i ; if(ind>=0&&segments[s0].data[s1].photo.length>ind) if((i=findimg(segments[s0].data[s1].photo[ind]))>=0) { imgdiv.removeChild(imgtable) ; imgtable = enlargetable(i) ; imgdiv.appendChild(imgtable) ; selected = [s0,s1] ; imgind = ind ; return 1 ; } else return 0 ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------- generate image table -------------------------- */ function enlargetable(i) { var w=window.innerWidth,h=window.innerHeight,t,td,tr,span,img,shape=[0,0] ; var k,ibest,ismall,spare,rooturi,srcset,imguri ; spare = (imginfo.list[i].caption==undefined?20:36) ; for(k=0;k<2;k++) shape[k] = imginfo.list[i].shape[k] ; // find the best size to use for(ismall=ibest=null,k=0;k<imginfo.sizes.length;k++) if(imginfo.sizes[k].type==undefined) { if(ismall==null||imginfo.sizes[k].scale<imginfo.sizes[ismall].scale) ismall = k ; r = imginfo.sizes[k].scale / imginfo.sizes[0].scale ; if(shape[0]*r<=w&&shape[1]*r+spare<=h) if(ibest==null||imginfo.sizes[k].scale>imginfo.sizes[ibest].scale) ibest = k ; } // compute the corresponding shape if(ibest==null) ibest = ismall ; r = imginfo.sizes[ibest].scale / imginfo.sizes[0].scale ; for(k=0;k<2;k++) shape[k] = Math.floor(0.5+shape[k]*r) ; // the image source uri if(imginfo.dir==null) rooturi = imginfo.uri ; else rooturi = reluri(imginfo.uri,imginfo.dir) + '/' ; imguri = reluri(rooturi,imginfo.list[i].name) + imginfo.sizes[ibest].suffix + '.jpg' ; // make an srcset for(srcset='',k=0;k<imginfo.sizes.length;k++) if(imginfo.sizes[k].type==undefined) if(imginfo.sizes[k].scale>imginfo.sizes[ibest].scale) { r = imginfo.sizes[k].scale / imginfo.sizes[ibest].scale ; srcset += reluri(rooturi,imginfo.list[i].name) + imginfo.sizes[k].suffix + '.jpg ' + r.toFixed(1) + "x, " ; } // start building the table t = document.createElement('table') ; t.setAttribute('cellspacing',0) ; t.setAttribute('cellpadding',0) ; t.setAttribute('width','100%') ; t.setAttribute('height','100%') ; t.setAttribute('bgcolor','black') ; t.appendChild(blankrow((h-(shape[1]+spare))/2)) ; // title tr = document.createElement('tr') ; r = blankcol((w-shape[0])/2,imginfo.list[i].caption==undefined?2:3) ; tr.appendChild(r) ; td = document.createElement('td') ; td.setAttribute('colspan',2) ; span = document.createElement('span') ; span.setAttribute('style','text-align:left ; font-family:helvetica ; '+ 'margin:0 0 2px; font-size:18px ; color:silver; ' + 'line-height:18px') ; span.appendChild(document.createTextNode(imginfo.list[i].title)) ; td.appendChild(span) ; tr.appendChild(td) ; t.appendChild(tr) ; // the image tr = document.createElement('tr') ; td = document.createElement('td') ; img = document.createElement('img') ; img.setAttribute('src',imguri) ; if(srcset!='') img.setAttribute('srcset',srcset.substring(0,srcset.length-2)) ; img.setAttribute('width',shape[0]) ; img.setAttribute('height',shape[1]) ; td.appendChild(img) ; tr.appendChild(td) ; tr.appendChild(blankcol((w-shape[0])/2,1)) ; t.appendChild(tr) ; // the caption if(imginfo.list[i].caption!=undefined) { tr = document.createElement('tr') ; td = document.createElement('td') ; td.setAttribute('colspan',2) ; span = document.createElement('span') ; span.setAttribute('style','text-align:left ; font-family:helvetica ; '+ 'margin:2px 0 0; font-size:14px ; color:silver; ' + 'line-height:14px') ; span.appendChild(document.createTextNode(imginfo.list[i].caption)) ; td.appendChild(span) ; tr.appendChild(td) ; t.appendChild(tr) ; } t.appendChild(blankrow((h-(shape[1]+spare))/2)) ; return t ; } function blankcol(wid,nrow) { var td = document.createElement('td') ; td.setAttribute('width',wid) ; if(nrow>1) td.setAttribute('rowspan',nrow) ; td.appendChild(document.createTextNode('\u00a0')) ; return td ; } function blankrow(ht) { var td = document.createElement('td') , tr = document.createElement('tr') ; td.setAttribute('colspan',3) ; td.setAttribute('height',ht) ; td.appendChild(document.createTextNode('\u00a0')) ; tr.appendChild(td) ; return tr ; } /* --------------------------------- photo info ----------------------------- */ function phinfo(i) { infowindow.close() ; var s0=selected[0],s1=selected[1],s,shape=[0,0],ind,hind,r,k ; for(k=0;k<2;k++) shape[k] = imginfo.list[i].shape[k] ; s = finalbox + 'Name: ' + imginfo.list[i].name + '<br>Title: ' + imginfo.list[i].title ; for(hind=null,ind=0;ind<i;ind++) if(imginfo.list[ind].name==null) hind = ind ; if(hind!=null) s += "<br>Under \u201c" + imginfo.list[hind].title + "\u201d" ; // how many sizes? for(r=ind=0;ind<imginfo.sizes.length;ind++) if(imginfo.sizes[ind].type==undefined) r += 1 ; s += '<br>Available in ' + r + ' size' ; // print the sizes for(ind=0;ind<imginfo.sizes.length;ind++) if(imginfo.sizes[ind].type==undefined) { if(ind==0&&imginfo.sizes.length==1) s += ': ' ; else if(ind==0) s += 's: ' ; else if(ind==imginfo.sizes.length-1) s += ' and ' ; else s += ', ' ; r = imginfo.sizes[ind].scale/imginfo.sizes[0].scale ; s += Math.floor(0.5+shape[0]*r) + 'x' + Math.floor(0.5+shape[1]*r) ; } if(imginfo.pixpage!=null) s += '<br><a style="cursor:pointer;color:#0000bd;text-decoration:none" '+ 'href="'+reluri(imginfo.uri,imginfo.pixpage)+'" target="_blank">'+ 'Full photo set</a>'+neutral+' (opens in new tab/window)</span>' ; if(imginfo.list[i].retid!=null) { for(hind=null,ind=0;ind<=i;ind++) if(imginfo.list[ind].retpage!=null) hind = imginfo.list[ind].retpage + '.html#' ; if(hind!=null) s += '<br><a style="cursor:pointer;color:#0000bd;'+ 'text-decoration:none" href="'+reluri(imginfo.uri,hind)+ imginfo.list[i].retid+'" target="_blank">'+ 'Route notes</a>'+neutral+' (opens in new tab/window)</span>' ; } infowindow.open(s,segments[s0].data[s1].pos,'phinfo') ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ------------------------- snip: apply scissors -------------------------- */ function snipwork(s0,s1) { var i,k,newlen ; undraw(s0) ; segments.length += 1 ; for(i=segments.length-1;i>s0+1;i--) segments[i] = segments[i-1] ; newlen = segments[s0].data.length - s1 ; segments[s0+1] = new genseg(segments[s0].data.slice(s1)) ; segments[s0+1].dots = segments[s0].dots ; segments[s0+1].dothandler = segments[s0].dothandler ; segments[s0].dots = segments[s0].dothandler = null ; segments[s0].data.length = s1 + 1 ; segments[s0].data[s1] = new datatype(segments[s0].data[s1].pos,segments[s0].data[s1].h) ; draw(s0) ; draw(s0+1) ; for(i=s0+2;i<segments.length;i++) recolour(i) ; selected = [s0+1,0] ; drawsel(1) ; greyout(dlbtn) ; } function snip() { var i,s0=selected[0],s1=selected[1] ; infowindow.close() ; done(['snip',s0,s1]) ; snipwork(s0,s1) ; } /* ------------------------ discard: bin a segment ------------------------- */ function binwork(s0) { var i ; obliterate(s0) ; for(i=s0;i<segments.length-1;i++) segments[i] = segments[i+1] ; segments.length -= 1 ; for(i=s0;i<segments.length;i++) recolour(i) ; connect(s0-1) ; selected[1] = 0 ; if(selected[0]==segments.length) selected[0] = 0 ; drawsel(1) ; if(segments.length==1) blackout(dlbtn) ; } function discard() { var i,s0=selected[0] ; infowindow.close() ; done(['bin',s0,segments[s0]]) ; binwork(s0) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------- actionname ------------------------------ */ function actionname(x) { if(x[0]=='bin') return 'delete segment' ; if(x[0]=='snip') return 'split segment' ; if(x[0]=='editlabel') { if(x[4]=='') return 'delete label' ; else if(x[3]=='') return 'label waypoint' ; else return 'edit label' ; } if(x[0]=='edittitle') return 'edit title' ; if(x[0]=='wpdel') return 'delete waypoint' ; if(x[0]=='move') { if(x[5]) return 'insert waypoint' ; else return 'drag waypoint' ; } if(x[0]=='recal') return 'recalibrate altitudes' ; if(x[0]=='setalt') return 'set waypoint altitude' ; if(x[0]=='resign') return 'change label symbol' ; if(x[0]=='combine') return 'combine '+x[1]+' segments' ; if(x[0]=='revseg') return 'reverse segment' ; if(x[0]=='interpolate') return 'interpolate missing altitudes' ; if(x[0]=='optimise') return 'optimisation' ; if(x[0]=='deltimes') return 'delete times' ; if(x[0]=='editphoto') { if(x[5]==null) return 'delete photo' ; else if(x[4]==null) return 'add photo' ; else return 'change photo' ; } if(x[0]=='extra') return 'interpolate extra points' ; } function actiontype(x) { if( x=='snip'||x=='combine'||x=='interpolate' || x=='optimise'||x=='load' ) return 0 ; else return 1 ; } /* -------------------------------------------------------------------------- */ function done(something) { if( nactions>0 && unsavedchanges.length>0 && something[0]=='editlabel' && actions[nactions-1][0]==something[0] && actions[nactions-1][1]==something[1] // don't merge change with delete && actions[nactions-1][2]==something[2] && something[6]!=null ) { actions[nactions-1][4] = something[4] ; // caption actions[nactions-1][6] = something[6] ; // type } else { actions[nactions++] = something ; donesomething() ; } } function donesomething() { actions.length = nactions ; blackout(undobtn) ; greyout(redobtn) ; if(actiontype(actions[nactions-1][0])!=0) { if(unsavedchanges.length>=3) unsavedchanges.push(null) ; else unsavedchanges.push(actionname(actions[nactions-1])) ; } } /* -------------------------------------------------------------------------- */
/*page*/ /* --------------------------------- undo ---------------------------------- */ function undo() { infowindow.close() ; var opts = active + '"confirmedundo()">Undo ' + actionname(actions[nactions-1])+'</span>' ; infowindow.open(opts,getbtnpos(5),'undo') ; } function confirmedundo() { var i,ano=nactions-1,action=actions[ano][0],s0=actions[ano][1],s1,caption ; var oldcaption,task,ind ; infowindow.close() ; if(action!='revseg'&&action!='interpolate'&&action!='deltimes') s1 = actions[ano][2] ; if(action=='bin') { disconnect(s0-1) ; for(i=segments.length;i>s0;i--) { segments[i] = segments[i-1] ; recolour(i) ; } segments[s0] = s1 ; for(s1=0;s1<segments[s0].data.length;s1++) segments[s0].data[s1].setmap(map) ; draw(s0) ; connect(s0-1) ; connect(s0) ; if(selected[0]>=s0) selected[0] += 1 ; drawsel(1) ; greyout(dlbtn) ; } else if(action=='snip') // undo snip { selected = [ s0 , segments[s0].data.length-1 ] ; combine1(s0,s0+1) ; for(i=s0+1;i<segments.length-1;i++) { segments[i] = segments[i+1] ; recolour(i) ; } segments.length -= 1 ; if(segments.length==1) blackout(dlbtn) ; draw(s0) ; drawsel(1) ; } else if(action=='editlabel') // undo create/edit/delete label segments[s0].data[s1].setlabel(actions[ano][5],actions[ano][3]) ; else if(action=='edittitle') settitle(s0) ; else if(action=='wpdel') // ['wpdel',s0,s1,wpdelwork(s0,s1)] { insert(s0,s1,1) ; segments[s0].data[s1] = actions[ano][3] ; segments[s0].data[s1].setmap(map) ; selected = [s0,s1] ; redrawconnect(s0,s1) ; drawsel(1) ; } else if(action=='move') { if(actions[ano][5]) wpdelwork(s0,s1) ; else move(s0,s1,actions[ano][3]) ; } else if(action=='recal') calwork(s0,-s1) ; else if(action=='setalt') segments[s0].data[s1].h = actions[ano][3] ; else if(action=='combine') uncombine(actions[ano]) ; else if(action=='revseg') revsegwork(s0) ; else if(action=='interpolate') for(i=0;i<s0.length;i++) segments[0].data[s0[i]].h = null ; else if(action=='deltimes') for(i=0;i<s0.length;i++) segments[s0[i][0]].data[s0[i][1]].t = s0[i][2] ; else if(action=='optimise') // [ 'load' , s0 , data.slice() , loadno , props ] { for(ano--;ano>=0&&actions[ano][0]!='load';ano--) ; segments[s0].data = actions[ano][2] ; actions[loadno][4].optim.ndel = 0 ; selected = [s0,0] ; redraw(s0) ; drawsel(1) ; } else if(action=='editphoto') { ind = actions[ano][3] ; if(actions[ano][5]==null) // undo delete for(i=segments[s0].data[s1].photo.length;i>ind;i--) segments[s0].data[s1].photo[i] = segments[s0].data[s1].photo[i-1] ; if(ind>=segments[s0].data[s1].photo.length) segments[s0].data[s1].addphoto(actions[ano][4]) ; else segments[s0].data[s1].setphoto(ind,actions[ano][4]) ; } else if(action=='extra') for(selected=[s0,s1],i=actions[ano].length-1;i>=3;i--) { task = actions[ano][i] segments[task[0]].data.splice(task[1],task[2].length-2) ; } nactions -= 1 ; if(nactions<=1) greyout(undobtn) ; blackout(redobtn) ; if(actiontype(actions[nactions][0])!=0&&unsavedchanges.length>0) unsavedchanges.length -= 1 ; ; if(action=='optimise'||action=='dltimes') routeinfo() ; else if(action=='editphoto'||action=='editlabel') walkto(s0,s1,0) ; } /* --------------------------------- move ----------------------------------- */ function move(s0,s1,pos) { segments[s0].data[s1].setpos(pos) ; redrawconnect(s0,s1) ; drawsel(1) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* --------------------------------- redo ---------------------------------- */ function redo() { infowindow.close() ; var opts = active + '"confirmedredo()">Redo ' + actionname(actions[nactions])+'</span>' ; infowindow.open(opts,getbtnpos(6),'redo') ; } function confirmedredo() { var i,action=actions[nactions][0],s0=actions[nactions][1],s1,caption,a,b,c ; var task,ind,photo ; if(action!='bin'&&action!='revseg'&&action!='interpolate'&&action!='deltimes') s1 = actions[nactions][2] ; infowindow.close() ; if(action=='bin') binwork(s0) ; else if(action=='snip') snipwork(s0,s1) ; else if(action=='editlabel') // redo create/edit/delete label segments[s0].data[s1].setlabel(actions[nactions][6],actions[nactions][4]) ; else if(action=='edittitle') settitle(s1) ; else if(action=='wpdel') wpdelwork(s0,s1) ; else if(action=='move') // ['move',s0,s1,oldpos,newpos,inserted] { if(actions[nactions][5]) insert(s0,s1,1) ; move(s0,s1,actions[nactions][4]) ; } else if(action=='recal') calwork(s0,s1) ; else if(action=='setalt') segments[s0].data[s1].h = actions[nactions][4] ; else if(action=='combine') recombine(actions[nactions]) ; else if(action=='revseg') revsegwork(s0) ; else if(action=='interpolate') interpolatework() ; else if(action=='deltimes') for(i=0;i<s0.length;i++) segments[s0[i][0]].data[s0[i][1]].t = null ; else if(action=='optimise') { result = optimise(segments[s0],actions[nactions][2]) ; actions[loadno][4].optim.ndel = segments[s0].data.length - result.length ; segments[s0].data = result ; selected = [s0,0] ; redraw(s0) ; drawsel(1) ; routeinfo() ; } else if(action=='editphoto') { ind = actions[nactions][3] ; photo = actions[nactions][5] ; if(actions[nactions][4]==null) segments[s0].data[s1].addphoto(photo) ; else segments[s0].data[s1].setphoto(ind,photo) ; } else if(action=='extra') for(selected=[s0,s1],i=3;i<actions[nactions].length;i++) { task = actions[nactions][i] ; a = segments[task[0]].data.slice(0,task[1]) ; b = task[2].slice(1,task[2].length-1) ; c = segments[task[0]].data.slice(task[1]) ; segments[task[0]].data = a.concat(b,c) ; } nactions += 1 ; if(nactions==actions.length) greyout(redobtn) ; blackout(undobtn) ; if(actiontype(actions[nactions-1][0])!=0) unsavedchanges.push(action) ; if(action=='editphoto'||action=='editlabel') walkto(s0,s1,0) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ---------------------------------- dl ----------------------------------- */ function dl() { var i,j,k,noalt,xmldoc,course,lap,datum,filename,blob,track,time,routelen ; var trackpoint,coursepoint,str,flag,clen=segments[0].data.length,tlast,di,dk ; var origlen,ndel,ano,photo,npix,thisuri,maxsep,sep,tdist,ttime,time,otime ; var distance = new Array(clen) , msecs = new Array(clen) ; var valid = new Array(clen) ; for(tlast=null,maxsep=noalt=tdist=ttime=i=flag=0;i<clen;otime=time,i++) { if(segments[0].data[i].h==null) noalt += 1 ; time = segments[0].data[i].t ; if(time!=null) time = time.getTime() ; if(tlast!=null&&time!=null&&time<tlast) flag = 1 ; // out of order if(time!=null) tlast = time ; if(i) { sep = dist(segments[0].data[i-1].pos,segments[0].data[i].pos) ; distance[i] = distance[i-1] + sep ; if(sep>maxsep) maxsep = sep ; if(time!=null&&otime!=null) { tdist += sep ; ttime += time - otime ; } } else distance[i] = 0 ; } routelen = distance[clen-1] ; if(maxsep>100&&!confirm('Some gaps between waypoints are >100m.\n'+ 'This will cause problems if used for navigation in a Garmin 500.\n'+ 'You can hit [OK] and I will proceed anyway, or\n'+ 'you can hit [Cancel] to interpolate extra points\n'+ '(recommended - go to Route Info under the cogwheel).')) return ; if(noalt&&!confirm(noalt+' waypoints have no associated altitudes.\n'+ 'You can hit [OK] and I will interpolate altitudes (not guaranteed), or\n'+ 'you can hit [Cancel] and try again later when the altitudes may be '+ 'available.')) return ; if(routetitle=='Untitled Route') filename = '' ; else { i = routetitle.indexOf(' ') ; if(i<=0) filename = routetitle ; else filename = routetitle.substring(0,i) ; } filename = prompt("Enter filename: ",filename) ; if(filename==null) return ; else if(routetitle=='Untitled Route') settitle(filename) ; i = filename.length ; if(i==0) { filename = 'route' ; i = 5 ; } if(filename.substring(i-4)!='.tcx') filename += '.tcx' ; if(noalt) interpolate() ; unsavedchanges = [] ; // interpolate/extrapolate times in msec for(i=0;i<clen;i++) { valid[i] = 1 ; if(segments[0].data[i].t==null) msecs[i] = null ; else msecs[i] = segments[0].data[i].t.getTime() ; } if(tdist==0||flag!=0) for(i=0;i<clen;i++) msecs[i] = distance[i] * 333 ; else for(i=0;i<clen;i=k) { for(;i<clen&&segments[0].data[i].t!=null;i++) ; // advance to null if(i==clen) break ; for(k=i+1;k<clen&&segments[0].data[k].t==null;k++) ; // advance to non-null for(j=i;j<k;j++) valid[i] = 0 ; if(i==0) for(time=msecs[k],j=i;j<k;j++) msecs[j] = time - (distance[k]-distance[j])*ttime/tdist ; else if(k==clen) for(time=msecs[i-1],j=i;j<clen;j++) msecs[j] = time + (distance[j]-distance[i-1])*ttime/tdist ; else for(j=i,di=distance[i-1],dk=distance[k];j<k;j++) msecs[j] = ( msecs[i-1]*(dk-distance[j]) + msecs[k]*(distance[j]-di) ) / (dk-di) ; } str = '<?xml version="1.0" encoding="UTF-8" standalone="no" ?>\n' + '<TrainingCenterDatabase xmlns="http://www.garmin.com/xmlschemas/' + 'TrainingCenterDatabase/v2"\n' + ' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"\n' + ' xsi:schemaLocation="http://www.garmin.com/' + 'xmlschemas/TrainingCenterDatabase/v2 ' + 'http://www.garmin.com/xmlschemas/TrainingCenterDatabasev2.xsd">\n' ; str += ' <Folders><Courses><CourseFolder Name="Courses">\n' ; str += ' <CourseNameRef><Id>'+routetitle+'</Id></CourseNameRef>\n' ; str += ' </CourseFolder></Courses></Folders>\n<Courses><Course>\n' ; str += ' <Name>'+routetitle+'</Name>\n <Lap>\n' + adddist(routelen) ; time = (msecs[clen-1]-msecs[0]) / 1000 ; str += ' <TotalTimeSeconds>' + time.toFixed(0) + '</TotalTimeSeconds>\n' ; str += addpos('Begin',segments[0].data[0].pos) ; str += addpos('End',segments[0].data[clen-1].pos) ; str += ' <Intensity>Active</Intensity>\n' + ' </Lap>\n <Track>\n' ; // loop over trackpoints for(npix=i=0;i<segments[0].data.length;i++) { str += ' <Trackpoint>\n' + addpos('',segments[0].data[i].pos) ; str += adddist(distance[i]) + addalt(segments[0].data[i].h) ; time = new Date(msecs[i]) ; str += ' <Time>' + time.toISOString() + '</Time>\n' ; if(segments[0].data[i].photo.length>0) { str += ' <Extensions><Photo>' ; for(k=0;k<segments[0].data[i].photo.length;k++) { if(k) str += ' ' ; str += segments[0].data[i].photo[k] ; } str += '</Photo></Extensions>\n' ; npix += segments[0].data[i].photo.length ; } if(valid[i]==0) str += ' <Extensions><ValidTime>False</ValidTime></Extensions>\n' ; str += ' <SensorState>Absent</SensorState>\n </Trackpoint>\n' ; } str += ' </Track>\n\n' ; // record optimisation and photo list for(ano=-1,ndel=origlen=0,i=loadno;i>=0;i=actions[i][3]) { ndel += actions[i][4].optim.ndel ; origlen += actions[i][4].optim.origlen ; if(actions[i][4].optim.parms!=null&&ano==-1) ano = i ; } if(ndel||(npix>0&&imginfo.status=='ready')) str += ' <Extensions>\n' ; if(ndel) { str += ' <Optimised from="'+origlen+'" to="'+(origlen-ndel) ; if(ano>=0) str += '" tol="'+actions[ano][4].optim.parms.tol.toFixed(0)+ '" maxsep="'+actions[ano][4].optim.parms.maxsep.toFixed(0)+ '" wppenalty="'+actions[ano][4].optim.parms.wppenalty.toFixed(0)+ '" vweight="'+actions[ano][4].optim.parms.vweight.toFixed(1) ; str += '"/>\n' } if(npix>0&&imginfo.status=='ready') { thisuri = imginfo.uri ; if(imginfo.type!='tcx') // vice 'uri' { thisuri = document.URL ; if((i=thisuri.lastIndexOf('?'))>=0) thisuri = thisuri.substring(0,i) ; thisuri = reluri(thisuri,imginfo.uri) ; } str += ' <PhotoList src="'+thisuri+'"/>\n' ; } if(ndel||(npix>0&&imginfo.status=='ready')) str += ' </Extensions>\n\n' ; // finally loop over coursepoints for(i=0;i<segments[0].data.length;i++) { datum = segments[0].data[i] ; if(datum.type==null) continue ; str += ' <CoursePoint><Name>'+datum.marker.title+'</Name>\n' ; str += ' <PointType>'+datum.type+'</PointType>\n' ; str += addpos('',segments[0].data[i].pos) + addalt(segments[0].data[i].h) ; time = new Date(msecs[i]) ; str += ' <Time>' + time.toISOString() + '</Time>\n </CoursePoint>\n' ; } str += '</Course></Courses></TrainingCenterDatabase>\n' ; blob = new Blob([str],{type: "text/plain;charset=utf-8"}) ; saveAs(blob,filename) ; } /* -------------------------------------------------------------------------- */ function addpos(tag,pos) { var str = ' <'+tag+'Position>\n <LatitudeDegrees>' ; str += pos.lat().toFixed(5)+'</LatitudeDegrees>\n <LongitudeDegrees>' ; str += pos.lng().toFixed(5)+'</LongitudeDegrees>\n </'+tag+'Position>\n' ; return str ; } function addalt(x) { return ' <AltitudeMeters>' + x.toFixed(0) + '</AltitudeMeters>\n' ; } function adddist(x) { return ' <DistanceMeters>' + x.toFixed(0) + '</DistanceMeters>\n' ; } /* ------------------------------ interpolate ------------------------------- */ function interpolate() { infowindow.close() ; var response = interpolatework() ; if(response.length>0) done([ 'interpolate' , response ]) ; } function interpolatework() { var i,j,k,response=[],x,y,distance,sum ; var a=segments[0].data,len=a.length ; for(i=0;i<len;i=j) { for(;i<len&&a[i].h!=null;i++) ; // advance to null for(j=i+1;j<len&&a[j].h==null;j++) response.push(j) ; // advance to non-null if(i==0) { for(y=a[j].h;i<j;i++) a[i].h = y ; continue ; } if(j==len) { for(x=a[i-1].h;i<j;i++) a[i].h = x ; continue ; } distance = new Array(1+j-i) ; for(sum=k=0;k<=j-i;k++) sum = distance[k] = sum + dist(a[i+k-1].pos,a[i+k].pos) ; for(x=a[i-1].h,y=a[j].h,k=0;k<j-i;k++) a[i+k].h = ( x*(sum-distance[i]) + y*distance[i] ) / sum ; } } /* -------------------------------------------------------------------------- */

Archived from pix.html

document.write ( '<style>body{background:black;margin:0;font-family:arial;color:silver}' + 'a{text-decoration:none;}a:link{color:#66aaaa}' + 'a:visited{color:#cc3388}a:active{color:#404040}' + 'td.nav{text-align:center;vertical-align:middle;' + 'font-size:16px;line-height:16px;width:16px;height:16px}</style>' ) ; if((typeof pixlib)=="undefined") document.write ('<scri'+'pt src="http://www.masterlyinactivity.com/pixlib.js"></scri'+'pt>'); var thispage,full=0,nload=0,here,lind,rind,blink,pagetitle,retpage ; var nitem=[],thumbshape=[],maxthumb,home,imagedir=null ; var llink,rlink,ulink,dlink,prevind,fromnotes,fullname ; /* -------------------------------------------------------------------------- */ // pixresize responds to a window resize as follows: // o. update 'full' if necessary // o. for a table view, if the number of columns can change, change it; // o. for an image view, if the new size permits a larger or smaller image, // enlarge or reduce; // o. or if the window is expanding or contracting but hasn't changed size // enough to change the desired image, anticipatively preload a larger or // smaller one. function pixresize() { if((full=queryfullscreen())==0&&here.full!=0&&fullname!=here.name) location.href = thispage + '?' + here.name + (fromnotes?'+n':'') ; if(here.name!=null) resize() else if(full!=here.full||here.ncol!=(k=getncol())) tabulate() ; } /* ------------------- construct a link to an image page -------------------- */ function piclink(ind) { if(full) return 'javascript:display("' + list[ind].name + '")' ; else return thispage + '?' + list[ind].name + (fromnotes?'+n':'') ; } /* ----- getncol finds the number of table columns which fit the screen ----- */ function getncol() { var maxcol,ncol,k,ind ; // 17 pix for scrollbar, 19 pix for margin, border... maxcol = Math.floor((window.innerWidth-17)/(maxthumb[0]+19)) ; if(maxcol<1) maxcol = 1 ; for(ncol=ind=0;ind<nitem.length;ind++) { k = Math.ceil(nitem[ind]/Math.ceil(nitem[ind]/maxcol)) ; if(k>ncol) ncol = k ; } return ncol ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------- create links line of the table page ----------------- */ function linkp() { var p,a,span ; p = document.createElement("p") ; p.setAttribute('style',"text-align:center;font-size:100%;margin:6px") ; if(retpage!=null) { a = document.createElement('a') ; a.setAttribute('href',list[retpage].retpage+'.html') ; a.setAttribute('style',"font-weight:normal") ; a.appendChild(document.createTextNode('notes')) ; p.appendChild(a) ; p.appendChild(document.createTextNode(' : ')) ; } if(home!=undefined&&home!=null) { a = document.createElement('a') ; a.setAttribute('href',home) ; a.setAttribute('style',"font-weight:normal") ; a.appendChild(document.createTextNode('home')) ; p.appendChild(a) ; } if(full==0&&querycanfullscreen()) { p.appendChild(document.createTextNode(' : ')) ; a = document.createElement('a') ; a.setAttribute('style',"font-weight:normal") ; a.setAttribute('href','javascript:{}') ; a.setAttribute('onclick','enterfullscreen()') ; a.appendChild(document.createTextNode('full screen')) ; p.appendChild(a) ; span = document.createElement('span') ; span.setAttribute('style',"color:gray") ; span.appendChild(document.createTextNode(' [f key] ')) ; p.appendChild(span) ; } return p ; } /* -------------------------------------------------------------------------- */ function display(name) { var titlestr,body,title,i,k,ind,fetchitem,s ; for(ind=list.length-1; ind>=0&&(list[ind].name==undefined||list[ind].name!=name); ind--) ; body = document.getElementsByTagName("body")[0] ; if(name!=here.name) { title = document.getElementsByTagName("title")[0] ; while(title.childNodes.length>1) title.removeChild(title.firstChild) ; titlestr = document.createTextNode(list[ind].title) if(title.childNodes.length==0) title.appendChild(titlestr) ; else title.replaceChild(titlestr,title.childNodes[0]) ; } here = { name:name , ind:ind , ncol:0 , full:full } ; // navigation links: < for(lind=ind-1; lind>=0&&(list[lind].name==undefined||list[lind].display=='none'); lind--) ; if(lind<0) llink = null ; else llink = piclink(lind) ; // navigation links: > for(rind=ind+1; rind<list.length && (list[rind].name==undefined||list[rind].display=='none'); rind++) ; if(rind==list.length) rlink = null ; else rlink = piclink(rind) ; // navigation links: return if(fromnotes==0) { if(full) blink = 'javascript:tabulate()' ; else blink = thispage ; s = "table" ; } else { for(i=ind;i>=0&&list[i].retpage==undefined;i--) ; if(i<0) alert('no return page for '+list[ind].name) ; blink = list[i].retpage+'.html' ; for(k=ind;k>=i&&list[k].retid==undefined;k--) ; if(k>=i) blink += '#' + list[k].retid ; s = "notes" ; } // decide which image if any to prefetch if(prevind!=null&&prevind>ind&&lind>=0) k = lind ; else if((prevind==null||prevind<=ind)&&rind<list.length) k = rind ; else k = null ; if(k==null) fetchitem = null ; else fetchitem = list[k] ; gendisplay(body,list[ind],sizes,llink,blink,rlink,s,fetchitem) ; } /* ------------------------ navigate using the arrow keys ------------------- */ function navigate(e) { if(e.keyCode==37&&lind>=0) // left arrow key { e.preventDefault() ; if(full==0) location.href= llink ; else { prevind = lind+1 ; display(list[lind].name) ; } } else if(e.keyCode==39&&rind<list.length) // right arrow key { e.preventDefault() ; if(full==0) location.href= rlink ; else { prevind = rind-1 ; display(list[rind].name) ; } } else if(e.keyCode==13&&here.name!=null) // return { e.preventDefault() ; if(full&&fromnotes==0) tabulate() ; else location.href = blink ; } else if(e.keyCode==40) { e.preventDefault() ; if(here.ncol==0) reduce() ; } else if(e.keyCode==38) { e.preventDefault() ; if(here.ncol==0) enlarge() ; } else if(e.keyCode==70&&full==0) // 'f' (full screen) { e.preventDefault() ; fullname = here.name ; enterfullscreen() ; full = 1 ; } } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------------------------------------------------- */ function tabulate() { var ind,ncol,p,body,table,tr,td,a,img,trflag,k,s,opts,thind=thumbind(sizes) ; var prefetch,nimg,padflag,k,num ; rind = list.length ; lind = -1 ; body = document.getElementsByTagName("body")[0] ; while(body.firstChild) body.removeChild(body.firstChild) ; ncol = getncol() ; here = { name:null , ind:null , ncol:ncol , full:full } ; // prefetch will be performed when nimg thumbs have been loaded for(nimg=ind=0;ind<list.length;ind++) if(list[ind].name!=undefined&&list[ind].display!='none') nimg += 1 ; p = document.createElement("p") ; p.setAttribute('style',"text-align:center;font-size:140%;margin:2px 0 6px") ; p.appendChild(document.createTextNode(pagetitle)) ; body.appendChild(p) ; body.appendChild(linkp()) ; table = document.createElement('table') ; table.setAttribute('cellspacing','0') ; table.setAttribute('cellpadding','0') ; table.setAttribute('align','center') ; for(s='',ind=0;s==''&&ind<list.length;ind++) if(list[ind].name==undefined) s = "border-bottom:1px solid #444;padding-bottom:4px;" ; table.setAttribute('style',s+'margin:0px auto') ; for(prefetch=null,nload=trflag=colno=ind=0;ind<list.length;ind++) if(list[ind].display!='none') { if(0==colno%ncol||list[ind].name==undefined) { if(trflag>0) table.appendChild(tr) ; tr = document.createElement('tr') ; trflag = 0 ; } td = document.createElement('td') ; if(list[ind].name==undefined) { td.setAttribute("style","border-top:1px solid #444") ; td.setAttribute('align','left') ; td.setAttribute('colspan',ncol) ; p = document.createElement("p") ; p.setAttribute("style","font-size:110%;padding-top:6px") ; if(list[ind].gps==undefined) p.appendChild(document.createTextNode(list[ind].title)) ; else { p.appendChild(document.createTextNode(list[ind].title+' : ')) ; a = document.createElement('a') ; a.setAttribute('href',list[ind].gps) ; a.setAttribute('style','font-size:90%;font-weight:normal') ; a.appendChild(document.createTextNode('[GPS track]')) ; p.appendChild(a) ; } td.appendChild(p) ; tr.appendChild(td) ; table.appendChild(tr) ; tr = document.createElement('tr') ; colno = trflag = 0 ; continue ; } lind = ind ; llink = piclink(lind) ; if(rind==list.length) { rind = ind ; rlink = piclink(rind) ; } // extra padding at the bottom before a title row if(0==colno%ncol) { padflag = 4 ; for(num=0,k=ind;k<list.length&&num<ncol&&list[k].name!=undefined;k++) if(list[k].display!='none') num += 1 ; if(k<list.length&&num<=ncol) { padflag = 8 ; if(ncol>5) padflag += 2*(ncol-5) ; } } td.setAttribute('align','center') ; if(list[ind].display=='|'&&colno%ncol>0) td.setAttribute('style','border-left:1px solid #444') ; a = document.createElement('a') ; a.setAttribute('href',piclink(ind)) ; a.setAttribute("class","box") ; a.setAttribute("title",list[ind].title) ; img = document.createElement('img') ; img.setAttribute('src',jpg(list[ind],sizes,thind)) ; img.setAttribute('width',list[ind].thumbshape[0]) ; img.setAttribute('height',list[ind].thumbshape[1]) ; img.setAttribute('border',1) ; img.onload = function() { nload += 1 ; if(nload==nimg&&prefetch!=null) preload(list[prefetch],sizes) ; } ; // vertical bar: margin is t-r-b-l or t-lr-b if(colno%ncol>0&&list[ind].display!='|') img.setAttribute('style',"padding:4px;margin:4px 4px "+padflag+"px 5px") ; else img.setAttribute('style',"padding:4px;margin:4px 4px "+padflag+"px") ; a.appendChild(img) ; td.appendChild(a) ; tr.appendChild(td) ; trflag = 1 ; if(prefetch==null) prefetch = ind ; colno += 1 ; } table.appendChild(tr) ; body.appendChild(table) ; body.appendChild(linkp()) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------------------------------------------------- */ function pix(h) { var ind,query,opts="",k,r,prev,fullpage=location.href ; thispage = fullpage ; home = h ; pagetitle = document.getElementsByTagName("title")[0].textContent ; prevind = null ; here = { name:null , ind:null , ncol:0 , full:0 } ; ind = thispage.lastIndexOf('/') ; if(ind>=0) thispage = thispage.substring(ind+1) ; // set up the array nitem containing the number of items in each block for(nitem=[],ind=-1;ind<list.length;ind=k) { for(k=ind+1;k<list.length&&list[k].name!=undefined;k++) ; if(k>ind+1) nitem.push(k-(ind+1)) ; } maxthumb = setthumbshape(list,sizes,thumbshape,imagedir) ; query = null ; ind = thispage.indexOf('?') ; if(ind>=0) { query = thispage.substring(ind+1) ; // 'garden+t2' thispage = thispage.substring(0,ind) ; fullpage = fullpage.substring(0,fullpage.length-1-query.length) ; ind = query.indexOf('+') ; if(ind>=0) { opts = query.substring(ind+1) ; query = query.substring(0,ind) ; } } else { query = null ; ind = -1 ; } if(query!=null) for(ind=0;ind<list.length&&(list[ind].name!=query);ind++) ; for(k=0; k<list.length&&(list[k].name==undefined||list[k].retpage==undefined); k++) ; if(k<list.length) retpage = k ; else retpage = null ; if(ind>=0&&ind<list.length) // return to table if there is no retpage { // all the following code is finding whether we're stepping backwards k = document.referrer.lastIndexOf('/') ; if(k>=0) { prev = document.referrer.substring(k+1) ; k = prev.indexOf('?') ; } if(k>=0) { prev = prev.substring(k+1) ; k = prev.indexOf('+') ; if(k>=0) prev = prev.substring(0,k) ; for(k=0;k<list.length&&list[k].name!=prev;k++) ; if(k<list.length) prevind = k ; } else if(fullpage==document.referrer) { for(k=ind+1;k<list.length&&list[ind].name==undefined;k++) ; if(k==list.length) prevind = list.length ; } } window.onresize = pixresize ; document.onkeydown = navigate ; document.addEventListener('touchstart',startswipe,false) ; document.addEventListener('touchend',endswipe,false) ; if(ind<0||ind>=list.length) { fromnotes = 0 ; tabulate() ; } else { fromnotes = (opts.charAt(0)=='n'&&retpage!=null) ; display(query) ; } } /* ---------------------------------- swipes -------------------------------- */ var xloc=null,yloc=null,fingersep=null ; function startswipe(e) { var x0=e.touches[0].clientX,y0=e.touches[0].clientY,x1,y1 ; if(e.touches.length==1) { xloc = x0 ; yloc = y0 ; } else if(e.touches.length==2) { x1 = e.touches[1].clientX ; y1 = e.touches[1].clientY ; fingersep = Math.sqrt((x1-x0)*(x1-x0)+(y1-y0)*(y1-y0)) ; } } function endswipe(e) { var x0=e.touches[0].clientX,y0=e.touches[0].clientY,x1,y1 ; if(e.touches.length==1) { if(xloc==null||yloc==null) return ; x0 -= xloc ; y0 -= yloc ; xloc = yloc = null ; if(Math.abs(x0)>100&&Math.abs(y0)<100) { if(x0>0) navigate({ keyCode:39 , preventDefault:function(){} }) ; else navigate({ keyCode:37 , preventDefault:function(){} }) ; } } else if(e.touches.length==2&&here.ncol==0) { if(fingersep==null) return ; x1 = e.touches[1].clientX ; y1 = e.touches[1].clientY ; x1 = Math.sqrt((x1-x0)*(x1-x0)+(y1-y0)*(y1-y0)) ; x0 = fingersep ; fingersep = null ; if(x1-x0>50) enlarge() ; else if(x0-x1>50) reduce() ; } }

Archived from pix.html

var pixlib = 1 ; /* -------------------------------------------------------------------------- */ function enterfullscreen() { if(document.documentElement.requestFullscreen) document.documentElement.requestFullscreen() ; else if(document.documentElement.mozRequestFullScreen) document.documentElement.mozRequestFullScreen() ; else if(document.documentElement.webkitRequestFullscreen) document.documentElement.webkitRequestFullscreen() ; else if(document.documentElement.msRequestFullscreen) document.documentElement.msRequestFullscreen() ; } function queryfullscreen() { if(document.fullScreen||document.mozFullScreen||document.webkitIsFullScreen) return 1 ; else return 0 ; } function querycanfullscreen() { if ( document.documentElement.requestFullscreen || document.documentElement.mozRequestFullScreen || document.documentElement.webkitRequestFullscreen || document.documentElement.msRequestFullscreen ) return 1 ; else return 0 ; } /* -------------------------------------------------------------------------- */ function thumbind(sizes) { var ind ; for(ind=0;ind<sizes.length&&sizes[ind].type!='thumb';ind++) ; if(ind==sizes.length) { alert('no sizes entry of type "thumb"') ; throw '' ; } return ind ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ---- fill in missing thumbs and raws, return maximum thumb dimensions ---- */ function setthumbshape(list,sizes,thumbshape,imagedir) { var ind,r0,r1,maxthumb,k,umax,doraw ; ind = thumbind(sizes) ; if(thumbshape.length!=2) { if(sizes[ind].scale>0) r0 = sizes[ind].scale / sizes[0].scale ; else { alert('thumbs have size '+sizes[ind].scale) ; throw '' ; } } for(doraw=r1=ind=0;ind<sizes.length&&sizes[ind].type!='raw';ind++) ; if(ind<sizes.length) { r1 = sizes[ind].scale / sizes[0].scale ; doraw = 1 ; } for(maxthumb=[0,0],ind=0;ind<list.length;ind++) if(list[ind].name!=undefined) { if(list[ind].thumbshape==undefined) { if(thumbshape.length==2) list[ind].thumbshape = thumbshape ; else for(list[ind].thumbshape=[0,0],k=0;k<2;k++) list[ind].thumbshape[k] = Math.floor(0.5+list[ind].shape[k]*r0) ; } for(k=0;k<2;k++) if(list[ind].thumbshape[k]>maxthumb[k]) maxthumb[k] = list[ind].thumbshape[k] ; if(doraw&&list[ind].rawshape==undefined) for(list[ind].rawshape=[0,0],k=0;k<2;k++) list[ind].rawshape[k] = Math.floor(0.5+list[ind].shape[k]*r1) ; if(imagedir==null) list[ind].filename = list[ind].name ; else list[ind].filename = imagedir + '/' + list[ind].name ; } // fill in the fontsize field in sizes for(R=k=ind=0;ind<sizes.length;ind++) if(sizes[ind].type==undefined) { R += Math.log(sizes[ind].scale) ; k += 1 ; } R /= k ; for(ind=0;ind<sizes.length;ind++) if(sizes[ind].type==undefined&&sizes[ind].fontsize==undefined) sizes[ind].fontsize = 16 * Math.exp((Math.log(sizes[ind].scale)-R)/3) ; for(umax=null,ind=0;ind<sizes.length;ind++) if(sizes[ind].type==undefined) if(umax==null||sizes[ind].fontsize>umax) umax = sizes[ind].fontsize ; for(ind=0;ind<sizes.length;ind++) if(sizes[ind].type=='raw'&&sizes[ind].fontsize==undefined) sizes[ind].fontsize = umax ; return maxthumb ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ----------------------------- image functions ---------------------------- */ function imgsize(item,sizes,sizeno) { if(sizes[sizeno].type=='raw') return item.rawshape ; return [ Math.floor(0.5+item.shape[0]*sizes[sizeno].scale/sizes[0].scale) , Math.floor(0.5+item.shape[1]*sizes[sizeno].scale/sizes[0].scale) ] ; } /* ----- sparepix finds the margins left if item is displayed at usesize ---- */ function sparepix(item,sizes,sizeno) { var shape=imgsize(item,sizes,sizeno),w=shape[0],h=shape[1],r ; h += Math.floor(0.5+1.25*sizes[sizeno].fontsize) + 2 ; if(item.caption!=undefined) h += Math.floor(0.5+sizes[sizeno].fontsize) + 2 ; r = [ window.innerHeight-50-h , window.innerHeight-h ] ; if(window.innerWidth-w<r[0]) r[0] = window.innerWidth-w ; if(window.innerWidth-w-50<r[1]) r[1] = window.innerWidth-w-50 ; return r ; } /* ---------------------- construct the jpg path name ----------------------- */ function jpg(item,sizes,sizeno) { return item.filename + sizes[sizeno].suffix + '.jpg' ; } /* ------------------- generate the srcset for an image --------------------- */ function srcset(item,sizes,sizeno) { var i,s="" ; for(i=0;i<sizes.length;i++) if(sizes[i].type==undefined) if(sizes[i].scale>sizes[sizeno].scale) s += jpg(item,sizes,i) + ' ' + (sizes[i].scale/sizes[sizeno].scale).toFixed(1) + "x, " ; if(s!='') s = s.substring(0,s.length-2) ; return s ; } /* -------------------- preload item for the given size --------------------- */ function preload(item,sizes,sizeno,loadaction) { var img = new Image(),s ; if(sizeno==undefined||sizeno<0) sizeno = getsize(item,sizes) ; if((s=srcset(item,sizes,sizeno))!='') img.srcset = s ; if(loadaction!=undefined) img.onload = loadaction ; img.src = jpg(item,sizes,sizeno) ; // console.log(img.src+(s==""?"":' ['+s+']')) ; } /* ------- getsize finds the largest image size which fits the screen ------- */ function getsize(item,sizes,loadstatus,thresh) { var i,ibest,ismall,spare ; for(ismall=ibest=null,i=0;i<sizes.length;i++) if(sizes[i].type==undefined&&(loadstatus==undefined||loadstatus[i]>=thresh)) { if(ismall==null||sizes[i].scale<sizes[ismall].scale) ismall = i ; spare = sparepix(item,sizes,i) ; if(spare[0]>=0||spare[1]>=0) if(ibest==null||sizes[i].scale>sizes[ibest].scale) ibest = i ; } if(ibest==null) return ismall ; else return ibest ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ------- setimgpos sets the image position according to window size ------- */ function setimgpos(parms,imgshape) { var space,imgw=imgshape[0],imgh=imgshape[1],x,y,s,pad=parms.headh+parms.caph ; // set space to the amount of room we've got for the image div space = [ window.innerWidth , window.innerHeight-parms.headh-parms.caph ] ; if(parms.portrait) space[0] -= 50 ; else space[1] -= 50 ; // set imgw and imgh to the desired dimensions of the image div if(imgh>space[1]) imgw += 20 ; // generous scrollbar if(imgw>space[0]) imgh += 20 ; // set (x,y) to the coords of the main div x = (window.innerWidth-imgw)/2 ; y = (window.innerHeight-imgh)/2 - parms.headh; if(parms.portrait) { if(x<50) x = 50 ; if(y<0) y = 0 ; } else { if(x<0) x = 0 ; if(y<50) y = 50 ; } x = Math.floor(x) ; y = Math.floor(y) ; // now constrain imgw, imgh by the window size if(imgw>window.innerWidth-x) imgw = window.innerWidth - x ; if(imgh>window.innerHeight-y-pad) imgh = window.innerHeight-y-pad ; s = 'position:absolute;left:'+x+'px;width:'+(window.innerWidth-x)+'px;top:' ; s += y+'px;height:'+(pad+imgh)+'px;overflow:hidden' ; parms.maindiv.setAttribute('style',s) ; s = 'position:absolute;left:0;width:'+imgw+'px;' ; s += 'top:'+parms.headh+'px;height:'+imgh+'px;overflow:auto' ; parms.imgdiv.setAttribute('style',s) ; } /* ---- adjust element attributes in accordance with a change of img size --- */ function setimg(parms,item,sizes,sizeno,fetchitem) { var font,spare,imgshape,img,s ; if(sizeno==null) return ; enredcell(parms.enlink,sizes,sizeno,2) ; enredcell(parms.redlink,sizes,sizeno,-1) ; spare = sparepix(item,sizes,sizeno) ; imgshape = imgsize(item,sizes,sizeno) ; font = sizes[sizeno].fontsize ; parms.headh = 2 + Math.floor(0.5+1.25*font) ; parms.caph = (item.caption==undefined)?0:2+Math.floor(0.5+font) ; parms.portrait = spare[0]<spare[1] ; // create the image for the new size img = document.createElement('img'),s ; img.setAttribute('width',imgshape[0]) ; img.setAttribute('height',imgshape[1]) ; if((s=srcset(item,sizes,sizeno))!='') img.setAttribute("srcset",s) ; if(fetchitem!=undefined&&fetchitem!=null) img.onload = function() { preload(fetchitem,sizes) ; } ; img.setAttribute("src",jpg(item,sizes,sizeno)) ; if(parms.img==null) parms.imgdiv.appendChild(img) ; else parms.imgdiv.replaceChild(img,parms.img) ; parms.img = img ; setimgpos(parms,imgshape) ; return sizeno ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ------------ rescale finds the next larger/next smaller image ------------ */ function rescale(sizes,sizeno,dir) { var i,ind ; if(dir<0) { for(ind=null,i=0;i<sizes.length;i++) if(sizes[i].type==undefined) if(sizes[i].scale<sizes[sizeno].scale||sizes[sizeno].type=='raw') if(ind==null||sizes[i].scale>sizes[ind].scale) ind = i ; return ind ; } if(sizes[sizeno].type=='raw') return null ; for(ind=null,i=0;i<sizes.length;i++) if(sizes[i].type==undefined||(sizes[i].type=='raw'&&dir==2)) if( sizes[i].scale>sizes[sizeno].scale || sizes[i].type=='raw' ) { if(ind==null) ind = i ; else if(sizes[ind].type=='raw') ind = i ; else if(sizes[i].scale<sizes[ind].scale&&sizes[i].type!='raw') ind = i ; } return ind ; } /* --------------- reposition image in response to a window resize ---------- */ function resizesub(iparms,idparms) { var k2,k0,dir,flag=0,imgshape ; k2 = getsize(iparms.item,iparms.sizes,iparms.loadstatus,2) ; k0 = getsize(iparms.item,iparms.sizes) ; // there's better size already loaded if(iparms.holdsize==0&&k2!=iparms.sizeno) { iparms.sizeno = setimg(idparms,iparms.item,iparms.sizes,k2) ; return ; } // there's a better size not yet loaded else if(iparms.holdsize==0&&k0!=iparms.sizeno&&iparms.loadstatus[k0]==0) flag = 1 ; else if(iparms.holdsize!=0&&k0==iparms.sizeno) iparms.holdsize = 0 ; else if(iparms.holdsize==0&&iparms.window!=null) { if( window.innerWidth>=iparms.window[0] && window.innerHeight>=iparms.window[1] ) dir = 1 ; else if( window.innerWidth<=iparms.window[0] && window.innerHeight<=iparms.window[1] ) dir = -1 ; else dir = null ; if(dir!=null&&(k0=rescale(iparms.sizes,iparms.sizeno,dir))!=null) if(iparms.loadstatus[k0]==0) flag = 1 ; } if(flag) { iparms.window = null ; iparms.loadstatus[k0] = 1 ; preload(iparms.item,iparms.sizes,k0,genloadhandler(iparms.loadstatus,k0)) ; } // redisplay if portrait<->landscape makes a fit possible, else redraw imgshape = imgsize(iparms.item,iparms.sizes,iparms.sizeno) ; spare = sparepix(iparms.item,iparms.sizes,iparms.sizeno) ; if(idparms.portrait!=(spare[0]<spare[1])&&(spare[0]<0)!=(spare[1]<0)) idparms.portrait = 1-idparms.portrait ; setimgpos(idparms,imgsize(iparms.item,iparms.sizes,iparms.sizeno)) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ---------------- create a div containing an image title ------------------ */ function maketextdiv(title) { var div=document.createElement('div') ; div.appendChild(document.createTextNode(title)) ; return div ; } /* --------------------- create/reset enlarge/reduce icon ------------------- */ function enredcell(td,sizes,sizeno,dir) { var a ; while(td.firstChild) td.removeChild(td.firstChild) ; if(rescale(sizes,sizeno,dir)!=null) { a = document.createElement('a') ; a.setAttribute('href',dir>0?'javascript:enlarge()':'javascript:reduce()') ; a.setAttribute("title",dir>0?"enlarge [\u2191 key]":"reduce [\u2193 key]") ; a.appendChild(document.createTextNode(dir>0?'\u2295':'\u2296')) ; td.appendChild(a) ; } } /* --------------------- create a cell for navigation icon ------------------ */ function navcell(link,dir,string) { var a,td=document.createElement('td'),s ; td.setAttribute("class","nav") ; s = (dir>=0?'right':'left') + (dir==0?'':';font-size:20px') ; td.setAttribute("style","text-align:"+s) ; if(dir!=0) td.setAttribute("rowspan",3) ; if(link!=null) { a = document.createElement('a') ; a.setAttribute('href',link) ; if(dir>0) s = "next [\u2192 key]" ; else if(dir==0) s = "back to " + string + " [\u21b5 key]" ; else s = "prev [\u2190 key]" ; a.setAttribute("title",s) ; if(dir>0) s = '>' ; else if(dir==0) s = '\u21b5' ; else s = '<' ; a.appendChild(document.createTextNode(s)) ; td.appendChild(a) ; } return td ; } /* -------------------------------------------------------------------------- */ var drawparms=null,itemparms=null ; function setsize(k) { if(itemparms==null||(k=rescale(itemparms.sizes,itemparms.sizeno,k))==null) return ; itemparms.sizeno = setimg(drawparms,itemparms.item,itemparms.sizes,k) ; itemparms.holdsize = 1 ; } function genloadhandler(loadstatus,k) { return function() { if(itemparms!=null) { loadstatus[k] = 2 ; resize() ; } } ; } function resize() { if(itemparms!=null) resizesub(itemparms,drawparms) ; } function enlarge() { setsize(2) ; } function reduce() { setsize(-1) ; } /* -------------------------------------------------------------------------- */ function gendisplay(element,item,sizes,llink,blink,rlink,fromstring,fetchitem) { var i,sizeno,captioned,table,tr,td,a,utd,dtd ; if(element==undefined||element==null) { itemparms = drawparms = null ; return ; } sizeno = getsize(item,sizes) ; if(item.caption==undefined) captioned = 0 ; else captioned = 1 ; itemparms = { item: item, sizes: sizes, sizeno: sizeno, holdsize: 0, loadstatus: new Array(sizes.length), window: [ window.innerWidth , window.innerHeight ] } ; for(i=0;i<sizes.length;i++) itemparms.loadstatus[i] = 0 ; itemparms.loadstatus[sizeno] = 2 ; // make the navigation table table = document.createElement('table') ; table.setAttribute('cellpadding',0) ; table.setAttribute('cellspacing',0) ; // the '<' link tr = document.createElement('tr') ; tr.appendChild(navcell(llink,-1)) ; // the enlarge link utd = document.createElement('td') ; utd.setAttribute("class","nav") ; tr.appendChild(utd) ; table.appendChild(tr) ; // the '>' link tr.appendChild(navcell(rlink,1)) ; table.appendChild(tr) ; // the return link tr = document.createElement('tr') ; tr.appendChild(navcell(blink,0,fromstring)) ; table.appendChild(tr) ; // the reduce link tr = document.createElement('tr') ; dtd = document.createElement('td') ; dtd.setAttribute("class","nav") ; tr.appendChild(dtd) ; table.appendChild(tr) ; drawparms = { headh: null, caph: null, enlink: utd, redlink: dtd, img: null, portrait: null, maindiv: document.createElement('div'), headdiv: maketextdiv(item.title), imgdiv: document.createElement('div'), capdiv: captioned?null:maketextdiv(item.caption) } ; setimg(drawparms,item,sizes,sizeno,fetchitem) ; drawparms.imgdiv.appendChild(drawparms.img) ; drawparms.maindiv.appendChild(drawparms.headdiv) ; drawparms.maindiv.appendChild(drawparms.imgdiv) ; if(captioned) drawparms.maindiv.appendChild(drawparms.capdiv) ; while(element.firstChild) element.removeChild(element.firstChild) ; element.appendChild(table) ; element.appendChild(drawparms.maindiv) ; }

Archived from routemaster.html

var segments=[],selected,actions,nactions,sel,dragging,loadno,shift=0 ; var pending,xpending,mouseopt=0,curhandle,elevator,resuri ; var routetitle,body,altdiv,curdiv,curcan,mapdiv,cmap,smap,prox,proa,pron ; var imgdiv,imghandle,imgtable,imginfo,imgind,selinit ; var cursorbtn,scissorsbtn,binbtn,undobtn,redobtn,penbtn,setbtn,dlbtn ; var defparms = {tol:10,maxsep:95,wppenalty:1000,vweight:1} ; var flagsign,turnleft,straighton,turnright,shriek,arrow,concircle,camera ; var neutral='<span style="font-family:helvetica">' ; var active='<span style="cursor:pointer;color:#0000bd" onclick=' ; var inactive='<span style="color:silver">' ; var textbox='<div style="margin-bottom:2px;border-bottom:solid 1px silver;' + 'padding-bottom:2px;font-family:helvetica">' ; var finalbox='<div style="font-family:helvetica">' ; var parser = new DOMParser() ; var map = null , clickhandle = null ; var unsavedchanges = [] ; var infowindow = { handle: null , type: null , open: function(s,pos,type) { this.handle = new google.maps.InfoWindow({content:s,position:pos}) ; this.handle.open(map) ; google.maps.event.addListener(this.handle,'closeclick', function() { infowindow.handle = infowindow.type = null ; } ) ; this.type = type ; } , close: function() { if(this.handle==null) return null ; var response = this.type ; this.handle.close() ; this.handle = this.type = null ; return response ; } } ; /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------------------------------------------------- */ /* CONSTRUCTORS */ /* -------------------------------------------------------------------------- */ function dotpath(a,b) { this.path = [a,b] ; this.cursor = 'default' ; this.geodesic = true ; this.strokeOpacity = 0 ; this.icons = [ { icon: { path: 'M 0 0 L 1 0',strokeOpacity:1,scale:1 } , offset: '1px' , repeat: '4px' } ] ; this.zIndex = 0 ; } /* -------------------------------------------------------------------------- */ function linepath(s0,start,end,colour) { var i,len=(start<0?segments[s0].data.length:end-start) ; this.path = new Array(len) ; if(start<0) for(i=0;i<len;i++) this.path[i] = segments[s0].data[i].pos ; else for(i=0;i<len;i++) this.path[i] = segments[s0].data[start+i].pos ; this.clickable = 'false' ; this.cursor = 'default' ; this.geodesic = true ; this.strokeColor = colour ; this.strokeOpacity = 1.0 ; this.strokeWeight = 2 ; this.zIndex = 0 ; } /* -------------------------------------------------------------------------- */ function listinfo() { this.list = [] ; this.sizes = [] ; this.dir = thumbsfx = null ; this.uri = this.scale = this.status = this.type = this.pixpage = null ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ------------------------------- data structure --------------------------- */ // I found the following logic quite hard to get right. A (non-null) label // satisfies the following constraints: // o. the marker is non-null // o. the map may be null, and if it is null the title may also be null and the // icon may be arbitrary // o. if the type is null, the map is null // o. the map is null if and only if the clickhandler is inactive // the same constraints apply (mutatis mutandis) to the photo, so it follows // that the label may have a null map and the photo non-null (and vice versa) // we therefore conclude that a label must be in one of 3 states: // o. type null, map null, handlers inactive, but marker non-null // o. type non-null, map null, handlers inactive, marker non-null // o. type non-null, map non-null, handlers active, marker non-null // the state in which type is non-null and map is null is applied to all // labels in a segment being deleted (we preserve the information in the // action list but don't want the label to be displayed) function datatype(pos,h) { this.pos = pos ; this.h = h ; this.marker = this.photomarker = this.type = this.t = null ; this.photo = [] ; this.caption = '' ; this.clickhandler = this.righthandler = this.photohandler = null ; } // member functions datatype.prototype.geticon = function() { if(this.type=='Left') return turnleft ; else if(this.type=='Straight') return straighton ; else if(this.type=='Right') return turnright ; else if(this.type=='Danger') return shriek ; else return flagsign ; } ; datatype.prototype.setlabelmap = function(m) { if(m==null||this.type==null) m = null ; else m = map ; if(m==null&&this.marker==null) return ; this.marker.setMap(m) ; if(m==null&&this.clickhandler!=null) { google.maps.event.removeListener(this.clickhandler) ; google.maps.event.removeListener(this.righthandler) ; this.clickhandler = this.righthandler = null ; } if(m!=null&&this.clickhandler==null) { this.clickhandler = this.marker.addListener('click',selpoint) ; this.righthandler = this.marker.addListener('rightclick',labelcycle) ; } } ; datatype.prototype.setphotomap = function(m) { if(m==null||this.photo.length==0) m = null ; else m = map ; if(m==null&&this.photomarker==null) return ; this.photomarker.setMap(m) ; if(m==null&&this.photohandler!=null) { google.maps.event.removeListener(this.photohandler) ; this.photohandler = null ; } if(m!=null&&this.photohandler==null) this.photohandler = this.photomarker.addListener('click',selpoint) ; } ; datatype.prototype.setlabel = function(t,c) { this.type = t ; if(t==null) { if(this.marker!=null) this.setlabelmap(null) ; return ; } if(this.marker==null) this.marker = new google.maps.Marker ({ position:this.pos,map:map,icon:this.geticon(),title:c,zIndex:1 }) ; else { this.marker.setIcon(this.geticon()) ; this.marker.setTitle(c) ; } this.setlabelmap(map) ; } ; datatype.prototype.setphoto = function(ind,p) { var i ; if(p==null) { for(i=ind;i<this.photo.length-1;i++) this.photo[i] = this.photo[i+1] ; this.photo.length -= 1 ; if(this.photo.length==0&&this.photomarker!=null) this.setphotomap(null) ; return ; } else { this.photo[ind] = p ; if(ind==0) this.photomarker.setTitle(p) ; } } ; datatype.prototype.addphoto = function(p) { this.photo.push(p) ; if(this.photomarker==null) this.photomarker = new google.maps.Marker ({ position:this.pos,map:map,icon:camera,title:p,zIndex:1 }) ; this.setphotomap(map) ; } ; datatype.prototype.setpos = function(p) { this.pos = p ; if(this.type!=null) this.marker.setPosition(p) ; if(this.photo.length>0) this.photomarker.setPosition(p) ; } ; datatype.prototype.setmap = function(m) { this.setlabelmap(m) ; this.setphotomap(m) ; } ; datatype.prototype.settype = function(t) { this.type = t ; this.marker.setIcon(this.geticon()) ; } ; /* -------------------------------------------------------------------------- */ /* UTILITY FUNCTIONS */ /* -------------------------------------------------------------------------- */ function xmlfloat(x) { return parseFloat(x.childNodes[0].textContent) ; } function dist(x,y) { return google.maps.geometry.spherical.computeDistanceBetween(x,y) ; } function interp(x,y,lamda) { return google.maps.geometry.spherical.interpolate(x,y,lamda) ; } function bearing(x,y) { return google.maps.geometry.spherical.computeHeading(x,y) ; } function angle(x,y) { return google.maps.geometry.spherical.computeHeading(x,y)*Math.PI/180 ; } function isvaliddate(d) { if(Object.prototype.toString.call(d)!=="[object Date]") return false ; else return !isNaN(d.getTime()) ; } /* --------------------------- button handlers ----------------------------- */ function greyout(btn) { if(btn.active==0) return 0 ; btn.btn.setAttribute('src',btn.greyimg) ; btn.ui.removeEventListener('click',btn.handler) ; if(btn==penbtn) btn.ui.removeEventListener('contextmenu',photoprompt) ; btn.ui.style.cursor = 'default' ; btn.active = 0 ; return 1 ; } function blackout(btn) { if(btn.active) return ; btn.btn.setAttribute('src',btn.blackimg) ; btn.ui.addEventListener('click',btn.handler) ; if(btn==penbtn) btn.ui.addEventListener('contextmenu',photoprompt) ; btn.ui.style.cursor = 'pointer' ; btn.active = 1 ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ------------------------ enter/exit full screen -------------------------- */ // Find the right method, call on correct element function enterFullscreen() { infowindow.close() ; if(document.documentElement.requestFullscreen) document.documentElement.requestFullscreen() ; else if(document.documentElement.mozRequestFullScreen) document.documentElement.mozRequestFullScreen() ; else if(document.documentElement.webkitRequestFullscreen) document.documentElement.webkitRequestFullscreen() ; else if(document.documentElement.msRequestFullscreen) document.documentElement.msRequestFullscreen() ; } function exitFullscreen() { infowindow.close() ; if(document.exitFullscreen) document.exitFullscreen() ; else if(document.mozCancelFullScreen) document.mozCancelFullScreen() ; else if(document.webkitExitFullscreen) document.webkitExitFullscreen() ; } /* ---------------- find the index of a given photo name -------------------- */ function findimg(id) { var i ; for(i=0;i<imginfo.list.length;i++) if(imginfo.list[i].name!=undefined&&imginfo.list[i].name==id) return i ; return -1 ; } /* ------------------- message warning of unsaved changes ------------------- */ function unsavedmsg(ok) { var msg , len = unsavedchanges.length , i ; if(len==0) return null ; msg = 'You have ' + len + ' unsaved change' + (len==1?"":'s') ; if(len<=3) for(i=0;i<len;i++) msg += (i?',':' (') + unsavedchanges[i] + (i==len-1?')':'') ; msg += '\nIf you hit ' + (ok?'[OK]':'[Leave page]') ; return msg + (len==1?' this change':' these changes') + ' will be lost.' ; } /* --------------- selpoint: choose the clicked waypoint ------------------- */ function selpoint(event) { var i,j,closest,d,mindist,s0=selected[0],s1=segments[s0].data.length ; if(dragging) return ; var flag = (infowindow.close()=='wpinfo') && (shift==0) ; if(shift) { insert(s0,s1,1) ; segments[s0].data[s1].setpos(event.latLng) ; lookupalt(s0,s1) ; redrawconnect(s0,s1) ; done(['move',s0,s1,event.latLng,event.latLng,1]) ; } else for(s1=s0=-1,i=0;i<segments.length;i++) for(j=0;j<segments[i].data.length;j++) { d = dist(segments[i].data[j].pos,event.latLng) ; if(s0<0||d<mindist) { s0 = i ; s1 = j ; mindist = d ; } } walkto(s0,s1,flag) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* --------------------------------- walkto --------------------------------- */ // draw a selection point (and possibly an info box) at [s0,s1], bringing up // a wpinfo window if flag != 0 function walkto(s0,s1,flag) { var s='',i,ind,imguri=null,excuse,imgname ; var datum = segments[s0].data[s1] , pos = datum.pos ; selected = [s0,s1] ; map.panToBounds(new google.maps.LatLngBounds(pos,pos)) ; drawsel(0) ; if(flag||(datum.type==null&&datum.photo.length==0)) { if(flag) wpinfo() ; return ; } if(datum.type!=null) { if(datum.photo.length>0) s = textbox ; else s = finalbox ; if(datum.type!='Generic') s += datum.type + ': ' ; s += datum.marker.title + ' [' + active + '"labelprompt()">Edit</span>]' ; s += '</div>' ; } for(ind=0;ind<datum.photo.length;ind++) { s += textbox ; if(imginfo.status=='ready'&&(i=findimg(datum.photo[ind]))>=0) { if(imginfo.dir==null) imguri = imginfo.uri ; else imguri = reluri(imginfo.uri,imginfo.dir) + '/' ; imguri = reluri(imguri,datum.photo[ind]) + imginfo.thumbsfx + '.jpg' ; s += '<img src="' + imguri + '" width=' + imginfo.list[i].thumbshape[0] + ' height=' + imginfo.list[i].thumbshape[1] + '><br>' + '<b>'+datum.photo[ind]+'</b>: ' ; } else { if(imginfo.status=='null') excuse = 'no list provided' ; else if(imginfo.status=='ready') { imgname = imginfo.uri ; i = imgname.lastIndexOf('/') ; if(i>=0) imgname = imgname.substring(i+1) ; excuse = 'not present in ' + imgname ; } else if(imginfo.status=='waiting') excuse = imgname + ' is not available' ; else excuse = 'imginfo.status = ' + imginfo.status ; s += 'Photo: ' + datum.photo[ind] + ' (' + excuse + ') ' ; } s += '['+active+ '"photoedit('+ind+')">Edit</span>'+']' ; if(imguri!=null) s += ' : ['+active+ '"phinfo('+i+')">Info</span>'+']' + ' : ['+active+ '"enlarge('+ind+','+i+')">Enlarge</span>'+']' ; s += '</div>' ; } if(datum.photo.length>0) s += finalbox + '[' + active + '"photoprompt' + '(null)">Add photo</span>' + ']</div>' ; infowindow.open(s,pos,'walking') ; } /* -------------------------- keystroke handler ---------------------------- */ function walk(e) { var s0=selected[0],s1=selected[1],slast,flag ; if(e.keyCode==16) shift = 1 ; if(e.keyCode==40) { map.panTo(segments[s0].data[s1].pos) ; return ; } if(infowindow.close()=='wpinfo') flag = 1 ; else flag = 0 ; if(e.keyCode==32) { selclick() ; return ; } // space if(e.keyCode==13) // return { if(dragging) undraggit() ; else if(s0>=0) draggit(0) ; return ; } if(dragging) return ; if(e.keyCode==8||e.keyCode==46) // delete/backspace { e.preventDefault() ; if(binbtn.active) discard() ; return ; } if(e.keyCode==9) { e.preventDefault() ; inswp(1) ; return ; } // tab if(e.keyCode==39) // forwards { e.preventDefault() ; if(s1<segments[s0].data.length-1) s1 += 1 ; else { s0 += 1 ; if(s0==segments.length) s0 = 0 ; s1 = 0 ; } } else if(e.keyCode==37) // backwards { e.preventDefault() ; if(s1>0) s1 -= 1 ; else { s0 -= 1 ; if(s0<0) s0 = segments.length-1 ; s1 = segments[s0].data.length-1 ; } } else return ; walkto(s0,s1,flag) ; } /* ---------------------------- relative uri ------------------------------- */ function reluri(u1,u2) { var last = u1.lastIndexOf('/') ; if(last<0) return u2 ; u1 = u1.substring(0,last) ; while(u2.substring(0,3)=='../') { last = u1.lastIndexOf('/') ; if(last<0) return u2 ; u2 = u2.substring(3) ; u1 = u1.substring(0,last) ; } return u1 + '/' + u2 ; } /* ------------------------------- getbtnpos -------------------------------- */ function getbtnpos(btnno) { var bounds=map.getBounds(),sw,ne,lat,lon,lam ; sw = bounds.getSouthWest() ; ne = bounds.getNorthEast() ; lam = 52.0 / window.innerHeight ; lat = lam*ne.lat() + (1-lam)*sw.lat() ; lam = 0.5 + (btnno*32-112.0)/window.innerWidth ; lon = lam*ne.lng() + (1-lam)*sw.lng() ; return new google.maps.LatLng(lat,lon) ; } /* ----- unambig: does the selected waypoint determine a unique segment? ---- */ function unambig() // does the selected waypoint determine a unique segment? { var s0=selected[0],s1=selected[1] ; if(segments.length==1) return 1 ; if( ( s0==segments.length-1 || s1!=segments[s0].data.length-1 || ! segments[s0].data[s1].pos.equals(segments[s0+1].data[0].pos) ) && ( s0==0 || s1!=0 || ! segments[s0].data[s1].pos.equals (segments[s0-1].data[segments[s0-1].data.length-1].pos) ) ) return 1 ; else return 0 ; } /* --------------------- undraw & redraw segments -------------------------- */ function undraw(i) { segments[i].route.setMap(null) ; if(segments[i].clickhandler!=null) { google.maps.event.removeListener(segments[i].clickhandler) ; segments[i].clickhandler = null ; } } function redraw(i) { undraw(i) ; draw(i) ; } function recolour(i) { if(i&1) segments[i].route.setOptions({strokeColor:"#ff9999"}) ; else segments[i].route.setOptions({strokeColor:"#ff0000"}) ; } function obliterate(s0) // undraw route and all labels { var i ; for(i=0;i<segments[s0].data.length;i++) segments[s0].data[i].setmap(null) ; undraw(s0) ; disconnect(s0-1) ; disconnect(s0) ; } /* ----------------------------- draw segments ------------------------------ */ function draw(i) { var colour ; if(i&1) colour = "#ff9999" ; else colour = "#ff0000" ; segments[i].route = new google.maps.Polyline(new linepath(i,-1,0,colour)) ; segments[i].route.setMap(map) ; segments[i].clickhandler = google.maps.event.addListener(segments[i].route,"click",selpoint) ; } /* ----------------------- connect and disconnect segments ------------------ */ function disconnect(i) { if(i<0||i>=segments.length-1||segments[i].dots==null) return ; segments[i].dots.setMap(null) ; if(segments[i].dothandler!=null) { google.maps.event.removeListener(segments[i].dothandler) ; segments[i].dothandler = null ; } } function reconnect(i) { disconnect(i) ; connect(i) ; } function connect(i) { if(i<0||i>=segments.length-1) return ; var opos = segments[i].data[segments[i].data.length-1].pos ; var npos = segments[i+1].data[0].pos ; if(opos.equals(npos)) return ; segments[i].dots = new google.maps.Polyline(new dotpath(opos,npos)) ; segments[i].dots.setMap(map) ; segments[i].dothandler = google.maps.event.addListener(segments[i].dots,"click",selpoint) ; } function redrawconnect(s0,s1) { redraw(s0) ; if(s1==0) reconnect(s0-1) ; if(s1=segments[s0].data.length-1) reconnect(s0) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ---------------------- draw the selection point -------------------------- */ // note: there's no point in allowing clicking on a marker because the // event position is always the marker position rather than the click position function drawsel(opt) { var ind,clen,s0=selected[0],s1=selected[1],pos=segments[s0].data[s1].pos ; if(opt) reprofile() ; clen = segments[s0].data.length ; if(clen==1) arrow.rotation = 90 ; else { if(s1==clen-1) ind = s1-1 ; else ind = s1 ; arrow.rotation = bearing(segments[s0].data[ind].pos,segments[s0].data[ind+1].pos) ; } if(sel.marker==null) sel.marker = new google.maps.Marker ( { position:pos, map:map, cursor:'default', icon:arrow , zIndex:2 } ) ; else // avoid unnecessary redraws { if(arrow.rotation!=sel.orientation) sel.marker.setIcon(arrow) ; if(!pos.equals(sel.marker.getPosition())) sel.marker.setPosition(pos) ; } sel.orientation = arrow.rotation ; procur() ; blackout(penbtn) ; if(segments.length>1&&unambig()) blackout(binbtn) ; else greyout(binbtn) ; if(s1!=0&&s1!=clen-1) blackout(scissorsbtn) ; else greyout(scissorsbtn) ; } /* ------------- selclick: respond to click of cursor button --------------- */ function selclick() { mouseopt = 1-mouseopt ; infowindow.close() ; if(mouseopt) { map.setOptions({draggable:false, draggableCursor:'default'}) ; cursorbtn.btn.setAttribute('src','hand.png') ; clickhandle = google.maps.event.addListener(map,"click",selpoint) ; } else { map.setOptions({draggable:true, draggableCursor:''}) ; cursorbtn.btn.setAttribute('src','arrow.png') ; google.maps.event.removeListener(clickhandle) ; } } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------------------------------------------------- */ function genhead(uri,key) { resuri = uri + '/' ; document.write ('<script src="http://maps.google.com/maps/api/js?' + ((key==null||key==undefined)?'':('key='+key+'&')) + 'libraries=geometry"></scr' + 'ipt>' + '<script src="' + resuri + 'dms.js"></scr' + 'ipt>' + '<script src="' + resuri + 'vector3d.js"></scr' + 'ipt>' + '<script src="' + resuri + 'latlon-ellipsoidal.js"></scr' + 'ipt>' + '<script src="' + resuri + 'utm.js"></scr' + 'ipt>' + '<script src="' + resuri + 'osgridref.js"></scr' + 'ipt>' + '<script src="' + resuri + 'FileSaver.js"></scr' + 'ipt>' + '<script src="' + resuri + 'Blob.js"></scr' + 'ipt>' + '<style type="text/css">html, body {width: 100%; height: 100%}' + 'body {margin:0px}</style><title>Routemaster</title>' + '<link rel="shortcut icon" href=' + resuri + 'bus.gif>' ) ; } /* -------------------------------------------------------------------------- */ /* FUNCTIONS TO GENERATE THE INITIAL MAP */ /* -------------------------------------------------------------------------- */ function genpage() { var thispage=document.URL,xhttp,quotind,plusind,listxhttp ; imginfo = new listinfo() ; imgdiv = null ; elevator = new google.maps.ElevationService ; // coursepoint icons flagsign = { path: "M 0.5 20.5 L 0.5 0.5 12.5 6 0.5 11.5 ", fillColor: '#FCDFFF', fillOpacity: 0.7, strokeColor: 'purple', strokeWeight: 1.5, anchor: new google.maps.Point(0.5,20.5), } ; turnleft = { path: "M 18.5 20.5 L 16.5 11.5 A 2 2 0 0 0 14.5 9.5 "+ "L 11.5 10 11.5 13.5 "+ "6.5 7.5 11.5 1.5 11.5 5 16.5 5.5 A 3.5 3.5 0 0 1 20 9 z", fillColor: '#FCDFFF', fillOpacity: 0.7, strokeColor: 'purple', strokeWeight: 1.5, anchor: new google.maps.Point(18.5,20.5), } ; straighton = { path: "M 7.5 20.5 L 4.5 6.5 0.5 6.5 7.5 0.5 14.5 6.5 10 6.5 z", fillColor: '#FCDFFF', fillOpacity: 0.7, strokeColor: 'purple', strokeWeight: 1.5, anchor: new google.maps.Point(7.5,20.5), } ; turnright = { path: "M 3.5 20.5 L 5.5 11.5 A 2 2 0 0 1 7.5 9.5 L 10.5 10 10.5 13.5 "+ "15.5 7.5 10.5 1.5 10.5 5 5.5 5.5 A 3.5 3.5 0 0 0 2 9 z", fillColor: '#FCDFFF', fillOpacity: 0.7, strokeColor: 'purple', strokeWeight: 1.5, anchor: new google.maps.Point(3.5,20.5), } ; shriek = { path: "M 8.5 21.5 A 2.5 2.5 0 0 1 8.5 16.5 A 2.5 2.5 0 1 1 8.5 21.5 "+ "M 8.5 14.5 4.5 5.5 A 4.5 4.5 0 1 1 12.5 5.5 L 8.5 14.5" , fillColor: '#FCDFFF', fillOpacity: 0.7, strokeColor: 'purple', strokeWeight: 1.5, anchor: new google.maps.Point(8.5,21.5), } ; // icon for arrow representing current waypoint arrow = { path: "M 6 9 0 15 6 0 12 15 z", fillColor: 'black', fillOpacity: 1, strokeColor: 'black', strokeWeight: 0, anchor: new google.maps.Point(6,6), rotation: 0, clickable: false } ; // icon for concentric circles representing draggable waypoint concircle = { path: "M 6 0 A 6 6 0 1 0 6 12 A 6 6 0 1 0 6 0 M 6 3 " + "A 3 3 0 1 0 6 9 A 3 3 0 1 0 6 3", fillColor: 'black', fillOpacity: 0, strokeColor: 'black', strokeWeight: 1, strokeOpacity: 1, anchor: new google.maps.Point(6,6), clickable: false } ; // camera icon camera = { path: "M 0.5 4 A 1.5 1.5 0 0 1 2 2.5 L 5.5 2.5 7 0.5 11 0.5 " + "12.5 2.5 14 2.5 A 1.5 1.5 0 0 1 16 3 L 20 7 16 11 " + "A 1.5 1.5 0 0 1 15 11.5 L 2 11.5 A 1.5 1.5 0 0 1 0.5 10 z " + "M 9 4 A 3 3 0 0 1 9 10 A 3 3 0 0 1 9 4 " , fillColor: '#FCDFFF', fillOpacity: 0.7, strokeColor: 'purple', strokeWeight: 1.5, anchor: new google.maps.Point(21,7), clickable: false } ; window.onload = function() { window.addEventListener("beforeunload",function(e) { var msg = unsavedmsg(0) ; if(msg==null) return undefined ; (e || window.event).returnValue = msg ; //Gecko + IE return msg ; //Gecko + Webkit, Safari, Chrome etc. (msg is ignored by ffx) } ) ; } ; body = document.getElementsByTagName("body")[0] ; while(body.childNodes.length>0) body.removeChild(body.childNodes[body.childNodes.length-1]) ; mapdiv = document.createElement('div') ; mapdiv.setAttribute('id','map') ; mapdiv.setAttribute('style','width:100%;height:100%') ; body.appendChild(mapdiv) ; if((quotind=thispage.indexOf('?'))>=0) { thispage = thispage.substring(quotind+1) ; if((plusind=thispage.indexOf('+'))>0) { getlist(thispage.substring(plusind+1),'uri') ; thispage = thispage.substring(0,plusind) ; } else if(thispage.substring(thispage.length-3)=='.js') { getlist(thispage,'uri') ; mapdiv.appendChild(filedialogue(0)) ; return ; } xhttp = new XMLHttpRequest() ; xhttp.onreadystatechange = function() { if(xhttp.readyState==4) { if(xhttp.status==200) { var x = parser.parseFromString(xhttp.responseText,"application/xml") ; render(x,thispage,0) ; } else alert("Unable to read "+thispage+": error code "+xhttp.status) ; } } xhttp.open("GET",thispage,true) ; xhttp.send() ; } else mapdiv.appendChild(filedialogue(0)) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------- getlist --------------------------------- */ function getlist(uri,imgtype) { var xhttp = new XMLHttpRequest(),list=null,sizes=null,pixpage=null,ind,r ; var imagedir=null,thumbshape = [] ; imginfo.status = 'waiting' ; imginfo.uri = uri ; imginfo.type = imgtype ; xhttp.onreadystatechange = function() { if(xhttp.readyState==4) { if(xhttp.status!=200) { alert("Unable to read "+uri+": error code "+xhttp.status) ; return ; } eval(xhttp.responseText) ; imginfo.list = list ; imginfo.sizes = sizes ; imginfo.dir = imagedir ; imginfo.pixpage = pixpage ; imginfo.status = 'ready' ; // prepare to set the thumb shapes by finding the thumb index for(ind=0;ind<sizes.length&&sizes[ind].type!='thumb';ind++) ; if(ind==sizes.length) { alert('no "sizes" entry of type "thumb"') ; throw '' ; } imginfo.thumbsfx = sizes[ind].suffix ; if(thumbshape.length!=2) { if(sizes[ind].scale>0) r = sizes[ind].scale / sizes[0].scale ; else { alert('thumbs have size '+sizes[ind].scale) ; throw '' ; } } // set thumbshapes for(ind=0;ind<list.length;ind++) if(list[ind].name!=undefined) { if(list[ind].thumbshape==undefined) { if(thumbshape.length==2) list[ind].thumbshape = thumbshape ; else for(list[ind].thumbshape=[0,0],k=0;k<2;k++) list[ind].thumbshape[k] = Math.floor(0.5+list[ind].shape[k]*r) ; } } } } xhttp.open("GET",uri,true) ; xhttp.send() ; } /* ----------------------------- file dialogue ------------------------------ */ function filedialogue(overwrite) { var input = document.createElement('input') ; var para = document.createElement('p') ; para.appendChild(document.createTextNode ('\u00a0\u00a0\u00a0Select TCX/GPX file: ')) ; input.setAttribute('type','file') ; input.setAttribute('accept','.tcx,.gpx') ; input.addEventListener('change',function(e) { reader = new FileReader() ; reader.onload = function(e) { var xmldoc = parser.parseFromString(reader.result,"application/xml") ; render(xmldoc,input.files[0].name,overwrite) ; } reader.readAsText(input.files[0]) ; } ) ; para.appendChild(input) ; return para ; } /* -------------------------------------------------------------------------- */
/*page*/ /* --------------------- set up the map and buttons ------------------------- */ function render(xmldoc,filename,overwrite) { var i,opts,centre,lat,lon,minlon,maxlon,minlat,maxlat,newseg,s0,bounds,sw,ne ; infowindow.close() ; document.onkeydown = walk ; document.onkeyup = function(e) { if(e.keyCode==16) shift = 0 ; } if(overwrite) { for(i=0;i<segments.length;i++) obliterate(i) ; unprofile() ; if(sel.marker!=null) sel.marker.setMap(null) ; segments = [] ; if(imginfo.type=='tcx') imginfo = new listinfo() ; } s0 = segments.length ; if(s0==0) { sel = { marker:null, orientation: null } ; pending = [] ; xpending = [] ; actions = [] ; unsavedchanges = [] ; nactions = dragging = 0 ; loadno = -1 ; prox = proa = pron = null ; altdiv = curcan = curdiv = cmap = smap = curhandle = null ; } // set up segments newseg = gensegment(xmldoc,filename) ; segments.push(newseg[0]) ; if(s0==0) { if(newseg[1].title!=null) settitle(newseg[1].title) ; else settitle('Untitled Route') ; } actions[nactions++] = [ 'load' , s0 , newseg[0].data.slice() , loadno , newseg[1] ] ; loadno = nactions-1 ; if(!newseg[1].optim.already) optimaction(segments.length-1,defparms,0) ; // find max and min lat and long for(i=0;i<segments[s0].data.length;i++) { lat = segments[s0].data[i].pos.lat() ; lon = segments[s0].data[i].pos.lng() ; if(i==0||lon<minlon) minlon = lon ; if(i==0||lon>maxlon) maxlon = lon ; if(i==0||lat<minlat) minlat = lat ; if(i==0||lat>maxlat) maxlat = lat ; } if(s0==0) centre = new google.maps.LatLng((minlat+maxlat)/2,(minlon+maxlon)/2) ; if(map==null) // all this only done on first call { opts = { zoom: 22, center: centre, scaleControl: true, rotateControl: false, streetViewControl: false, mapTypeId: google.maps.MapTypeId.TERRAIN, disableDoubleClickZoom: true, styles: [ { "featureType": "poi", "stylers": [{ "visibility": "off" }] } ], mapTypeControl:true, mapTypeControlOptions: { style:google.maps.MapTypeControlStyle.HORIZONTAL_BAR }, mapTypeIds: [ google.maps.MapTypeId.ROADMAP, google.maps.MapTypeId.TERRAIN, google.maps.MapTypeId.SATELLITE ] } ; map = new google.maps.Map(mapdiv,opts) ; // set up buttons setbtn = genbutton('settings') ; cursorbtn = genbutton('cursor') ; cursorbtn.ui.addEventListener('click',selclick) ; scissorsbtn = genbutton('scissors') ; binbtn = genbutton('bin') ; penbtn = genbutton('pen') ; undobtn = genbutton('undo') ; redobtn = genbutton('redo') ; dlbtn = genbutton('dl') ; selclick() ; } else if(s0!=0) { bounds = map.getBounds() ; sw = bounds.getSouthWest() ; ne = bounds.getNorthEast() ; if(sw.lat()<minlat) minlat = sw.lat() ; if(sw.lng()<minlon) minlon = sw.lng() ; if(ne.lat()>maxlat) maxlat = ne.lat() ; if(ne.lng()>maxlon) maxlon = ne.lng() ; } map.fitBounds(new google.maps.LatLngBounds( new google.maps.LatLng(minlat,minlon), new google.maps.LatLng(maxlat,maxlon))) ; for(i=0;i<segments[s0].data.length;i++) segments[s0].data[i].setmap(map) ; if(nactions>1) donesomething() ; // specifically, done loading & optimisation else actions.length = nactions ; // load with no optimisation hence no undo if(s0==0) { selected = [0,0] ; drawsel(1) ; } else greyout(dlbtn) ; draw(s0) ; connect(s0-1) ; connect(s0) ; reprofile() ; } /* ------------------------------- settitle --------------------------------- */ function settitle(newtitle) { routetitle = newtitle ; var h = document.getElementsByTagName('title')[0] ; while(h.firstChild) h.removeChild(h.firstChild) ; h.appendChild(document.createTextNode(routetitle)) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ------------------------------- retitle ---------------------------------- */ function retitle() { infowindow.close() ; var response = window.prompt("Modify title:",routetitle) ; if(response==null) return ; else response = response.substring(0,15) ; if(response==routetitle) return ; actions[nactions++] = ['edittitle',routetitle,response] ; actions.length = nactions ; blackout(undobtn) ; greyout(redobtn) ; settitle(response) ; } /* ------------------------------- genbutton -------------------------------- */ function genbutton(name) { var u,v,w,b,g,k,h=null,div=document.createElement('div'),act ; u = document.createElement('div') ; u.style.backgroundColor = '#ffffff' ; u.style.border = '2px solid #ffffff' ; u.style.borderRadius = '3px' ; u.style.boxShadow = '0 2px 6px rgba(0,0,0,.3)' ; if(name=='dl'||name=='settings'||name=='cursor') u.style.cursor = 'pointer' ; else u.style.cursor = 'default' ; u.style.marginBottom = '12px' ; if(name!='dl') u.style.marginRight = '4px' ; u.style.textAlign = 'center' ; div.appendChild(u) ; b = document.createElement('img') ; g = name + '.png' ; if(name=='scissors') { h = snip ; div.index = 3 ; } else if(name=='bin') { h = discard ; div.index = 4 ; } else if(name=='pen') { h = labelprompt ; div.index = 5 ; } else if(name=='undo') { h = undo ; div.index = 6 ; } else if(name=='redo') { h = redo ; div.index = 7 ; } else if(name=='dl') { h = dl ; div.index = 8 ; } else if(name=='settings') { h = popup ; div.index = 1 ; } else if(name=='cursor') { g = 'arrow.png' ; k = 'hand.png' ; div.index = 2 ; } if(name!='cursor') { k = 'black' + g ; g = 'grey' + g ; } g = resuri + g ; k = resuri + k ; if(name=='dl'||name=='settings'||name=='cursor') b.setAttribute('src',k) ; else b.setAttribute('src',g) ; b.setAttribute('width',24) ; b.setAttribute('height',24) ; u.appendChild(b) ; map.controls[google.maps.ControlPosition.BOTTOM_CENTER].push(div) ; if(name=='dl'||name=='settings') u.addEventListener('click',h) ; if(name=='dl'||name=='settings') act = 1 ; else act = 0 ; return { ui:u , btn:b , active:act , greyimg:g , blackimg:k , handler:h } ; } /* -------------------------------------------------------------------------- */
/*page*/ /* --------------- construct a segment from an xml document ----------------- */ function genseg(a) { this.data = a ; this.route = this.routehandler = this.dots = this.dothandler = null ; } /* -------------------------------------------------------------------------- */ function gensegment(xmldoc,filename) { var xmlcoords,nodeno,tcx,type,mindist,lat,lon,i,j,segment,node,alt,pos ; var ind,caption,data=[],alreadyoptimised=0,photo,time,valid ; var props = { title: null , inputlen: null , optim: { already: 0, ndel: 0, origlen: 0, parms: null } } tcx = filename.length ; if(filename.substring(tcx-4,tcx)=='.tcx') tcx = 1 ; else if(filename.substring(tcx-4,tcx)=='.gpx') tcx = 0 ; else { alert(filename+' is neither .tcx nor .gcx') ; throw '' ; } if(tcx) { // optimised? xmlcoords = xmldoc.getElementsByTagName('Optimised') ; if(xmlcoords.length) { props.optim.already = 1 ; props.optim.origlen = parseInt(xmlcoords[0].getAttribute('from')) ; props.optim.ndel = props.optim.origlen - parseInt(xmlcoords[0].getAttribute('to')) ; props.optim.parms = { tol: parseFloat(xmlcoords[0].getAttribute('tol')) , maxsep: parseFloat(xmlcoords[0].getAttribute('maxsep')) , wppenalty: parseFloat(xmlcoords[0].getAttribute('wppenalty')) , vweight: parseFloat(xmlcoords[0].getAttribute('vweight')) } ; } // photo list? if(imginfo.uri==null||imginfo.type=='tcx') { xmlcoords = xmldoc.getElementsByTagName('PhotoList') ; if(xmlcoords.length) { imginfo = new listinfo() ; getlist(xmlcoords[0].getAttribute('src'),'tcx') ; } } // route title xmlcoords = xmldoc.getElementsByTagName('Name') ; for(i=0;i<xmlcoords.length&&props.title==null;i++) if( xmlcoords[i].parentNode.nodeName=='Course' || xmlcoords[i].parentNode.nodeName=='Lap' ) props.title = xmlcoords[i].childNodes[0].textContent ; // loop over the track points to get the coords xmlcoords = xmldoc.getElementsByTagName('Trackpoint') ; for(i=0;i<xmlcoords.length;i++) { lat = lon = alt = time = null ; photo = [] ; for(valid=1,nodeno=0;nodeno<xmlcoords[i].childNodes.length;nodeno++) { node = xmlcoords[i].childNodes[nodeno] ; if(node.nodeName=='AltitudeMeters') alt = xmlfloat(node) ; else if(node.nodeName=='Time') // '1970-01-01T03:040:08Z' { time = new Date(node.childNodes[0].textContent) ; if(!isvaliddate(time)) time = null ; } else if(node.nodeName=='Position') for(j=0;j<node.childNodes.length;j++) { if(node.childNodes[j].nodeName=='LatitudeDegrees') lat = xmlfloat(node.childNodes[j]) ; else if(node.childNodes[j].nodeName=='LongitudeDegrees') lon = xmlfloat(node.childNodes[j]) ; } else if(node.nodeName=='Extensions') for(j=0;j<node.childNodes.length;j++) { if(node.childNodes[j].nodeName=='Photo') photo = node.childNodes[j].childNodes[0].textContent.split(' ') ; else if(node.childNodes[j].nodeName=='ValidTime') valid = 0 ; } } if(lat==null||lon==null) continue ; data[data.length] = new datatype(new google.maps.LatLng(lat,lon),alt) ; for(ind=0;ind<photo.length;ind++) data[data.length-1].addphoto(photo[ind]) ; if(valid) data[data.length-1].t = time ; } } else // gpx { if(xmldoc.getElementsByTagName('name').length>0) props.title = xmldoc.getElementsByTagName('name')[0].childNodes[0].textContent ; // loop over the track points to get the coords xmlcoords = xmldoc.getElementsByTagName('trkpt') ; if(xmlcoords.length==0) xmlcoords = xmldoc.getElementsByTagName('rtept') ; for(i=0;i<xmlcoords.length;i++) { lat = parseFloat(xmlcoords[i].getAttribute('lat')) ; lon = parseFloat(xmlcoords[i].getAttribute('lon')) ; for(time=alt=null,nodeno=0;nodeno<xmlcoords[i].childNodes.length;nodeno++) { node = xmlcoords[i].childNodes[nodeno] ; if(node.nodeName=='ele') alt = parseFloat(node.textContent) ; else if(node.nodeName=='time') time = new Date(node.childNodes[0].textContent) ; } data[data.length] = new datatype(new google.maps.LatLng(lat,lon),alt) ; data[data.length-1].t = time ; } } for(i=0;i<data.length;i++) if(data[i].h==null) { alert(data[i].pos+' has no altitude... unable to proceed') ; throw '' ; } props.inputlen = data.length ; for(i=0;i<data.length;i++) if((time=data[i].t)!=null) // nullify illegal times if(time.getTime()<365*24*3600000) data[i].t = null ; segment = new genseg(data) ; // loop over the course points to get the labels if(tcx) xmlcoords = xmldoc.getElementsByTagName('CoursePoint') ; else xmlcoords = xmldoc.getElementsByTagName('wpt') ; for(i=0;i<xmlcoords.length;i++) { caption = type = lat = lon = null ; if(tcx) for(nodeno=0;nodeno<xmlcoords[i].childNodes.length;nodeno++) { node = xmlcoords[i].childNodes[nodeno] ; if(node.nodeName=='Name') caption = node.childNodes[0].textContent ; else if(node.nodeName=='PointType') type = node.childNodes[0].textContent ; else if(node.nodeName=='Position') for(j=0;j<node.childNodes.length;j++) { if(node.childNodes[j].nodeName=='LatitudeDegrees') lat = xmlfloat(node.childNodes[j]) ; else if(node.childNodes[j].nodeName=='LongitudeDegrees') lon = xmlfloat(node.childNodes[j]) ; } } else { lat = parseFloat(xmlcoords[i].getAttribute('lat')) ; lon = parseFloat(xmlcoords[i].getAttribute('lon')) ; for(nodeno=0;nodeno<xmlcoords[i].childNodes.length;nodeno++) { node = xmlcoords[i].childNodes[nodeno] ; if(node.nodeName=='name') caption = node.childNodes[0].textContent ; else if(node.nodeName=='type') type = node.childNodes[0].textContent ; } } if(lat==null||lon==null||caption==null||type==null) { alert('Badly formatted course point' ) ; throw '' ; } pos = new google.maps.LatLng(lat,lon) ; for(j=0;j<data.length;j++) if(j==0||dist(pos,data[j].pos)<mindist) { mindist = dist(pos,data[j].pos) ; ind = j ; } segment.data[ind].setlabel(type,caption) ; } if(props.optim.origlen==0) props.optim.origlen = data.length ; return [ segment , props ] ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------------------------------------------------- */ /* OPTIMISATION */ /* -------------------------------------------------------------------------- */ function optimaction(segno,parms,force) { var s = segments[segno], result = optimise(s,parms) ; var ndel = s.data.length - result.length ; if((force==0&&ndel<s.data.length/2)||ndel==0) return 0 ; actions[loadno][4].optim.ndel = ndel ; actions[nactions++] = [ 'optimise' , segno , parms ] ; segments[segno] = new genseg(result) ; actions[loadno][4].optim.parms = { tol: parms.tol , maxsep: parms.maxsep , wppenalty: parms.wppenalty , vweight: parms.vweight } ; return 1 ; } /* -------------------------------------------------------------------------- */ function optimprompt() { var msg = 'Enter 4 optimisation parms (tol, maxsep, wppenalty and vweight)' ; var parmstr = defparms.tol + ' ' + defparms.maxsep.toFixed(0) + ' ' + defparms.wppenalty.toFixed(0) + ' ' + defparms.vweight.toFixed(1) ; var parms,i ; infowindow.close() ; for(i=0;;i++) { newparms = prompt(msg,parmstr) ; if(newparms==null) return ; if(newparms=='') { parms = defparms ; break ; } newparms = newparms.split(' ') ; if(newparms.length==0) { parms = defparms ; break ; } parms = { tol: parseFloat(newparms[0]) , maxsep: parseFloat(newparms[1]) , wppenalty: parseFloat(newparms[2]) , vweight: parseFloat(newparms[3]) } ; if( isNaN(parms.tol)==0 && isNaN(parms.maxsep)==0 && isNaN(parms.wppenalty)==0 && isNaN(parms.vweight)==0 ) break ; if(i==0) msg = '*** Illegal parms ***\n' + msg ; } if(optimaction(segments.length-1,parms,1)) { donesomething() ; draw(segments.length-1) ; } routeinfo() ; } /* -------------------------------------------------------------------------- */ function optimise(s,parms) { var stk,nstk,stk2,clen=s.data.length,i,j,m,step=new Array(clen-1) ; var opos,oalt,npos,nalt,ndatum,mpos,malt,arccentre,arctol,pathpos ; var legal,theta,omega,x,y,hyp,tdist,maxtheta,mintheta,d,dver,od,odver,odash ; var bearings,pi=Math.PI,tol=parms.tol ; stk = [ { data:[s.data[0]] , err:0 , pathpos:1 } ] ; for(i=0;i<clen-1;i++) step[i] = dist(s.data[i].pos,s.data[i+1].pos) ; while(stk[0].pathpos<clen) { pathpos = stk[0].pathpos ; opos = s.data[pathpos-1].pos ; oalt = s.data[pathpos-1].h ; // try extending to pathpos+i for(bearings=[],nstk=[],arctol=null,i=0;i<clen-pathpos;i++) { ndatum = s.data[pathpos+i] ; npos = ndatum.pos ; nalt = ndatum.h ; if(i==0) hyp = step[pathpos-1] ; else if((hyp=dist(opos,npos))>parms.maxsep) break ; omega = angle(opos,npos) ; // find the min and max legal bearing if(hyp>tol) { theta = Math.asin(tol/hyp) ; if(arctol==null) { arccentre = omega ; arctol = theta ; } else { odash = omega - arccentre ; while(odash>pi) odash -= 2*pi ; while(odash<-pi) odash += 2*pi ; maxtheta = Math.min(arctol,odash+theta) ; mintheta = Math.max(-arctol,odash-theta) ; if(maxtheta<mintheta) break ; arccentre += (maxtheta+mintheta) /2 ; arctol = (maxtheta-mintheta) /2 ; } } bearings[i] = { hyp:hyp , omega:omega } ; // see whether this breaches the max error on any intermediate point for(legal=1,od=odver=tdist=m=0;m<i;m++,od=d,odver=dver) { mpos = s.data[pathpos+m].pos ; malt = s.data[pathpos+m].h ; x = bearings[m].hyp ; theta = bearings[m].omega ; d = x * Math.sin(theta-omega) ; dver = 0 ; if(d*d<tol*tol&&oalt!=null&&nalt!=null&&malt!=null) { y = hyp - x*Math.cos(theta-omega) ; y = Math.sqrt(d*d+y*y) ; dver = parms.vweight * ( malt - (oalt*y+nalt*x)/(x+y) ) ; } if(d*d+dver*dver>tol*tol) { legal = 0 ; break ; } tdist += (step[pathpos-1+i]/3) * (d*d+d*od+od*od + dver*dver+odver*dver+odver*odver) ; } // if we emerge with 'legal' non-zero then we may advance to pathpos+i // and tdist is the sum of squared errors if(legal) nstk.push ( { data: stk[0].data.concat([ndatum]) , err: stk[0].err + pi*tdist + parms.wppenalty , pathpos: stk[0].pathpos+i+1 } ) ; if(ndatum.type!=null||ndatum.photo.length>0) break ; } // end loop over i for(stk2=[],i=1,j=0;i<stk.length||j<nstk.length;) if(i==stk.length) stk2.push(nstk[j++]) ; else if(j==nstk.length||stk[i].pathpos<nstk[j].pathpos) stk2.push(stk[i++]) ; else if(stk[i].pathpos>nstk[j].pathpos) stk2.push(nstk[j++]) ; else if(stk[i].err<nstk[j].err) { stk2.push(stk[i++]) ; j += 1 ; } else { stk2.push(nstk[j++]) ; i += 1 ; } stk = stk2 ; } return stk[0].data ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------------------------------------------------- */ /* THE MAIN POPUP MENU AND THE FUNCTIONS IT GOVERNS */ /* -------------------------------------------------------------------------- */ function popup() { var opts,pos,s0=selected[0],s1=selected[1] ; infowindow.close() ; if(dragging) { opts = finalbox + "Hit [return] when you've finished dragging.</div>" ; infowindow.open(opts,getbtnpos(0),'settings') ; return ; } // route options opts = textbox + active + '"routeinfo()">Route info</span><br>' ; if(altdiv==null) opts += active + '"profile()">Show altitude profile</span><br>' ; else opts += active + '"unprofile()">Hide altitude profile</span><br>' ; opts += active + '"addload(1)">Load new route</span></div>' + textbox ; // segment options if(unambig()) opts += active + '"revseg()">' ; else opts += inactive ; opts += 'Reverse segment</span><br>' ; opts += active + '"manualcal()">Calibrate segment altitudes</span><br>' ; opts += active + '"addload(0)">Load route as a new segment</span></div>' ; // waypoint options opts += textbox + active + '"wpinfo()">Waypoint info</span><br>' ; if(segments[selected[0]].data.length>1) opts += active + '"wpdel()">' ; else opts += inactive ; opts += 'Delete waypoint</span><br>' ; opts += active + '"draggit(0)">Make waypoint draggable</span><br>' ; opts += active + '"inswp(1)">Insert draggable waypoint ahead</span><br>' ; opts += active + '"inswp(-1)">Insert draggable waypoint behind</span>' ; opts += '</div>'+finalbox+active ; // tool options if( document.documentElement.requestFullscreen || document.documentElement.mozRequestFullScreen || document.documentElement.webkitRequestFullscreen || document.documentElement.msRequestFullscreen) { if( (document.fullScreenElement && document.fullScreenElement !== null) || (!document.mozFullScreenElement && !document.webkitFullScreenElement) ) opts += '"enterFullscreen()">Enter full screen</span><br>' + active ; else opts += '"exitFullscreen()">Leave full screen</span><br>' + active ; } opts += '"help()">Help</span></div>' ; infowindow.open(opts,getbtnpos(0),'settings') ; } /* ------------------------------- calwork --------------------------------- */ function calwork(s0,y) { var i,s1 ; for(s1=0;s1<segments[s0].data.length;s1++) if(segments[s0].data[s1].h!=null) segments[s0].data[s1].h += y ; reprofile() ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ------------------------------ manualcal --------------------------------- */ function manualcal() { infowindow.close() ; var x,y,s0=selected[0] ; x = prompt('Enter offset in metres to add to altidudes:') ; if(x==null) return ; y = parseFloat(x) ; if(isNaN(y)) { alert(x+' is not a number') ; return ; } calwork(s0,y) ; done(['recal',s0,y]) ; } /* --------------------------------- help ----------------------------------- */ function help() { var str ; infowindow.close() ; str = textbox + '<table cellpadding=0 cellspacing=0>' ; str += '<tr><td style="padding-bottom:4px">' ; str += '<nobr><img src="'+cursorbtn.greyimg+'" width=24 height=24>&nbsp;' ; str += '<img src="'+cursorbtn.blackimg+'" width=24 height=24></nobr>' ; str += '<td rowspan=6>&nbsp;&nbsp;&nbsp;' ; str += '<td style="padding-bottom:4px">' ; str += 'toggle between using the mouse to select waypoints and to ' ; str += 'drag the map<br>(the space bar has the same function)' ; str += '<tr><td style="padding-bottom:4px">' ; str += '<nobr><img src="'+scissorsbtn.blackimg+'" width=24 height=24>&nbsp;' ; str += '<img src="'+binbtn.blackimg+'" width=24 height=24></nobr>' ; str += '<td style="padding-bottom:4px">' ; str += 'split the current segment at the selected point<br>' ; str += 'delete the currrent segment (or use the delete or backspace key)' ; str += '<tr><td style="padding-bottom:4px">' ; str += '<img src="'+penbtn.blackimg+'" width=24 height=24>' ; str += '<td style="padding-bottom:4px">' ; str += 'add a labelled coursepoint at the current position (1-10chars)<br>' ; str += 'click on flag to edit; right-click to change symbol; ' ; str += 'delete label to delete' ; str += '<tr><td style="padding-bottom:4px">' ; str += '<nobr><img src="'+dlbtn.blackimg+'" width=24 height=24>&nbsp;' ; str += '<img src="'+setbtn.blackimg+'" width=24 height=24></nobr>' ; str += '<td style="padding-bottom:4px">download route as .tcx<br>' ; str += 'access to miscellaneous tools and functions' ; str += '<tr><td valign=top>Keyboard: <td>' ; str += '\u2190/\u2192 move the current waypoint forwards or backwards;<br>' ; str += '\u2193 centres the map on the current waypoint;<br>' ; str += '[return] makes the current waypoint draggable;<br>' ; str += '[tab] inserts a draggable waypoint;<br>[space]=toggle cursor mode;' ; str += '<br>[del], [backspace]=delete segment (bin button).' ; str += '<tr><td valign=top>Mouse: <td>when the cursor is in selection' ; str += ' mode:<br>[shift click] extends the current segment by' ; str += ' the cursor position.</table></div>' ; str += finalbox + '<a style="cursor:pointer;color:#0000bd;text-'+ 'decoration:none" href="http://www.masterlyinactivity.com/software'+ '/routemaster.html" target="_blank">Technical documentation and '+ 'source code</a>'+neutral+' (opens in new tab/window)</span></div>' ; infowindow.open(str,getbtnpos(0),'help') ; } /* -------------------------------------------------------------------------- */
/*page*/ /* --------------------------------- wpdel ---------------------------------- */ function wpdelwork(s0,s1) { var i,response=segments[s0].data[s1],clen=segments[s0].data.length ; response.setmap(null) ; for(i=s1;i<clen-1;i++) segments[s0].data[i] = segments[s0].data[i+1] ; segments[s0].data.length = clen-1 ; selected = [s0,s1] ; if(s1==segments[s0].data.length) selected[1] -= 1 ; redrawconnect(s0,s1) ; drawsel(1) ; return response ; } function wpdel() { var s0=selected[0],s1=selected[1],i ; infowindow.close() ; done(['wpdel',s0,s1,wpdelwork(s0,s1)]) ; } /* --------------------------------- revseg --------------------------------- */ function revsegwork(s0) { var i,s=segments[s0],j,x,len=s.data.length ; disconnect(s0-1) ; disconnect(s0) ; for(i=0;i<len/2;i++) { j = (len-1) - i ; x = s.data[i] ; s.data[i] = s.data[j] ; s.data[j] = x ; } for(i=0;i<s.data.length;i++) if(s.data[i].type=='Right') s.data[i].settype('Left') ; else if(s.data[i].type=='Left') s.data[i].settype('Right') ; if(s0==selected[0]) selected[1] = (len-1) - selected[1] ; connect(s0-1) ; connect(s0) ; drawsel(1) ; } /* -------------------------------------------------------------------------- */ function revseg() { infowindow.close() ; revsegwork(selected[0]) ; done(['revseg',selected[0]]) ; } /* -------------------------------------------------------------------------- */ function addload(overwrite) { var msg ; infowindow.close() ; if(overwrite) { msg = unsavedmsg(1) ; if(msg!=null) if(!confirm(msg)) return ; } infowindow.open(filedialogue(overwrite),getbtnpos(0),'addload') ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------------------------------------------------- */ /* DRAGGING A (POSSIBLY NEWLY INSERTED) WAYPOINT IS QUITE A LOT OF WORK */ /* -------------------------------------------------------------------------- */ function insert(s0,s1,n) { var i ; for(i=segments[s0].data.length+n-1;i>s1;i--) segments[s0].data[i] = segments[s0].data[i-n] ; for(i=0;i<n;i++) segments[s0].data[s1+i] = new datatype(null,null) ; } /* --------------------------------- inswp ---------------------------------- */ function inswp(dir) { var s0=selected[0],s1=selected[1],bounds,del,pos,data=segments[s0].data ; var len = data.length ; if(len==1) pos = data[0].pos ; if(dir>=0) s1 = selected[1] += 1 ; insert(s0,s1,1) ; if(len==1) { bounds = map.getBounds() ; del = bounds.getNorthEast().lng() - bounds.getSouthWest().lng() ; pos = new google.maps.LatLng(pos.lat(),pos.lng()+dir*del/10) ; } else if(s1==0) pos = interp(data[2].pos,data[1].pos,1.5) ; else if(s1<len) pos = interp(data[s1-1].pos,data[s1+1].pos,0.5) ; else pos = interp(data[s1-2].pos,data[s1-1].pos,1.5) ; data[s1].setpos(pos) ; draggit(1) ; } /* -------------------------------- draggit --------------------------------- */ // draggit makes the current waypoint draggable var l1,l2,startpos,seg0,seg1,seg2,colour,inserted ; function draggit(insparm) { var s0=selected[0],s1=selected[1],start,end,i,len=segments[s0].data.length ; startpos = segments[s0].data[s1].pos ; inserted = insparm ; infowindow.close() ; greyout(scissorsbtn) ; greyout(binbtn) ; greyout(penbtn) ; greyout(undobtn) ; greyout(redobtn) ; greyout(dlbtn) ; map.panToBounds(new google.maps.LatLngBounds(startpos,startpos)) ; sel.marker.setMap(null) ; sel.marker = new google.maps.Marker( { position: segments[s0].data[s1].pos, map: map, cursor: 'default', icon: concircle , draggable: true , zIndex: 2 } ) ; if(s0&1) colour = "#ff9999" ; else colour = "#ff0000" ; segments[s0].route.setMap(null) ; if(segments[s0].clickhandler!=null) { google.maps.event.removeListener(segments[s0].clickhandler) ; segments[s0].clickhandler = null ; } seg0 = seg2 = null; if(s1>1) { seg0 = new google.maps.Polyline(new linepath(s0,0,s1,colour)) ; seg0.setMap(map) ; } if(s1==0) start = 0 ; else start = s1-1 ; if(s1==len-1) end = s1+1 ; else end = s1+2 ; seg1 = new google.maps.Polyline(new linepath(s0,start,end,colour)) ; seg1.setMap(map) ; if(s1<segments[s0].data.length-2) { seg2 = new google.maps.Polyline(new linepath(s0,s1+1,len,colour)) ; seg2.setMap(map) ; } l1 = google.maps.event.addListener(sel.marker,'drag',function() { segments[s0].data[s1].setpos(this.getPosition()) ; seg1.setMap(null) ; seg1 = new google.maps.Polyline(new linepath(s0,start,end,colour)) ; seg1.setMap(map) ; if(s1==0&&s0>0) { disconnect(s0-1) ; connect(s0-1) ; } if(s1==len-1&&s0<segments.length-1) { disconnect(s0) ; connect(s0) ; } } ) ; dragging = 1 ; } /* ------------------------------- undraggit -------------------------------- */ // undraggit is invoked by [return] to terminate waypoint dragging function undraggit() { var s0=selected[0],s1=selected[1],i,s1dash,pos=segments[s0].data[s1].pos ; var xpos ; google.maps.event.removeListener(l1) ; dragging = 0 ; if(seg0!=null) seg0.setMap(null) ; seg1.setMap(null) ; if(seg2!=null) seg2.setMap(null) ; segments[s0].route = new google.maps.Polyline(new linepath(s0,-1,0,colour)) ; segments[s0].route.setMap(map) ; segments[s0].data[s1].h = null ; lookupalt(s0,s1) ; sel.marker.setMap(null) ; sel.marker = null ; // force a redraw drawsel(1) ; if(inserted||dist(startpos,pos)>5) done(['move',s0,s1,startpos,pos,inserted]) ; if(segments.length==1) blackout(dlbtn) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------------------------------------------------- */ /* CODE TO GET ALTITUDES FOR NEWLY INSERTED POINTS */ /* -------------------------------------------------------------------------- */ function lookupalt(s0,s1) // set up a request for the alt of the new point { var hi,lo,segno=s0,ptno=s1,lopos,hipos,datum=segments[s0].data[s1] ; for(lo=null,s0=segno,s1=ptno-1;lo==null;s1--) { if(s1<0) { s0 -= 1 ; if(s0<0) break ; s1 = segments[s0].data.length-1 ; } if(segments[s0].data[s1].h!=null) lo = [s0,s1] ; } for(hi=null,s0=segno,s1=ptno+1;hi==null;s1++) { if(s1==segments[s0].data.length) { s0 += 1 ; if(s0==segments.length) break ; s1 = 0 ; } if(segments[s0].data[s1].h!=null) hi = [s0,s1] ; } if(lo==null&&hi==null) { alert('no points left with altitudes: unable to proceed') ; throw '' ; } if(lo!=null) lopos = segments[lo[0]].data[lo[1]].pos ; if(hi!=null) hipos = segments[hi[0]].data[hi[1]].pos ; if(lo==null||(hi!=null&&dist(lopos,datum.pos)>dist(hipos,datum.pos))) { lo = hi ; lopos = hipos ; } pending.push([datum,lopos,segments[lo[0]].data[lo[1]].h]) ; elevator.getElevationForLocations({locations:[datum.pos,lopos]},calibrate) ; } /* -------------------------------------------------------------------------- */ // pending is the list of inserted points for which google altitudes are needed // note a pitfall with the elevation service - it's hard to tell which // response corresponds to which request: the coordinates may not match // because google truncates to 0.00001 deg; hence the use of the dist function. function calibrate(results,status) { var pno,flag ; if(status===google.maps.ElevationStatus.OVER_QUERY_LIMIT) alert('No calibration data available: over Google query limit') ; else if(status!==google.maps.ElevationStatus.OK||results.length!=2) alert('Calibration error') ; // now find whether any of our elevation results allow us to fix an altitude for(pno=0;pno<pending.length;pno++) if( dist(pending[pno][0].pos,results[0].location)<5 && dist(pending[pno][1],results[1].location)<5 ) { if(pno>0) alert('Warning: Google elevation results out of sequence') ; diff = results[0].elevation - results[1].elevation ; pending[pno][0].h = pending[pno][2] + diff ; } for(i=pno=0;pno<pending.length;pno++) if(pending[pno][0].h==null) { if(pno!=i) pending[i] = pending[pno] ; i += 1 ; } pending.length = i ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------------------------------------------------- */ /* FUNCTIONS FOR COMPUTING & DISPLAYING THE ALTITUDE PROFILE */ /* -------------------------------------------------------------------------- */ function profile() { infowindow.close() ; var i,sum,s0,s1,len,oldpos,pos,alt,h,amin,amax,c,y,step,ctx ; var startpos,prevpos ; for(pron=s0=0;s0<segments.length;s0++) pron += segments[s0].data.length ; prox = new Array(pron) ; proa = new Array(pron) ; smap = new Array(segments.length) ; cmap = new Array(610) ; prox[0] = 0 ; for(amax=amin=null,sum=pron=s0=0;s0<segments.length;s0++) for(len=segments[s0].data.length,s1=0;s1<len;s1++,pron++) { proa[pron] = segments[s0].data[s1].h ; if(proa[pron]!=null) { if(amax==null||proa[pron]>amax) amax = proa[pron] ; if(amin==null||proa[pron]<amin) amin = proa[pron] ; } pos = segments[s0].data[s1].pos ; if(pron) sum = prox[pron] = sum + dist(pos,oldpos) ; oldpos = pos ; } if(pron==0||amin==amax) return ; if(amin>0) { if(amax>3*amin) amin = 0 ; else amin *= 1 - (amax/amin-1)/2 ; } altdiv = document.createElement('div') ; altdiv.setAttribute ('style','position:absolute;height:190px;right:0;top:0;width:620px') ; c = document.createElement('canvas') ; c.setAttribute('width',620) ; c.setAttribute('height',200) ; altdiv.appendChild(c) ; body.appendChild(altdiv) ; ctx = c.getContext("2d") ; ctx.font = "10px Helvetica" ; ctx.lineWidth = 0 ; ctx.globalAlpha = 0.6 ; ctx.fillStyle = 'lightgray' ; ctx.lineWidth = 0 ; ctx.rect(0,0,620,200) ; ctx.fill() ; // draw a profile of each segment for(i=pron=s0=0;s0<segments.length;s0++) { len = segments[s0].data.length ; smap[s0] = new Array(len) ; if(s0&1) ctx.fillStyle = "#ff9999" ; else ctx.fillStyle = "#ff0000" ; for(startpos=opos=null,s1=0;s1<len;prevpos=[s0,s1],s1++,pron++,opos=pos) { pos = 10 + 600 * prox[pron] / sum ; if(opos!=null) for(;i<(pos+opos)/2;i++) cmap[i-10] = prevpos ; smap[s0][s1] = 0.5 + Math.floor(pos) ; if(proa[pron]!=null) { y = 10 + 180 * (amax-proa[pron]) / (amax-amin) ; if(startpos==null) { ctx.beginPath() ; ctx.moveTo(pos,y) ; startpos = pos ; } else ctx.lineTo(pos,y) ; } } ctx.lineTo(pos,190) ; ctx.lineTo(startpos,190) ; ctx.closePath() ; ctx.fill() ; } for(;i<610;i++) cmap[i] = prevpos ; // lines if(amax-amin>2500) step = 1000 ; else if(amax-amin>1250) step = 500 ; else step = 100 ; for(i=step*Math.floor(amin/step+1);i<amax;i+=step) { y = 10.5 + Math.floor(180*(amax-i)/(amax-amin)) ; ctx.beginPath() ; ctx.lineWidth = 1 ; ctx.strokeStyle = '#555' ; ctx.moveTo(10,y) ; ctx.lineTo(610,y) ; ctx.stroke() ; ctx.strokeText(i,590,y-2) ; } // cursor curdiv = document.createElement('div') ; curdiv.setAttribute ('style','position:absolute;height:190px;right:0;top:0;width:620px') ; curhandle = curdiv.addEventListener("click",function(e) { var pos = e.clientX - (window.innerWidth-610) ; if((pos-594)*(pos-594)+(e.clientY-16)*(e.clientY-16)<200) { unprofile() ; return ; } if(pos<0) pos = 0 ; else if(pos>600) pos = 600 ; selected = cmap[pos] ; drawsel(0) ; } ) ; body.appendChild(curdiv) ; curcan = null ; procur() ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ------------------------------- unprofile -------------------------------- */ function unprofile() { var i,match,node ; infowindow.close() ; if(altdiv==null) return ; curdiv.removeEventListener('click',curhandle) ; for(match=0,i=body.childNodes.length-1;match==0&&i>=0;i--) { node = body.childNodes[i] ; match = (node==altdiv) ; body.removeChild(node) ; } prox = proa = altdiv = cmap = smap = curdiv = curcan = curhandle = null ; } function reprofile() { if(altdiv!=null) { unprofile() ; profile() ; } } /* -------------------------------- procur ---------------------------------- */ function procur() { if(altdiv==null) return ; var pos = smap[selected[0]][selected[1]] , ctx , i ; if(curcan!=null) curdiv.removeChild(curcan) ; curcan = document.createElement('canvas') ; curcan.setAttribute('width',620) ; curcan.setAttribute('height',200) ; curdiv.appendChild(curcan) ; ctx = curcan.getContext("2d") ; ctx.beginPath() ; ctx.lineWidth = 1 ; ctx.moveTo(pos,10) ; ctx.lineTo(pos,190) ; ctx.stroke() ; // the circle of the 'x' ctx.beginPath() ; ctx.strokeStyle = '#555' ; ctx.fillStyle = 'white' ; ctx.lineWidth = 3 ; ctx.arc(604,16,14.1,0,2*Math.PI,false) ; ctx.stroke() ; ctx.fill() ; for(i=6;i<=26;i+=20) // the two bars of the 'x' { ctx.beginPath() ; ctx.moveTo(594,i) ; ctx.lineTo(614,32-i) ; ctx.stroke() ; } } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------------------------------------------------- */ /* ROUTEINFO IS A MENU WHICH ITSELF SUPPORTS THE COMBINE FUNCTION */ /* -------------------------------------------------------------------------- */ function routeinfo() { var s0,s1,s,d,asc,des,oalt,alt,nlabels,nowpts,i,unsaved,props,spacing,npix ; var maxsep,sep,outoforder,tlast,otime,time,tdist,ttime,ntimes ; infowindow.close() ; props = actions[loadno][4] ; tlast = null ; tdist = ttime = outoforder = 0 ; maxsep = nlabels = npix = des = asc = d = nowpts = ntimes = 0 ; for(s0=0;s0<segments.length;s0++) { nowpts += segments[s0].data.length ; for(oalt=null,s1=0;s1<segments[s0].data.length;otime=time,s1++) { if((alt=segments[s0].data[s1].h)!=null) { if(oalt!=null) { if(alt>oalt) asc += alt-oalt ; else des += oalt - alt ; } oalt = alt ; } if(segments[s0].data[s1].type!=null) nlabels += 1 ; npix += segments[s0].data[s1].photo.length ; time = segments[0].data[s1].t ; if(time!=null) { time = time.getTime() ; ntimes += 1 ; } if(tlast!=null&&time!=null&&time<tlast) outoforder = 1 ; // out of order if(time!=null) tlast = time ; if(s1) { sep = dist(segments[s0].data[s1-1].pos,segments[s0].data[s1].pos) ; d += sep ; if(sep>maxsep) maxsep = sep ; if(time!=null&&otime!=null) { tdist += sep/1000 ; ttime += (time-otime)/(3600*1000) ; } } } } s = finalbox +'<nobr>Title: <b>'+routetitle+'</b> [' ; s += active + '"retitle()">Edit</span>'+']</nobr><br>' ; if(loadno>0) { s += '<nobr>&nbsp;&nbsp;&nbsp;Last added route' ; if(props.title!=null) s += ' (' + props.title + ')' ; s += ':</nobr><br><nobr>&nbsp;&nbsp;&nbsp;' ; } else s += '<nobr>' ; s += 'Track points on input: ' + props.inputlen ; if(props.optim.already) { s += ' (previously optimised)</nobr>' ; if(nowpts!=props.inputlen) s += '<br>Now ' + nowpts + ' track points' ; } else if(props.optim.ndel==0) { if(nactions==loadno+1) s += ' [' + active + '"optimprompt()">' ; else s += ' [' + inactive ; s += 'Optimise' + neutral + ']</span></nobr>' ; } else s += ', optimised to ' + (props.inputlen-props.optim.ndel) + '</nobr>' ; /* ------------------------------------------------------------------------ */
/*page*/ /* ------------------------------------------------------------------------ */ if(!props.optim.already&&props.inputlen-props.optim.ndel!=nowpts) s += '<br>Now ' + nowpts + ' track points' ; s += '<br>' ; if(outoforder==0) { if(ntimes==0) s += 'No timings provided<br>' ; else { if(ntimes<nowpts) s += (nowpts-ntimes) + ' points have no associated timings ' ; s += '[' + active + '"deltimes()">Discard timings</span>' + neutral + ']<br>' ; } if(tdist>0&&ttime>0) s += 'Average speed = ' + (tdist/ttime).toFixed(1) + ' km/hr<br>' ; } else s += 'Times are out of sequence (will be discarded on download)<br>' ; if(nlabels>0) s += nlabels + ' labelled course point' + (nlabels>1?'s':'') + '<br>' ; if(npix>0) s += npix + ' photo' + (npix>1?'s':'') + '<br>' ; unsaved = unsavedchanges.length ; if(unsaved>0) s += unsaved + ' unsaved change' + (unsaved>1?'s':'') + '<br>' ; if(segments.length>1) s += segments.length + ' segments [' + active + '"combine()">Combine</span>' + neutral + ']<br>' + '<i>Note that segments must be combined before saving</i><br>' ; s += 'Max waypoint separation: '+maxsep.toFixed(0)+'m<br>' ; if(maxsep>=100) s += '<i>Note that separations &gt;100m are illegal on Garmin</i><br>' + '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[</span>' + active + '"extrapts()">' + 'Interpolate extra points</span>]<br>' + neutral ; s += 'Total distance: '+(d/1000).toFixed(3)+'km<br>' ; s += 'Total ascent: '+asc.toFixed(0)+'m<br>' ; s += 'Total descent: '+des.toFixed(0)+'m</div>' ; infowindow.open(s,getbtnpos(0),'routeinfo') ; } /* -------------------------------------------------------------------------- */ function deltimes() { var s0,s1,task=[] ; for(s0=0;s0<segments.length;s0++) for(s1=0;s1<segments[s0].data.length;s1++) if(segments[s0].data[s1].t!=null) { task.push([s0,s1,segments[s0].data[s1].t]) ; segments[s0].data[s1].t = null ; } infowindow.close() ; done(['deltimes',task]) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ------------------------- interpolate extra points ----------------------- */ function extrapts(opt) { var s0,s1,sep,data,n,opos,npos,i,lambda,lox,nlox,taskno,ind ; var task = [ 'extra' , selected[0] , selected[1] ] ; infowindow.close() ; for(nlox=s0=0;s0<segments.length;s0++) for(data=segments[s0].data,s1=1;s1<data.length;s1++) if((sep=dist(opos=data[s1-1].pos,npos=data[s1].pos))>100) { n = Math.floor(sep/95) ; insert(s0,s1,n) ; for(i=0;i<n;i++) { lambda = (i+1) / (n+1) ; data[s1+i].setpos(new google.maps. LatLng(lambda*npos.lat()+(1-lambda)*opos.lat(), lambda*npos.lng()+(1-lambda)*opos.lng())) ; } if(selected[0]==s0&&s1<=selected[1]) selected[1] += n ; task.push([s0,s1,data.slice(s1-1,s1+n+1)]) ; s1 += n ; nlox += n+2 ; } if(nlox==0) return ; done(task) ; xpending.push(task) ; lox = new Array(nlox) ; for(ind=0,taskno=3;taskno<task.length;taskno++) { n = task[taskno][2].length ; for(i=0;i<n;i++) lox[ind++] = task[taskno][2][i].pos ; } elevator.getElevationForLocations( {locations:lox} , function (results,status) { // assume that the results come in sequence, ie. correspond to xpending[0] var task=xpending.shift(),taskno,d0,dn,lambda ; if(status===google.maps.ElevationStatus.OVER_QUERY_LIMIT) alert('No calibration data available: over Google query limit') ; else if(status!==google.maps.ElevationStatus.OK) alert('Calibration error') ; for(ind=0,taskno=3;taskno<task.length;taskno++,ind+=n+2) { n = task[taskno][2].length-2 ; d0 = task[taskno][2][0].h - results[ind].elevation ; dn = task[taskno][2][n+1].h - results[ind+n+1].elevation ; if( dist(task[taskno][2][0].pos,results[ind].location)>5 || dist(task[taskno][2][n+1].pos,results[ind+n+1].location)>5 ) alert('Anomaly with Google elevation results') ; for(i=0;i<n;i++) { lambda = (i+1) / (n+1) ; task[taskno][2][1+i].h = results[ind+1+i].elevation + lambda*dn + (1-lambda)*d0 ; } } } ) ; routeinfo() ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ------------------------------- combine1 --------------------------------- */ function combine1(sa,sb) { var i,ca,cb,calen,cblen,la=null,lb=null,cdup=0 ; undraw(sb) ; disconnect(sb-1) ; calen = segments[sa].data.length ; cblen = segments[sb].data.length ; cb = segments[sb].data[0].pos ; cdup = ( ca = cb.equals(segments[sa].data[calen-1].pos) ) ; if(cdup) { la = segments[sa].data[calen-1] ; lb = segments[sb].data[0] ; segments[sa].data.length = ( calen -= 1 ) ; } if(selected[0]==sb) selected = [ sa , selected[1]+calen ] ; segments[sa].data = segments[sa].data.concat(segments[sb].data) ; return [ cblen , cdup , la , lb ] ; } function combinework() { var task,s0 ; for(task=['combine',segments.length],s0=1;s0<segments.length;s0++) task.push(combine1(0,s0)) ; segments.length = 1 ; return task ; } /* -------------------------------------------------------------------------- */ function combine() { infowindow.close() ; done(combinework()) ; drawsel(1) ; redraw(0) ; blackout(dlbtn) ; } function recombine() { var s0 ; for(s0=1;s0<segments.length;s0++) { undraw(s0) ; combine1(0,s0) ;} segments.length = 1 ; drawsel(1) ; redraw(0) ; blackout(dlbtn) ; } /* -------------------------------------------------------------------------- */ // combine returns [ cblen , cdup , la , lb ] ; function uncombine(task) { var i,j,llen,flag,subtask ; for(flag=0,s0=task[1]-1,i=task.length-1;i>=2;i--,s0--) { subtask = task[i] ; cblen = subtask[0] ; cdup = subtask[1] ; llen = segments[0].data.length ; segments[s0] = { data: segments[0].data.slice(llen-cblen,llen) , route: null , clickhandler: null } ; llen = segments[0].data.length = llen+cdup-cblen ; if(cdup) { segments[0].data[llen-1] = subtask[2] ; segments[s0].data[0] = subtask[3] ; } if(flag==0&&selected[1]>=llen) { selected = [ s0 , selected[1]-llen ] ; flag = 1 ; } } drawsel(1) ; undraw(0) ; for(i=0;i<segments.length;i++) { draw(i) ; connect(i) ; } greyout(dlbtn) ; } /* -------------------------------------------------------------------------- */ /* WPINFO IS A MENU GIVING ACCESS TO THE SETALT FUNCTION */ /* -------------------------------------------------------------------------- */ function wpinfo() { infowindow.close() ; var s0=selected[0],s1=selected[1],alt,nalt,s,x,lat,lng,grad,gradstr,time ; var datum = segments[s0].data[s1] , pos = datum.pos ; lat = pos.lat() ; lng = pos.lng() ; s = finalbox ; if(lat>=0) s += lat.toFixed(5) + '\u00b0 N, ' ; else { lat = -lat ; s += lat.toFixed(5) + '\u00b0 S, ' ; } if(lng>=0) s += lng.toFixed(5) + '\u00b0 E<br>' ; else { lng = -lng ; s += lng.toFixed(5) + '\u00b0 W<br>' ; } x = new LatLon(lat,lng) ; if(lat>49.9&&lat<62&&lng>-12&&lng<2.5&&lat-1.5*lng<75) s += 'OS grid ref: ' + OsGridRef.latLonToOsGrid(x) ; else s += 'UTM coords = ' + x.toUtm() ; s += '<br>' ; alt = segments[s0].data[s1].h ; if(alt!=null) s += 'Altitude: ' + alt.toFixed(0) + 'm ' + active + '"setalt(1)">[Edit]' ; else s += active + '"setalt(0)">Set altitude' ; s += '</span><br>' ; time = segments[s0].data[s1].t ; if(time!=null&&time.getFullYear()>1980) s += 'Date: ' + time.toDateString() + '<br>' + 'Time: ' + time.toTimeString() + '<br>' ; if(datum.type!=null) s += datum.type + ': ' + datum.marker.title + ' [' + active + '"labelprompt()">Edit</span>]<br>' ; if(alt==null||s1==segments[s0].data.length-1) nalt = null ; else { nalt = segments[s0].data[s1+1].h ; if(nalt!=null) x = dist(pos,segments[s0].data[s1+1].pos) ; } if(nalt!=null&&Math.abs(nalt-alt)<x) { grad = 100*Math.asin((nalt-alt)/x) ; gradstr = Math.abs(grad).toFixed(0) ; if(gradstr=='0') s += 'Flat<br>' ; else if(grad>0) s += 'Climb '+gradstr+'%<br>' ; else s += 'Descend: '+gradstr+'%<br>' ; } s += '<span style="font-size:80%">' ; if(segments.length>1) s += 'Segment '+s0+' p' ; else s += 'P' ; s += 'oint ' + s1 + '</span></div>' ; infowindow.open(s,pos,'wpinfo') ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------- setalt ---------------------------------- */ function setalt(edit) { infowindow.close() ; var s0=selected[0],s1=selected[1],x,y=null,oldalt ; oldalt = segments[s0].data[s1].h.toFixed(0) ; if(edit) x = prompt('Enter altitude (m):',oldalt) ; else x = prompt('Enter altitude (m):') ; if(x==null) return ; if(x!='') { y = parseFloat(x) ; if(isNaN(y)) { alert(x+' is not a number') ; return ; } } if(y==null&&oldalt==null) return ; if(y!=null&&Math.abs(y-oldalt)<0.1) return ; segments[s0].data[s1].h = y ; done(['setalt',s0,s1,oldalt,y]) ; reprofile() ; wpinfo() ; } /* -------------------------------------------------------------------------- */ /* THE LABELS ARE ACCESSED FROM THE PEN BUTTON OR BY CLICKING ON THE MAP */ /* -------------------------------------------------------------------------- */ function labelprompt() { var oldcaption='',oldtype,type='Generic',s0=selected[0],s1=selected[1] ; var str , flag = (infowindow.close()=='wpinfo') ; var datum = segments[s0].data[s1] ; oldtype = datum.type ; if(oldtype!=null) oldcaption = datum.marker.title ; if(oldcaption==null) oldcaption = '' ; if(oldtype!=null) type = oldtype ; if(oldtype==null) str = 'Enter' ; else str = 'Modify or delete' ; var caption = window.prompt(str+' label:',oldcaption) ; if(caption==null) { if(flag) wpinfo() ; else walkto(s0,s1,0) ; return ; } else if(caption=='') type = null ; else caption = caption.substring(0,10) ; if(caption==oldcaption) { if(flag) wpinfo() ; else walkto(s0,s1,0) ; return ; } segments[s0].data[s1].setlabel(type,caption) ; done(['editlabel',s0,s1,oldcaption,caption,oldtype,type]) ; if(flag) wpinfo() ; else walkto(s0,s1,0) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ------------------------------ labelcycle -------------------------------- */ function labelcycle() { var s0,s1,datum,oldtype,caption,type,flag=(infowindow.close()=='wpinfo') ; ; for(s0=0;s0<segments.length;s0++) for(s1=0;s1<segments[s0].data.length;s1++) { datum = segments[s0].data[s1] ; if(datum.marker!=this) continue ; oldtype = datum.type ; caption = datum.marker.title ; if(oldtype=='Generic') type = 'Left' ; else if(oldtype=='Left') type = 'Straight' ; else if(oldtype=='Straight') type = 'Right' ; else if(oldtype=='Right') type = 'Danger' ; else type = 'Generic' ; datum.settype(type) ; selected = [s0,s1] ; done(['editlabel',s0,s1,caption,caption,oldtype,type]) ; if(flag) wpinfo() ; return ; } } function photoprompt(e) { var s0=selected[0],s1=selected[1] ; if(e!=null) e.preventDefault() ; var flag = (infowindow.close()=='wpinfo') ; var datum = segments[s0].data[s1] ; var photo = window.prompt('Enter photo name:','') ; if(photo!=null&&photo!='') { done(['editphoto',s0,s1,datum.photo.length,null,photo]) ; datum.addphoto(photo) ; } if(flag) wpinfo() ; else walkto(s0,s1,0) ; } function photoedit(ind) { var s0=selected[0],s1=selected[1],i ; var flag = (infowindow.close()=='wpinfo') ; var datum = segments[s0].data[s1] ; var photo = window.prompt('New photo name:',datum.photo[ind]) ; if(photo!=null&&photo!='') for(i=0;i<datum.photo.length;i++) if(datum.photo[i]==photo) { photo = null ; break ; } if(photo!=null) { if(photo=='') photo = null ; done(['editphoto',s0,s1,ind,datum.photo[ind],photo]) ; datum.setphoto(ind,photo) ; } if(flag) wpinfo() ; else walkto(s0,s1,0) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ----------------------------- enlarge photo ------------------------------ */ function enlarge(ind,i) { document.onkeydown = imgwalk ; infowindow.close() ; imgind = ind ; selinit = [ selected[0] , selected[1] , ind ] ; imgdiv = document.createElement('div') ; imgdiv.setAttribute ('style','position:fixed;width:100%;height:100%;left:0;top:0') ; imghandle = imgdiv.addEventListener("click",imgwalk) ; imgtable = enlargetable(i) ; imgdiv.appendChild(imgtable) ; body.appendChild(imgdiv) ; } /* ------------------------------ image walk -------------------------------- */ function imgwalk(e) { var s0,s1,ind ; if(e.keyCode==16) shift = 1 ; else if(e.keyCode==39) // forwards { e.preventDefault() ; for(s0=selected[0],s1=selected[1],ind=imgind+1;;ind++) { if(ind>=segments[s0].data[s1].photo.length) { ind = 0 ; s1 += 1 ; } if(s1==segments[s0].data.length) { s0 += 1 ; if(s0==segments.length) s0 = 0 ; s1 = 0 ; } if(s0==selinit[0]&&s1==selinit[1]&&ind==selinit[2]) break ; if(imgreplace(s0,s1,ind)) return ; } } else if(e.keyCode==37) // backwards { e.preventDefault() ; for(s0=selected[0],s1=selected[1],ind=imgind-1;;ind--) { if(ind<0) { s1 -= 1 ; ind = null ; } if(s1<0) { if(s0==0) s0 = segments.length-1 ; else s0 -= 1 ; s1 = segments[s0].data.length-1 ; } if(ind==null) ind = segments[s0].data[s1].photo.length - 1 ; if(s0==selinit[0]&&s1==selinit[1]&&ind==selinit[2]) break ; if(imgreplace(s0,s1,ind)) return ; } } document.onkeydown = walk ; imgdiv.removeEventListener('click',imghandle) ; body.removeChild(imgdiv) ; walkto(selected[0],selected[1],0) ; } /* ----------------------------- image replace ------------------------------ */ function imgreplace(s0,s1,ind) { var i ; if(ind>=0&&segments[s0].data[s1].photo.length>ind) if((i=findimg(segments[s0].data[s1].photo[ind]))>=0) { imgdiv.removeChild(imgtable) ; imgtable = enlargetable(i) ; imgdiv.appendChild(imgtable) ; selected = [s0,s1] ; imgind = ind ; return 1 ; } else return 0 ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------- generate image table -------------------------- */ function enlargetable(i) { var w=window.innerWidth,h=window.innerHeight,t,td,tr,span,img,shape=[0,0] ; var k,ibest,ismall,spare,rooturi,srcset,imguri ; spare = (imginfo.list[i].caption==undefined?20:36) ; for(k=0;k<2;k++) shape[k] = imginfo.list[i].shape[k] ; // find the best size to use for(ismall=ibest=null,k=0;k<imginfo.sizes.length;k++) if(imginfo.sizes[k].type==undefined) { if(ismall==null||imginfo.sizes[k].scale<imginfo.sizes[ismall].scale) ismall = k ; r = imginfo.sizes[k].scale / imginfo.sizes[0].scale ; if(shape[0]*r<=w&&shape[1]*r+spare<=h) if(ibest==null||imginfo.sizes[k].scale>imginfo.sizes[ibest].scale) ibest = k ; } // compute the corresponding shape if(ibest==null) ibest = ismall ; r = imginfo.sizes[ibest].scale / imginfo.sizes[0].scale ; for(k=0;k<2;k++) shape[k] = Math.floor(0.5+shape[k]*r) ; // the image source uri if(imginfo.dir==null) rooturi = imginfo.uri ; else rooturi = reluri(imginfo.uri,imginfo.dir) + '/' ; imguri = reluri(rooturi,imginfo.list[i].name) + imginfo.sizes[ibest].suffix + '.jpg' ; // make an srcset for(srcset='',k=0;k<imginfo.sizes.length;k++) if(imginfo.sizes[k].type==undefined) if(imginfo.sizes[k].scale>imginfo.sizes[ibest].scale) { r = imginfo.sizes[k].scale / imginfo.sizes[ibest].scale ; srcset += reluri(rooturi,imginfo.list[i].name) + imginfo.sizes[k].suffix + '.jpg ' + r.toFixed(1) + "x, " ; } // start building the table t = document.createElement('table') ; t.setAttribute('cellspacing',0) ; t.setAttribute('cellpadding',0) ; t.setAttribute('width','100%') ; t.setAttribute('height','100%') ; t.setAttribute('bgcolor','black') ; t.appendChild(blankrow((h-(shape[1]+spare))/2)) ; // title tr = document.createElement('tr') ; r = blankcol((w-shape[0])/2,imginfo.list[i].caption==undefined?2:3) ; tr.appendChild(r) ; td = document.createElement('td') ; td.setAttribute('colspan',2) ; span = document.createElement('span') ; span.setAttribute('style','text-align:left ; font-family:helvetica ; '+ 'margin:0 0 2px; font-size:18px ; color:silver; ' + 'line-height:18px') ; span.appendChild(document.createTextNode(imginfo.list[i].title)) ; td.appendChild(span) ; tr.appendChild(td) ; t.appendChild(tr) ; // the image tr = document.createElement('tr') ; td = document.createElement('td') ; img = document.createElement('img') ; img.setAttribute('src',imguri) ; if(srcset!='') img.setAttribute('srcset',srcset.substring(0,srcset.length-2)) ; img.setAttribute('width',shape[0]) ; img.setAttribute('height',shape[1]) ; td.appendChild(img) ; tr.appendChild(td) ; tr.appendChild(blankcol((w-shape[0])/2,1)) ; t.appendChild(tr) ; // the caption if(imginfo.list[i].caption!=undefined) { tr = document.createElement('tr') ; td = document.createElement('td') ; td.setAttribute('colspan',2) ; span = document.createElement('span') ; span.setAttribute('style','text-align:left ; font-family:helvetica ; '+ 'margin:2px 0 0; font-size:14px ; color:silver; ' + 'line-height:14px') ; span.appendChild(document.createTextNode(imginfo.list[i].caption)) ; td.appendChild(span) ; tr.appendChild(td) ; t.appendChild(tr) ; } t.appendChild(blankrow((h-(shape[1]+spare))/2)) ; return t ; } function blankcol(wid,nrow) { var td = document.createElement('td') ; td.setAttribute('width',wid) ; if(nrow>1) td.setAttribute('rowspan',nrow) ; td.appendChild(document.createTextNode('\u00a0')) ; return td ; } function blankrow(ht) { var td = document.createElement('td') , tr = document.createElement('tr') ; td.setAttribute('colspan',3) ; td.setAttribute('height',ht) ; td.appendChild(document.createTextNode('\u00a0')) ; tr.appendChild(td) ; return tr ; } /* --------------------------------- photo info ----------------------------- */ function phinfo(i) { infowindow.close() ; var s0=selected[0],s1=selected[1],s,shape=[0,0],ind,hind,r,k ; for(k=0;k<2;k++) shape[k] = imginfo.list[i].shape[k] ; s = finalbox + 'Name: ' + imginfo.list[i].name + '<br>Title: ' + imginfo.list[i].title ; for(hind=null,ind=0;ind<i;ind++) if(imginfo.list[ind].name==null) hind = ind ; if(hind!=null) s += "<br>Under \u201c" + imginfo.list[hind].title + "\u201d" ; // how many sizes? for(r=ind=0;ind<imginfo.sizes.length;ind++) if(imginfo.sizes[ind].type==undefined) r += 1 ; s += '<br>Available in ' + r + ' size' ; // print the sizes for(ind=0;ind<imginfo.sizes.length;ind++) if(imginfo.sizes[ind].type==undefined) { if(ind==0&&imginfo.sizes.length==1) s += ': ' ; else if(ind==0) s += 's: ' ; else if(ind==imginfo.sizes.length-1) s += ' and ' ; else s += ', ' ; r = imginfo.sizes[ind].scale/imginfo.sizes[0].scale ; s += Math.floor(0.5+shape[0]*r) + 'x' + Math.floor(0.5+shape[1]*r) ; } if(imginfo.pixpage!=null) s += '<br><a style="cursor:pointer;color:#0000bd;text-decoration:none" '+ 'href="'+reluri(imginfo.uri,imginfo.pixpage)+'" target="_blank">'+ 'Full photo set</a>'+neutral+' (opens in new tab/window)</span>' ; if(imginfo.list[i].retid!=null) { for(hind=null,ind=0;ind<=i;ind++) if(imginfo.list[ind].retpage!=null) hind = imginfo.list[ind].retpage + '.html#' ; if(hind!=null) s += '<br><a style="cursor:pointer;color:#0000bd;'+ 'text-decoration:none" href="'+reluri(imginfo.uri,hind)+ imginfo.list[i].retid+'" target="_blank">'+ 'Route notes</a>'+neutral+' (opens in new tab/window)</span>' ; } infowindow.open(s,segments[s0].data[s1].pos,'phinfo') ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ------------------------- snip: apply scissors -------------------------- */ function snipwork(s0,s1) { var i,k,newlen ; undraw(s0) ; segments.length += 1 ; for(i=segments.length-1;i>s0+1;i--) segments[i] = segments[i-1] ; newlen = segments[s0].data.length - s1 ; segments[s0+1] = new genseg(segments[s0].data.slice(s1)) ; segments[s0+1].dots = segments[s0].dots ; segments[s0+1].dothandler = segments[s0].dothandler ; segments[s0].dots = segments[s0].dothandler = null ; segments[s0].data.length = s1 + 1 ; segments[s0].data[s1] = new datatype(segments[s0].data[s1].pos,segments[s0].data[s1].h) ; draw(s0) ; draw(s0+1) ; for(i=s0+2;i<segments.length;i++) recolour(i) ; selected = [s0+1,0] ; drawsel(1) ; greyout(dlbtn) ; } function snip() { var i,s0=selected[0],s1=selected[1] ; infowindow.close() ; done(['snip',s0,s1]) ; snipwork(s0,s1) ; } /* ------------------------ discard: bin a segment ------------------------- */ function binwork(s0) { var i ; obliterate(s0) ; for(i=s0;i<segments.length-1;i++) segments[i] = segments[i+1] ; segments.length -= 1 ; for(i=s0;i<segments.length;i++) recolour(i) ; connect(s0-1) ; selected[1] = 0 ; if(selected[0]==segments.length) selected[0] = 0 ; drawsel(1) ; if(segments.length==1) blackout(dlbtn) ; } function discard() { var i,s0=selected[0] ; infowindow.close() ; done(['bin',s0,segments[s0]]) ; binwork(s0) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------- actionname ------------------------------ */ function actionname(x) { if(x[0]=='bin') return 'delete segment' ; if(x[0]=='snip') return 'split segment' ; if(x[0]=='editlabel') { if(x[4]=='') return 'delete label' ; else if(x[3]=='') return 'label waypoint' ; else return 'edit label' ; } if(x[0]=='edittitle') return 'edit title' ; if(x[0]=='wpdel') return 'delete waypoint' ; if(x[0]=='move') { if(x[5]) return 'insert waypoint' ; else return 'drag waypoint' ; } if(x[0]=='recal') return 'recalibrate altitudes' ; if(x[0]=='setalt') return 'set waypoint altitude' ; if(x[0]=='resign') return 'change label symbol' ; if(x[0]=='combine') return 'combine '+x[1]+' segments' ; if(x[0]=='revseg') return 'reverse segment' ; if(x[0]=='interpolate') return 'interpolate missing altitudes' ; if(x[0]=='optimise') return 'optimisation' ; if(x[0]=='deltimes') return 'delete times' ; if(x[0]=='editphoto') { if(x[5]==null) return 'delete photo' ; else if(x[4]==null) return 'add photo' ; else return 'change photo' ; } if(x[0]=='extra') return 'interpolate extra points' ; } function actiontype(x) { if( x=='snip'||x=='combine'||x=='interpolate' || x=='optimise'||x=='load' ) return 0 ; else return 1 ; } /* -------------------------------------------------------------------------- */ function done(something) { if( nactions>0 && unsavedchanges.length>0 && something[0]=='editlabel' && actions[nactions-1][0]==something[0] && actions[nactions-1][1]==something[1] // don't merge change with delete && actions[nactions-1][2]==something[2] && something[6]!=null ) { actions[nactions-1][4] = something[4] ; // caption actions[nactions-1][6] = something[6] ; // type } else { actions[nactions++] = something ; donesomething() ; } } function donesomething() { actions.length = nactions ; blackout(undobtn) ; greyout(redobtn) ; if(actiontype(actions[nactions-1][0])!=0) { if(unsavedchanges.length>=3) unsavedchanges.push(null) ; else unsavedchanges.push(actionname(actions[nactions-1])) ; } } /* -------------------------------------------------------------------------- */
/*page*/ /* --------------------------------- undo ---------------------------------- */ function undo() { infowindow.close() ; var opts = active + '"confirmedundo()">Undo ' + actionname(actions[nactions-1])+'</span>' ; infowindow.open(opts,getbtnpos(5),'undo') ; } function confirmedundo() { var i,ano=nactions-1,action=actions[ano][0],s0=actions[ano][1],s1,caption ; var oldcaption,task,ind ; infowindow.close() ; if(action!='revseg'&&action!='interpolate'&&action!='deltimes') s1 = actions[ano][2] ; if(action=='bin') { disconnect(s0-1) ; for(i=segments.length;i>s0;i--) { segments[i] = segments[i-1] ; recolour(i) ; } segments[s0] = s1 ; for(s1=0;s1<segments[s0].data.length;s1++) segments[s0].data[s1].setmap(map) ; draw(s0) ; connect(s0-1) ; connect(s0) ; if(selected[0]>=s0) selected[0] += 1 ; drawsel(1) ; greyout(dlbtn) ; } else if(action=='snip') // undo snip { selected = [ s0 , segments[s0].data.length-1 ] ; combine1(s0,s0+1) ; for(i=s0+1;i<segments.length-1;i++) { segments[i] = segments[i+1] ; recolour(i) ; } segments.length -= 1 ; if(segments.length==1) blackout(dlbtn) ; draw(s0) ; drawsel(1) ; } else if(action=='editlabel') // undo create/edit/delete label segments[s0].data[s1].setlabel(actions[ano][5],actions[ano][3]) ; else if(action=='edittitle') settitle(s0) ; else if(action=='wpdel') // ['wpdel',s0,s1,wpdelwork(s0,s1)] { insert(s0,s1,1) ; segments[s0].data[s1] = actions[ano][3] ; segments[s0].data[s1].setmap(map) ; selected = [s0,s1] ; redrawconnect(s0,s1) ; drawsel(1) ; } else if(action=='move') { if(actions[ano][5]) wpdelwork(s0,s1) ; else move(s0,s1,actions[ano][3]) ; } else if(action=='recal') calwork(s0,-s1) ; else if(action=='setalt') segments[s0].data[s1].h = actions[ano][3] ; else if(action=='combine') uncombine(actions[ano]) ; else if(action=='revseg') revsegwork(s0) ; else if(action=='interpolate') for(i=0;i<s0.length;i++) segments[0].data[s0[i]].h = null ; else if(action=='deltimes') for(i=0;i<s0.length;i++) segments[s0[i][0]].data[s0[i][1]].t = s0[i][2] ; else if(action=='optimise') // [ 'load' , s0 , data.slice() , loadno , props ] { for(ano--;ano>=0&&actions[ano][0]!='load';ano--) ; segments[s0].data = actions[ano][2] ; actions[loadno][4].optim.ndel = 0 ; selected = [s0,0] ; redraw(s0) ; drawsel(1) ; } else if(action=='editphoto') { ind = actions[ano][3] ; if(actions[ano][5]==null) // undo delete for(i=segments[s0].data[s1].photo.length;i>ind;i--) segments[s0].data[s1].photo[i] = segments[s0].data[s1].photo[i-1] ; if(ind>=segments[s0].data[s1].photo.length) segments[s0].data[s1].addphoto(actions[ano][4]) ; else segments[s0].data[s1].setphoto(ind,actions[ano][4]) ; } else if(action=='extra') for(selected=[s0,s1],i=actions[ano].length-1;i>=3;i--) { task = actions[ano][i] segments[task[0]].data.splice(task[1],task[2].length-2) ; } nactions -= 1 ; if(nactions<=1) greyout(undobtn) ; blackout(redobtn) ; if(actiontype(actions[nactions][0])!=0&&unsavedchanges.length>0) unsavedchanges.length -= 1 ; ; if(action=='optimise'||action=='dltimes') routeinfo() ; else if(action=='editphoto'||action=='editlabel') walkto(s0,s1,0) ; } /* --------------------------------- move ----------------------------------- */ function move(s0,s1,pos) { segments[s0].data[s1].setpos(pos) ; redrawconnect(s0,s1) ; drawsel(1) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* --------------------------------- redo ---------------------------------- */ function redo() { infowindow.close() ; var opts = active + '"confirmedredo()">Redo ' + actionname(actions[nactions])+'</span>' ; infowindow.open(opts,getbtnpos(6),'redo') ; } function confirmedredo() { var i,action=actions[nactions][0],s0=actions[nactions][1],s1,caption,a,b,c ; var task,ind,photo ; if(action!='bin'&&action!='revseg'&&action!='interpolate'&&action!='deltimes') s1 = actions[nactions][2] ; infowindow.close() ; if(action=='bin') binwork(s0) ; else if(action=='snip') snipwork(s0,s1) ; else if(action=='editlabel') // redo create/edit/delete label segments[s0].data[s1].setlabel(actions[nactions][6],actions[nactions][4]) ; else if(action=='edittitle') settitle(s1) ; else if(action=='wpdel') wpdelwork(s0,s1) ; else if(action=='move') // ['move',s0,s1,oldpos,newpos,inserted] { if(actions[nactions][5]) insert(s0,s1,1) ; move(s0,s1,actions[nactions][4]) ; } else if(action=='recal') calwork(s0,s1) ; else if(action=='setalt') segments[s0].data[s1].h = actions[nactions][4] ; else if(action=='combine') recombine(actions[nactions]) ; else if(action=='revseg') revsegwork(s0) ; else if(action=='interpolate') interpolatework() ; else if(action=='deltimes') for(i=0;i<s0.length;i++) segments[s0[i][0]].data[s0[i][1]].t = null ; else if(action=='optimise') { result = optimise(segments[s0],actions[nactions][2]) ; actions[loadno][4].optim.ndel = segments[s0].data.length - result.length ; segments[s0].data = result ; selected = [s0,0] ; redraw(s0) ; drawsel(1) ; routeinfo() ; } else if(action=='editphoto') { ind = actions[nactions][3] ; photo = actions[nactions][5] ; if(actions[nactions][4]==null) segments[s0].data[s1].addphoto(photo) ; else segments[s0].data[s1].setphoto(ind,photo) ; } else if(action=='extra') for(selected=[s0,s1],i=3;i<actions[nactions].length;i++) { task = actions[nactions][i] ; a = segments[task[0]].data.slice(0,task[1]) ; b = task[2].slice(1,task[2].length-1) ; c = segments[task[0]].data.slice(task[1]) ; segments[task[0]].data = a.concat(b,c) ; } nactions += 1 ; if(nactions==actions.length) greyout(redobtn) ; blackout(undobtn) ; if(actiontype(actions[nactions-1][0])!=0) unsavedchanges.push(action) ; if(action=='editphoto'||action=='editlabel') walkto(s0,s1,0) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ---------------------------------- dl ----------------------------------- */ function dl() { var i,j,k,noalt,xmldoc,course,lap,datum,filename,blob,track,time,routelen ; var trackpoint,coursepoint,str,flag,clen=segments[0].data.length,tlast,di,dk ; var origlen,ndel,ano,photo,npix,thisuri,maxsep,sep,tdist,ttime,time,otime ; var distance = new Array(clen) , msecs = new Array(clen) ; var valid = new Array(clen) ; for(tlast=null,maxsep=noalt=tdist=ttime=i=flag=0;i<clen;otime=time,i++) { if(segments[0].data[i].h==null) noalt += 1 ; time = segments[0].data[i].t ; if(time!=null) time = time.getTime() ; if(tlast!=null&&time!=null&&time<tlast) flag = 1 ; // out of order if(time!=null) tlast = time ; if(i) { sep = dist(segments[0].data[i-1].pos,segments[0].data[i].pos) ; distance[i] = distance[i-1] + sep ; if(sep>maxsep) maxsep = sep ; if(time!=null&&otime!=null) { tdist += sep ; ttime += time - otime ; } } else distance[i] = 0 ; } routelen = distance[clen-1] ; if(maxsep>100&&!confirm('Some gaps between waypoints are >100m.\n'+ 'This will cause problems if used for navigation in a Garmin 500.\n'+ 'You can hit [OK] and I will proceed anyway, or\n'+ 'you can hit [Cancel] to interpolate extra points\n'+ '(recommended - go to Route Info under the cogwheel).')) return ; if(noalt&&!confirm(noalt+' waypoints have no associated altitudes.\n'+ 'You can hit [OK] and I will interpolate altitudes (not guaranteed), or\n'+ 'you can hit [Cancel] and try again later when the altitudes may be '+ 'available.')) return ; if(routetitle=='Untitled Route') filename = '' ; else { i = routetitle.indexOf(' ') ; if(i<=0) filename = routetitle ; else filename = routetitle.substring(0,i) ; } filename = prompt("Enter filename: ",filename) ; if(filename==null) return ; else if(routetitle=='Untitled Route') settitle(filename) ; i = filename.length ; if(i==0) { filename = 'route' ; i = 5 ; } if(filename.substring(i-4)!='.tcx') filename += '.tcx' ; if(noalt) interpolate() ; unsavedchanges = [] ; // interpolate/extrapolate times in msec for(i=0;i<clen;i++) { valid[i] = 1 ; if(segments[0].data[i].t==null) msecs[i] = null ; else msecs[i] = segments[0].data[i].t.getTime() ; } if(tdist==0||flag!=0) for(i=0;i<clen;i++) msecs[i] = distance[i] * 333 ; else for(i=0;i<clen;i=k) { for(;i<clen&&segments[0].data[i].t!=null;i++) ; // advance to null if(i==clen) break ; for(k=i+1;k<clen&&segments[0].data[k].t==null;k++) ; // advance to non-null for(j=i;j<k;j++) valid[i] = 0 ; if(i==0) for(time=msecs[k],j=i;j<k;j++) msecs[j] = time - (distance[k]-distance[j])*ttime/tdist ; else if(k==clen) for(time=msecs[i-1],j=i;j<clen;j++) msecs[j] = time + (distance[j]-distance[i-1])*ttime/tdist ; else for(j=i,di=distance[i-1],dk=distance[k];j<k;j++) msecs[j] = ( msecs[i-1]*(dk-distance[j]) + msecs[k]*(distance[j]-di) ) / (dk-di) ; } str = '<?xml version="1.0" encoding="UTF-8" standalone="no" ?>\n' + '<TrainingCenterDatabase xmlns="http://www.garmin.com/xmlschemas/' + 'TrainingCenterDatabase/v2"\n' + ' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"\n' + ' xsi:schemaLocation="http://www.garmin.com/' + 'xmlschemas/TrainingCenterDatabase/v2 ' + 'http://www.garmin.com/xmlschemas/TrainingCenterDatabasev2.xsd">\n' ; str += ' <Folders><Courses><CourseFolder Name="Courses">\n' ; str += ' <CourseNameRef><Id>'+routetitle+'</Id></CourseNameRef>\n' ; str += ' </CourseFolder></Courses></Folders>\n<Courses><Course>\n' ; str += ' <Name>'+routetitle+'</Name>\n <Lap>\n' + adddist(routelen) ; time = (msecs[clen-1]-msecs[0]) / 1000 ; str += ' <TotalTimeSeconds>' + time.toFixed(0) + '</TotalTimeSeconds>\n' ; str += addpos('Begin',segments[0].data[0].pos) ; str += addpos('End',segments[0].data[clen-1].pos) ; str += ' <Intensity>Active</Intensity>\n' + ' </Lap>\n <Track>\n' ; // loop over trackpoints for(npix=i=0;i<segments[0].data.length;i++) { str += ' <Trackpoint>\n' + addpos('',segments[0].data[i].pos) ; str += adddist(distance[i]) + addalt(segments[0].data[i].h) ; time = new Date(msecs[i]) ; str += ' <Time>' + time.toISOString() + '</Time>\n' ; if(segments[0].data[i].photo.length>0) { str += ' <Extensions><Photo>' ; for(k=0;k<segments[0].data[i].photo.length;k++) { if(k) str += ' ' ; str += segments[0].data[i].photo[k] ; } str += '</Photo></Extensions>\n' ; npix += segments[0].data[i].photo.length ; } if(valid[i]==0) str += ' <Extensions><ValidTime>False</ValidTime></Extensions>\n' ; str += ' <SensorState>Absent</SensorState>\n </Trackpoint>\n' ; } str += ' </Track>\n\n' ; // record optimisation and photo list for(ano=-1,ndel=origlen=0,i=loadno;i>=0;i=actions[i][3]) { ndel += actions[i][4].optim.ndel ; origlen += actions[i][4].optim.origlen ; if(actions[i][4].optim.parms!=null&&ano==-1) ano = i ; } if(ndel||(npix>0&&imginfo.status=='ready')) str += ' <Extensions>\n' ; if(ndel) { str += ' <Optimised from="'+origlen+'" to="'+(origlen-ndel) ; if(ano>=0) str += '" tol="'+actions[ano][4].optim.parms.tol.toFixed(0)+ '" maxsep="'+actions[ano][4].optim.parms.maxsep.toFixed(0)+ '" wppenalty="'+actions[ano][4].optim.parms.wppenalty.toFixed(0)+ '" vweight="'+actions[ano][4].optim.parms.vweight.toFixed(1) ; str += '"/>\n' } if(npix>0&&imginfo.status=='ready') { thisuri = imginfo.uri ; if(imginfo.type!='tcx') // vice 'uri' { thisuri = document.URL ; if((i=thisuri.lastIndexOf('?'))>=0) thisuri = thisuri.substring(0,i) ; thisuri = reluri(thisuri,imginfo.uri) ; } str += ' <PhotoList src="'+thisuri+'"/>\n' ; } if(ndel||(npix>0&&imginfo.status=='ready')) str += ' </Extensions>\n\n' ; // finally loop over coursepoints for(i=0;i<segments[0].data.length;i++) { datum = segments[0].data[i] ; if(datum.type==null) continue ; str += ' <CoursePoint><Name>'+datum.marker.title+'</Name>\n' ; str += ' <PointType>'+datum.type+'</PointType>\n' ; str += addpos('',segments[0].data[i].pos) + addalt(segments[0].data[i].h) ; time = new Date(msecs[i]) ; str += ' <Time>' + time.toISOString() + '</Time>\n </CoursePoint>\n' ; } str += '</Course></Courses></TrainingCenterDatabase>\n' ; blob = new Blob([str],{type: "text/plain;charset=utf-8"}) ; saveAs(blob,filename) ; } /* -------------------------------------------------------------------------- */ function addpos(tag,pos) { var str = ' <'+tag+'Position>\n <LatitudeDegrees>' ; str += pos.lat().toFixed(5)+'</LatitudeDegrees>\n <LongitudeDegrees>' ; str += pos.lng().toFixed(5)+'</LongitudeDegrees>\n </'+tag+'Position>\n' ; return str ; } function addalt(x) { return ' <AltitudeMeters>' + x.toFixed(0) + '</AltitudeMeters>\n' ; } function adddist(x) { return ' <DistanceMeters>' + x.toFixed(0) + '</DistanceMeters>\n' ; } /* ------------------------------ interpolate ------------------------------- */ function interpolate() { infowindow.close() ; var response = interpolatework() ; if(response.length>0) done([ 'interpolate' , response ]) ; } function interpolatework() { var i,j,k,response=[],x,y,distance,sum ; var a=segments[0].data,len=a.length ; for(i=0;i<len;i=j) { for(;i<len&&a[i].h!=null;i++) ; // advance to null for(j=i+1;j<len&&a[j].h==null;j++) response.push(j) ; // advance to non-null if(i==0) { for(y=a[j].h;i<j;i++) a[i].h = y ; continue ; } if(j==len) { for(x=a[i-1].h;i<j;i++) a[i].h = x ; continue ; } distance = new Array(1+j-i) ; for(sum=k=0;k<=j-i;k++) sum = distance[k] = sum + dist(a[i+k-1].pos,a[i+k].pos) ; for(x=a[i-1].h,y=a[j].h,k=0;k<j-i;k++) a[i+k].h = ( x*(sum-distance[i]) + y*distance[i] ) / sum ; } } /* -------------------------------------------------------------------------- */

Archived from utils.html

#include "memory.h" #include <math.h> double cholsqrt(double **,double **,int),logcholsqrt(double **,double **,int) ; void cholinv(double **,double **,int) ; void cholsqr(double **,double **,int) ; /* ------------------------------------------------------------------- cholesky inverts a positive-definite symmetric matrix. */ double cholesky(double **a,double **b,int n) { double qdet = cholsqrt(a,b,n) ; cholinv(b,b,n) ; cholsqr(b,b,n) ; return qdet ; } double logcholesky(double **a,double **b,int n) { double logqdet = logcholsqrt(a,b,n) ; cholinv(b,b,n) ; cholsqr(b,b,n) ; return logqdet ; } /* ------------------------------------------------------------------- cholsqrt looks at a[i][j] only for i<=j, and constructs b with b[i][j]=0 for i>j. it may be run in place if you wish, ie. with a=b. */ double cholsqrt(double **a,double **b,int n) { int i,j,k ; double q,qdet=1 ; for(j=0;j<n;j++) { q = a[j][j] ; for(i=0;i<j;i++) q -= b[i][j] * b[i][j] ; if(q<=0) return 0.0 ; qdet *= q ; b[j][j] = sqrt(q) ; for(k=j+1;k<n;k++) { q = 0 ; for(i=0;i<j;i++) q += b[i][j] * b[i][k] ; b[j][k] = ( a[j][k] - q ) / b[j][j] ; } } for(j=0;j<n;j++) for(k=0;k<j;k++) b[j][k] = 0 ; return qdet ; } double logcholsqrt(double **a,double **b,int n) { int i,j,k ; double q,logqdet=0 ; for(j=0;j<n;j++) { q = a[j][j] ; for(i=0;i<j;i++) q -= b[i][j] * b[i][j] ; insist(q>0,orscream("cholesky doesn\'t think your input matrix is pd")) ; logqdet += log(q) ; b[j][j] = sqrt(q) ; for(k=j+1;k<n;k++) { q = 0 ; for(i=0;i<j;i++) q += b[i][j] * b[i][k] ; b[j][k] = ( a[j][k] - q ) / b[j][j] ; } } for(j=0;j<n;j++) for(k=0;k<j;k++) b[j][k] = 0 ; return logqdet ; } /* ------------------------------------------------------------------- cholinv looks at a[i][j] only for i<=j, and constructs b with b[i][j] zero for i<j. it may be run in place if you wish, ie. with a=b. */ void cholinv(double **a,double **b,int n) { int i,j,k ; double q ; for(j=0;j<n;j++) { b[j][j] = 1 / a[j][j] ; for(i=j-1;i>=0;i--) { q = 0 ; for(k=i;k<j;k++) q += b[k][i] * a[k][j] ; b[j][i] = - q * b[j][j] ; } } for(j=0;j<n;j++) for(k=0;k<j;k++) b[k][j] = 0 ; } /* ------------------------------------------------------------------- cholsqr looks at a[i][j] only for i>=j, and constructs the complete matrix b. it may be run in place if you wish, ie. with a=b. */ void cholsqr(double **a,double **b,int n) { int i,j,k ; double q ; for(i=0;i<n;i++) for(j=i;j<n;j++) { q = 0 ; for(k=j;k<n;k++) q += a[k][i] * a[k][j] ; b[i][j] = q ; } for(j=0;j<n;j++) for(k=0;k<j;k++) b[j][k] = b[k][j] ; }

Archived from sloping.html

double slopingbw(int m,int n,double **S,double **ybar,double **ybardash, int *ni,double *mu,double **zb,double **zw, double *muhat,double **zbhat,double **zwhat,int niter) ; struct slopingstats { int m,N,len,*n ; double **S,**ybar,**ybardash ; slopingstats() { N = len = m = 0 ; n = 0 ; S = ybar = ybardash = 0 ; } slopingstats(int p) { this[0] = slopingstats() ; m = p ; S = matrix(m+1,m+1) ; } void take(double **y,int ni) { if(ni<=0) return ; double q=1.0/ni ; int t,j,k ; if(len==N) { len += 20 + len ; n = ivector(n,len) ; ybar = matrix(ybar,len,m) ; ybardash = matrix(ybardash,len,m) ; for(t=N;t<len;t++) { n[t] = 0 ; for(j=0;j<m;j++) ybar[t][j] = ybardash[t][j] = 0 ; } } for(t=0;t<ni;t++) for(j=0;j<m;j++) ybar[N][j] += q * y[t][j] ; if(ni>1) for(q=2/(ni*(ni-1.0)),t=0;t<ni;t++) for(j=0;j<m;j++) ybardash[N][j] += q * t * y[t][j] ; for(j=0;j<m;j++) for(k=0;k<m;k++) for(t=0;t<ni;t++) S[j][k] += (y[t][j]-ybar[N][j]) * (y[t][k]-ybar[N][k]) ; n[N++] = ni ; } void release() { free(n) ; freematrix(S,ybar,ybardash) ; this[0] = slopingstats() ; } void sort() { int t,j ; double **x=matrix(N,m) ; ij *list = ijvector(n,N) ; ijsort(list,N) ; for(t=0;t<N;t++) for(j=0;j<m;j++) x[t][j] = ybar[list[t].j][j] ; for(t=0;t<N;t++) for(j=0;j<m;j++) ybar[t][j] = ybardash[list[t].j][j] ; for(t=0;t<N;t++) n[t] = list[list[t].j].i ; freematrix(ybardash) ; ybardash = ybar ; ybar = x ; free(list) ; } } ; double slopingbw(slopingstats s,double *mu,double **zb,double **zw, double *muhat,double **zbhat,double **zwhat,int niter) { return slopingbw(s.m,s.N,s.S,s.ybar,s.ybardash,s.n,mu,zb,zw, muhat,zbhat,zwhat,niter) ; }

Archived from routemaster.html

var segments=[],selected,actions,nactions,sel,dragging,loadno,shift=0 ; var pending,xpending,mouseopt=0,curhandle,elevator,resuri ; var routetitle,body,altdiv,curdiv,curcan,mapdiv,cmap,smap,prox,proa,pron ; var imgdiv,imghandle,imgtable,imginfo,imgind,selinit ; var cursorbtn,scissorsbtn,binbtn,undobtn,redobtn,penbtn,setbtn,dlbtn ; var defparms = {tol:10,maxsep:95,wppenalty:1000,vweight:1} ; var flagsign,turnleft,straighton,turnright,shriek,arrow,concircle,camera ; var neutral='<span style="font-family:helvetica">' ; var active='<span style="cursor:pointer;color:#0000bd" onclick=' ; var inactive='<span style="color:silver">' ; var textbox='<div style="margin-bottom:2px;border-bottom:solid 1px silver;' + 'padding-bottom:2px;font-family:helvetica">' ; var finalbox='<div style="font-family:helvetica">' ; var parser = new DOMParser() ; var map = null , clickhandle = null ; var unsavedchanges = [] ; var infowindow = { handle: null , type: null , open: function(s,pos,type) { this.handle = new google.maps.InfoWindow({content:s,position:pos}) ; this.handle.open(map) ; google.maps.event.addListener(this.handle,'closeclick', function() { infowindow.handle = infowindow.type = null ; } ) ; this.type = type ; } , close: function() { if(this.handle==null) return null ; var response = this.type ; this.handle.close() ; this.handle = this.type = null ; return response ; } } ; /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------------------------------------------------- */ /* CONSTRUCTORS */ /* -------------------------------------------------------------------------- */ function dotpath(a,b) { this.path = [a,b] ; this.cursor = 'default' ; this.geodesic = true ; this.strokeOpacity = 0 ; this.icons = [ { icon: { path: 'M 0 0 L 1 0',strokeOpacity:1,scale:1 } , offset: '1px' , repeat: '4px' } ] ; this.zIndex = 0 ; } /* -------------------------------------------------------------------------- */ function linepath(s0,start,end,colour) { var i,len=(start<0?segments[s0].data.length:end-start) ; this.path = new Array(len) ; if(start<0) for(i=0;i<len;i++) this.path[i] = segments[s0].data[i].pos ; else for(i=0;i<len;i++) this.path[i] = segments[s0].data[start+i].pos ; this.clickable = 'false' ; this.cursor = 'default' ; this.geodesic = true ; this.strokeColor = colour ; this.strokeOpacity = 1.0 ; this.strokeWeight = 2 ; this.zIndex = 0 ; } /* -------------------------------------------------------------------------- */ function listinfo() { this.list = [] ; this.sizes = [] ; this.dir = thumbsfx = null ; this.uri = this.scale = this.status = this.type = this.pixpage = null ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ------------------------------- data structure --------------------------- */ // I found the following logic quite hard to get right. A (non-null) label // satisfies the following constraints: // o. the marker is non-null // o. the map may be null, and if it is null the title may also be null and the // icon may be arbitrary // o. if the type is null, the map is null // o. the map is null if and only if the clickhandler is inactive // the same constraints apply (mutatis mutandis) to the photo, so it follows // that the label may have a null map and the photo non-null (and vice versa) // we therefore conclude that a label must be in one of 3 states: // o. type null, map null, handlers inactive, but marker non-null // o. type non-null, map null, handlers inactive, marker non-null // o. type non-null, map non-null, handlers active, marker non-null // the state in which type is non-null and map is null is applied to all // labels in a segment being deleted (we preserve the information in the // action list but don't want the label to be displayed) function datatype(pos,h) { this.pos = pos ; this.h = h ; this.marker = this.photomarker = this.type = this.t = null ; this.photo = [] ; this.caption = '' ; this.clickhandler = this.righthandler = this.photohandler = null ; } // member functions datatype.prototype.geticon = function() { if(this.type=='Left') return turnleft ; else if(this.type=='Straight') return straighton ; else if(this.type=='Right') return turnright ; else if(this.type=='Danger') return shriek ; else return flagsign ; } ; datatype.prototype.setlabelmap = function(m) { if(m==null||this.type==null) m = null ; else m = map ; if(m==null&&this.marker==null) return ; this.marker.setMap(m) ; if(m==null&&this.clickhandler!=null) { google.maps.event.removeListener(this.clickhandler) ; google.maps.event.removeListener(this.righthandler) ; this.clickhandler = this.righthandler = null ; } if(m!=null&&this.clickhandler==null) { this.clickhandler = this.marker.addListener('click',selpoint) ; this.righthandler = this.marker.addListener('rightclick',labelcycle) ; } } ; datatype.prototype.setphotomap = function(m) { if(m==null||this.photo.length==0) m = null ; else m = map ; if(m==null&&this.photomarker==null) return ; this.photomarker.setMap(m) ; if(m==null&&this.photohandler!=null) { google.maps.event.removeListener(this.photohandler) ; this.photohandler = null ; } if(m!=null&&this.photohandler==null) this.photohandler = this.photomarker.addListener('click',selpoint) ; } ; datatype.prototype.setlabel = function(t,c) { this.type = t ; if(t==null) { if(this.marker!=null) this.setlabelmap(null) ; return ; } if(this.marker==null) this.marker = new google.maps.Marker ({ position:this.pos,map:map,icon:this.geticon(),title:c,zIndex:1 }) ; else { this.marker.setIcon(this.geticon()) ; this.marker.setTitle(c) ; } this.setlabelmap(map) ; } ; datatype.prototype.setphoto = function(ind,p) { var i ; if(p==null) { for(i=ind;i<this.photo.length-1;i++) this.photo[i] = this.photo[i+1] ; this.photo.length -= 1 ; if(this.photo.length==0&&this.photomarker!=null) this.setphotomap(null) ; return ; } else { this.photo[ind] = p ; if(ind==0) this.photomarker.setTitle(p) ; } } ; datatype.prototype.addphoto = function(p) { this.photo.push(p) ; if(this.photomarker==null) this.photomarker = new google.maps.Marker ({ position:this.pos,map:map,icon:camera,title:p,zIndex:1 }) ; this.setphotomap(map) ; } ; datatype.prototype.setpos = function(p) { this.pos = p ; if(this.type!=null) this.marker.setPosition(p) ; if(this.photo.length>0) this.photomarker.setPosition(p) ; } ; datatype.prototype.setmap = function(m) { this.setlabelmap(m) ; this.setphotomap(m) ; } ; datatype.prototype.settype = function(t) { this.type = t ; this.marker.setIcon(this.geticon()) ; } ; /* -------------------------------------------------------------------------- */ /* UTILITY FUNCTIONS */ /* -------------------------------------------------------------------------- */ function xmlfloat(x) { return parseFloat(x.childNodes[0].textContent) ; } function dist(x,y) { return google.maps.geometry.spherical.computeDistanceBetween(x,y) ; } function interp(x,y,lamda) { return google.maps.geometry.spherical.interpolate(x,y,lamda) ; } function bearing(x,y) { return google.maps.geometry.spherical.computeHeading(x,y) ; } function angle(x,y) { return google.maps.geometry.spherical.computeHeading(x,y)*Math.PI/180 ; } function isvaliddate(d) { if(Object.prototype.toString.call(d)!=="[object Date]") return false ; else return !isNaN(d.getTime()) ; } /* --------------------------- button handlers ----------------------------- */ function greyout(btn) { if(btn.active==0) return 0 ; btn.btn.setAttribute('src',btn.greyimg) ; btn.ui.removeEventListener('click',btn.handler) ; if(btn==penbtn) btn.ui.removeEventListener('contextmenu',photoprompt) ; btn.ui.style.cursor = 'default' ; btn.active = 0 ; return 1 ; } function blackout(btn) { if(btn.active) return ; btn.btn.setAttribute('src',btn.blackimg) ; btn.ui.addEventListener('click',btn.handler) ; if(btn==penbtn) btn.ui.addEventListener('contextmenu',photoprompt) ; btn.ui.style.cursor = 'pointer' ; btn.active = 1 ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ------------------------ enter/exit full screen -------------------------- */ // Find the right method, call on correct element function enterFullscreen() { infowindow.close() ; if(document.documentElement.requestFullscreen) document.documentElement.requestFullscreen() ; else if(document.documentElement.mozRequestFullScreen) document.documentElement.mozRequestFullScreen() ; else if(document.documentElement.webkitRequestFullscreen) document.documentElement.webkitRequestFullscreen() ; else if(document.documentElement.msRequestFullscreen) document.documentElement.msRequestFullscreen() ; } function exitFullscreen() { infowindow.close() ; if(document.exitFullscreen) document.exitFullscreen() ; else if(document.mozCancelFullScreen) document.mozCancelFullScreen() ; else if(document.webkitExitFullscreen) document.webkitExitFullscreen() ; } /* ---------------- find the index of a given photo name -------------------- */ function findimg(id) { var i ; for(i=0;i<imginfo.list.length;i++) if(imginfo.list[i].name!=undefined&&imginfo.list[i].name==id) return i ; return -1 ; } /* ------------------- message warning of unsaved changes ------------------- */ function unsavedmsg(ok) { var msg , len = unsavedchanges.length , i ; if(len==0) return null ; msg = 'You have ' + len + ' unsaved change' + (len==1?"":'s') ; if(len<=3) for(i=0;i<len;i++) msg += (i?',':' (') + unsavedchanges[i] + (i==len-1?')':'') ; msg += '\nIf you hit ' + (ok?'[OK]':'[Leave page]') ; return msg + (len==1?' this change':' these changes') + ' will be lost.' ; } /* --------------- selpoint: choose the clicked waypoint ------------------- */ function selpoint(event) { var i,j,closest,d,mindist,s0=selected[0],s1=segments[s0].data.length ; if(dragging) return ; var flag = (infowindow.close()=='wpinfo') && (shift==0) ; if(shift) { insert(s0,s1,1) ; segments[s0].data[s1].setpos(event.latLng) ; lookupalt(s0,s1) ; redrawconnect(s0,s1) ; done(['move',s0,s1,event.latLng,event.latLng,1]) ; } else for(s1=s0=-1,i=0;i<segments.length;i++) for(j=0;j<segments[i].data.length;j++) { d = dist(segments[i].data[j].pos,event.latLng) ; if(s0<0||d<mindist) { s0 = i ; s1 = j ; mindist = d ; } } walkto(s0,s1,flag) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* --------------------------------- walkto --------------------------------- */ // draw a selection point (and possibly an info box) at [s0,s1], bringing up // a wpinfo window if flag != 0 function walkto(s0,s1,flag) { var s='',i,ind,imguri=null,excuse,imgname ; var datum = segments[s0].data[s1] , pos = datum.pos ; selected = [s0,s1] ; map.panToBounds(new google.maps.LatLngBounds(pos,pos)) ; drawsel(0) ; if(flag||(datum.type==null&&datum.photo.length==0)) { if(flag) wpinfo() ; return ; } if(datum.type!=null) { if(datum.photo.length>0) s = textbox ; else s = finalbox ; if(datum.type!='Generic') s += datum.type + ': ' ; s += datum.marker.title + ' [' + active + '"labelprompt()">Edit</span>]' ; s += '</div>' ; } for(ind=0;ind<datum.photo.length;ind++) { s += textbox ; if(imginfo.status=='ready'&&(i=findimg(datum.photo[ind]))>=0) { if(imginfo.dir==null) imguri = imginfo.uri ; else imguri = reluri(imginfo.uri,imginfo.dir) + '/' ; imguri = reluri(imguri,datum.photo[ind]) + imginfo.thumbsfx + '.jpg' ; s += '<img src="' + imguri + '" width=' + imginfo.list[i].thumbshape[0] + ' height=' + imginfo.list[i].thumbshape[1] + '><br>' + '<b>'+datum.photo[ind]+'</b>: ' ; } else { if(imginfo.status=='null') excuse = 'no list provided' ; else if(imginfo.status=='ready') { imgname = imginfo.uri ; i = imgname.lastIndexOf('/') ; if(i>=0) imgname = imgname.substring(i+1) ; excuse = 'not present in ' + imgname ; } else if(imginfo.status=='waiting') excuse = imgname + ' is not available' ; else excuse = 'imginfo.status = ' + imginfo.status ; s += 'Photo: ' + datum.photo[ind] + ' (' + excuse + ') ' ; } s += '['+active+ '"photoedit('+ind+')">Edit</span>'+']' ; if(imguri!=null) s += ' : ['+active+ '"phinfo('+i+')">Info</span>'+']' + ' : ['+active+ '"enlarge('+ind+','+i+')">Enlarge</span>'+']' ; s += '</div>' ; } if(datum.photo.length>0) s += finalbox + '[' + active + '"photoprompt' + '(null)">Add photo</span>' + ']</div>' ; infowindow.open(s,pos,'walking') ; } /* -------------------------- keystroke handler ---------------------------- */ function walk(e) { var s0=selected[0],s1=selected[1],slast,flag ; if(e.keyCode==16) shift = 1 ; if(e.keyCode==40) { map.panTo(segments[s0].data[s1].pos) ; return ; } if(infowindow.close()=='wpinfo') flag = 1 ; else flag = 0 ; if(e.keyCode==32) { selclick() ; return ; } // space if(e.keyCode==13) // return { if(dragging) undraggit() ; else if(s0>=0) draggit(0) ; return ; } if(dragging) return ; if(e.keyCode==8||e.keyCode==46) // delete/backspace { e.preventDefault() ; if(binbtn.active) discard() ; return ; } if(e.keyCode==9) { e.preventDefault() ; inswp(1) ; return ; } // tab if(e.keyCode==39) // forwards { e.preventDefault() ; if(s1<segments[s0].data.length-1) s1 += 1 ; else { s0 += 1 ; if(s0==segments.length) s0 = 0 ; s1 = 0 ; } } else if(e.keyCode==37) // backwards { e.preventDefault() ; if(s1>0) s1 -= 1 ; else { s0 -= 1 ; if(s0<0) s0 = segments.length-1 ; s1 = segments[s0].data.length-1 ; } } else return ; walkto(s0,s1,flag) ; } /* ---------------------------- relative uri ------------------------------- */ function reluri(u1,u2) { var last = u1.lastIndexOf('/') ; if(last<0) return u2 ; u1 = u1.substring(0,last) ; while(u2.substring(0,3)=='../') { last = u1.lastIndexOf('/') ; if(last<0) return u2 ; u2 = u2.substring(3) ; u1 = u1.substring(0,last) ; } return u1 + '/' + u2 ; } /* ------------------------------- getbtnpos -------------------------------- */ function getbtnpos(btnno) { var bounds=map.getBounds(),sw,ne,lat,lon,lam ; sw = bounds.getSouthWest() ; ne = bounds.getNorthEast() ; lam = 52.0 / window.innerHeight ; lat = lam*ne.lat() + (1-lam)*sw.lat() ; lam = 0.5 + (btnno*32-112.0)/window.innerWidth ; lon = lam*ne.lng() + (1-lam)*sw.lng() ; return new google.maps.LatLng(lat,lon) ; } /* ----- unambig: does the selected waypoint determine a unique segment? ---- */ function unambig() // does the selected waypoint determine a unique segment? { var s0=selected[0],s1=selected[1] ; if(segments.length==1) return 1 ; if( ( s0==segments.length-1 || s1!=segments[s0].data.length-1 || ! segments[s0].data[s1].pos.equals(segments[s0+1].data[0].pos) ) && ( s0==0 || s1!=0 || ! segments[s0].data[s1].pos.equals (segments[s0-1].data[segments[s0-1].data.length-1].pos) ) ) return 1 ; else return 0 ; } /* --------------------- undraw & redraw segments -------------------------- */ function undraw(i) { segments[i].route.setMap(null) ; if(segments[i].clickhandler!=null) { google.maps.event.removeListener(segments[i].clickhandler) ; segments[i].clickhandler = null ; } } function redraw(i) { undraw(i) ; draw(i) ; } function recolour(i) { if(i&1) segments[i].route.setOptions({strokeColor:"#ff9999"}) ; else segments[i].route.setOptions({strokeColor:"#ff0000"}) ; } function obliterate(s0) // undraw route and all labels { var i ; for(i=0;i<segments[s0].data.length;i++) segments[s0].data[i].setmap(null) ; undraw(s0) ; disconnect(s0-1) ; disconnect(s0) ; } /* ----------------------------- draw segments ------------------------------ */ function draw(i) { var colour ; if(i&1) colour = "#ff9999" ; else colour = "#ff0000" ; segments[i].route = new google.maps.Polyline(new linepath(i,-1,0,colour)) ; segments[i].route.setMap(map) ; segments[i].clickhandler = google.maps.event.addListener(segments[i].route,"click",selpoint) ; } /* ----------------------- connect and disconnect segments ------------------ */ function disconnect(i) { if(i<0||i>=segments.length-1||segments[i].dots==null) return ; segments[i].dots.setMap(null) ; if(segments[i].dothandler!=null) { google.maps.event.removeListener(segments[i].dothandler) ; segments[i].dothandler = null ; } } function reconnect(i) { disconnect(i) ; connect(i) ; } function connect(i) { if(i<0||i>=segments.length-1) return ; var opos = segments[i].data[segments[i].data.length-1].pos ; var npos = segments[i+1].data[0].pos ; if(opos.equals(npos)) return ; segments[i].dots = new google.maps.Polyline(new dotpath(opos,npos)) ; segments[i].dots.setMap(map) ; segments[i].dothandler = google.maps.event.addListener(segments[i].dots,"click",selpoint) ; } function redrawconnect(s0,s1) { redraw(s0) ; if(s1==0) reconnect(s0-1) ; if(s1=segments[s0].data.length-1) reconnect(s0) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ---------------------- draw the selection point -------------------------- */ // note: there's no point in allowing clicking on a marker because the // event position is always the marker position rather than the click position function drawsel(opt) { var ind,clen,s0=selected[0],s1=selected[1],pos=segments[s0].data[s1].pos ; if(opt) reprofile() ; clen = segments[s0].data.length ; if(clen==1) arrow.rotation = 90 ; else { if(s1==clen-1) ind = s1-1 ; else ind = s1 ; arrow.rotation = bearing(segments[s0].data[ind].pos,segments[s0].data[ind+1].pos) ; } if(sel.marker==null) sel.marker = new google.maps.Marker ( { position:pos, map:map, cursor:'default', icon:arrow , zIndex:2 } ) ; else // avoid unnecessary redraws { if(arrow.rotation!=sel.orientation) sel.marker.setIcon(arrow) ; if(!pos.equals(sel.marker.getPosition())) sel.marker.setPosition(pos) ; } sel.orientation = arrow.rotation ; procur() ; blackout(penbtn) ; if(segments.length>1&&unambig()) blackout(binbtn) ; else greyout(binbtn) ; if(s1!=0&&s1!=clen-1) blackout(scissorsbtn) ; else greyout(scissorsbtn) ; } /* ------------- selclick: respond to click of cursor button --------------- */ function selclick() { mouseopt = 1-mouseopt ; infowindow.close() ; if(mouseopt) { map.setOptions({draggable:false, draggableCursor:'default'}) ; cursorbtn.btn.setAttribute('src','hand.png') ; clickhandle = google.maps.event.addListener(map,"click",selpoint) ; } else { map.setOptions({draggable:true, draggableCursor:''}) ; cursorbtn.btn.setAttribute('src','arrow.png') ; google.maps.event.removeListener(clickhandle) ; } } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------------------------------------------------- */ function genhead(uri,key) { resuri = uri + '/' ; document.write ('<script src="http://maps.google.com/maps/api/js?' + ((key==null||key==undefined)?'':('key='+key+'&')) + 'libraries=geometry"></scr' + 'ipt>' + '<script src="' + resuri + 'dms.js"></scr' + 'ipt>' + '<script src="' + resuri + 'vector3d.js"></scr' + 'ipt>' + '<script src="' + resuri + 'latlon-ellipsoidal.js"></scr' + 'ipt>' + '<script src="' + resuri + 'utm.js"></scr' + 'ipt>' + '<script src="' + resuri + 'osgridref.js"></scr' + 'ipt>' + '<script src="' + resuri + 'FileSaver.js"></scr' + 'ipt>' + '<script src="' + resuri + 'Blob.js"></scr' + 'ipt>' + '<style type="text/css">html, body {width: 100%; height: 100%}' + 'body {margin:0px}</style><title>Routemaster</title>' + '<link rel="shortcut icon" href=' + resuri + 'bus.gif>' ) ; } /* -------------------------------------------------------------------------- */ /* FUNCTIONS TO GENERATE THE INITIAL MAP */ /* -------------------------------------------------------------------------- */ function genpage() { var thispage=document.URL,xhttp,quotind,plusind,listxhttp ; imginfo = new listinfo() ; imgdiv = null ; elevator = new google.maps.ElevationService ; // coursepoint icons flagsign = { path: "M 0.5 20.5 L 0.5 0.5 12.5 6 0.5 11.5 ", fillColor: '#FCDFFF', fillOpacity: 0.7, strokeColor: 'purple', strokeWeight: 1.5, anchor: new google.maps.Point(0.5,20.5), } ; turnleft = { path: "M 18.5 20.5 L 16.5 11.5 A 2 2 0 0 0 14.5 9.5 "+ "L 11.5 10 11.5 13.5 "+ "6.5 7.5 11.5 1.5 11.5 5 16.5 5.5 A 3.5 3.5 0 0 1 20 9 z", fillColor: '#FCDFFF', fillOpacity: 0.7, strokeColor: 'purple', strokeWeight: 1.5, anchor: new google.maps.Point(18.5,20.5), } ; straighton = { path: "M 7.5 20.5 L 4.5 6.5 0.5 6.5 7.5 0.5 14.5 6.5 10 6.5 z", fillColor: '#FCDFFF', fillOpacity: 0.7, strokeColor: 'purple', strokeWeight: 1.5, anchor: new google.maps.Point(7.5,20.5), } ; turnright = { path: "M 3.5 20.5 L 5.5 11.5 A 2 2 0 0 1 7.5 9.5 L 10.5 10 10.5 13.5 "+ "15.5 7.5 10.5 1.5 10.5 5 5.5 5.5 A 3.5 3.5 0 0 0 2 9 z", fillColor: '#FCDFFF', fillOpacity: 0.7, strokeColor: 'purple', strokeWeight: 1.5, anchor: new google.maps.Point(3.5,20.5), } ; shriek = { path: "M 8.5 21.5 A 2.5 2.5 0 0 1 8.5 16.5 A 2.5 2.5 0 1 1 8.5 21.5 "+ "M 8.5 14.5 4.5 5.5 A 4.5 4.5 0 1 1 12.5 5.5 L 8.5 14.5" , fillColor: '#FCDFFF', fillOpacity: 0.7, strokeColor: 'purple', strokeWeight: 1.5, anchor: new google.maps.Point(8.5,21.5), } ; // icon for arrow representing current waypoint arrow = { path: "M 6 9 0 15 6 0 12 15 z", fillColor: 'black', fillOpacity: 1, strokeColor: 'black', strokeWeight: 0, anchor: new google.maps.Point(6,6), rotation: 0, clickable: false } ; // icon for concentric circles representing draggable waypoint concircle = { path: "M 6 0 A 6 6 0 1 0 6 12 A 6 6 0 1 0 6 0 M 6 3 " + "A 3 3 0 1 0 6 9 A 3 3 0 1 0 6 3", fillColor: 'black', fillOpacity: 0, strokeColor: 'black', strokeWeight: 1, strokeOpacity: 1, anchor: new google.maps.Point(6,6), clickable: false } ; // camera icon camera = { path: "M 0.5 4 A 1.5 1.5 0 0 1 2 2.5 L 5.5 2.5 7 0.5 11 0.5 " + "12.5 2.5 14 2.5 A 1.5 1.5 0 0 1 16 3 L 20 7 16 11 " + "A 1.5 1.5 0 0 1 15 11.5 L 2 11.5 A 1.5 1.5 0 0 1 0.5 10 z " + "M 9 4 A 3 3 0 0 1 9 10 A 3 3 0 0 1 9 4 " , fillColor: '#FCDFFF', fillOpacity: 0.7, strokeColor: 'purple', strokeWeight: 1.5, anchor: new google.maps.Point(21,7), clickable: false } ; window.onload = function() { window.addEventListener("beforeunload",function(e) { var msg = unsavedmsg(0) ; if(msg==null) return undefined ; (e || window.event).returnValue = msg ; //Gecko + IE return msg ; //Gecko + Webkit, Safari, Chrome etc. (msg is ignored by ffx) } ) ; } ; body = document.getElementsByTagName("body")[0] ; while(body.childNodes.length>0) body.removeChild(body.childNodes[body.childNodes.length-1]) ; mapdiv = document.createElement('div') ; mapdiv.setAttribute('id','map') ; mapdiv.setAttribute('style','width:100%;height:100%') ; body.appendChild(mapdiv) ; if((quotind=thispage.indexOf('?'))>=0) { thispage = thispage.substring(quotind+1) ; if((plusind=thispage.indexOf('+'))>0) { getlist(thispage.substring(plusind+1),'uri') ; thispage = thispage.substring(0,plusind) ; } else if(thispage.substring(thispage.length-3)=='.js') { getlist(thispage,'uri') ; mapdiv.appendChild(filedialogue(0)) ; return ; } xhttp = new XMLHttpRequest() ; xhttp.onreadystatechange = function() { if(xhttp.readyState==4&&xhttp.status==200) { var x = parser.parseFromString(xhttp.responseText,"application/xml") ; render(x,thispage,0) ; } } xhttp.open("GET",thispage,true) ; xhttp.send() ; } else mapdiv.appendChild(filedialogue(0)) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------- getlist --------------------------------- */ function getlist(uri,imgtype) { var xhttp = new XMLHttpRequest(),list=null,sizes=null,pixpage=null,ind,r ; var imagedir=null,thumbshape = [] ; imginfo.status = 'waiting' ; imginfo.uri = uri ; imginfo.type = imgtype ; xhttp.onreadystatechange = function() { if(xhttp.readyState==4&&xhttp.status==200) { sizes = 3 ; eval(xhttp.responseText) ; imginfo.list = list ; imginfo.sizes = sizes ; imginfo.dir = imagedir ; imginfo.pixpage = pixpage ; imginfo.status = 'ready' ; // prepare to set the thumb shapes by finding the thumb index for(ind=0;ind<sizes.length&&sizes[ind].type!='thumb';ind++) ; if(ind==sizes.length) { alert('no "sizes" entry of type "thumb"') ; throw '' ; } imginfo.thumbsfx = sizes[ind].suffix ; if(thumbshape.length!=2) { if(sizes[ind].scale>0) r = sizes[ind].scale / sizes[0].scale ; else { alert('thumbs have size '+sizes[ind].scale) ; throw '' ; } } // set thumbshapes for(ind=0;ind<list.length;ind++) if(list[ind].name!=undefined) { if(list[ind].thumbshape==undefined) { if(thumbshape.length==2) list[ind].thumbshape = thumbshape ; else for(list[ind].thumbshape=[0,0],k=0;k<2;k++) list[ind].thumbshape[k] = Math.floor(0.5+list[ind].shape[k]*r) ; } } } } xhttp.open("GET",uri,true) ; xhttp.send() ; } /* ----------------------------- file dialogue ------------------------------ */ function filedialogue(overwrite) { var input = document.createElement('input') ; var para = document.createElement('p') ; para.appendChild(document.createTextNode ('\u00a0\u00a0\u00a0Select TCX/GPX file: ')) ; input.setAttribute('type','file') ; input.setAttribute('accept','.tcx,.gpx') ; input.addEventListener('change',function(e) { reader = new FileReader() ; reader.onload = function(e) { var xmldoc = parser.parseFromString(reader.result,"application/xml") ; render(xmldoc,input.files[0].name,overwrite) ; } reader.readAsText(input.files[0]) ; } ) ; para.appendChild(input) ; return para ; } /* -------------------------------------------------------------------------- */
/*page*/ /* --------------------- set up the map and buttons ------------------------- */ function render(xmldoc,filename,overwrite) { var i,opts,centre,lat,lon,minlon,maxlon,minlat,maxlat,newseg,s0,bounds,sw,ne ; infowindow.close() ; document.onkeydown = walk ; document.onkeyup = function(e) { if(e.keyCode==16) shift = 0 ; } if(overwrite) { for(i=0;i<segments.length;i++) obliterate(i) ; unprofile() ; if(sel.marker!=null) sel.marker.setMap(null) ; segments = [] ; if(imginfo.type=='tcx') imginfo = new listinfo() ; } s0 = segments.length ; if(s0==0) { sel = { marker:null, orientation: null } ; pending = [] ; xpending = [] ; actions = [] ; unsavedchanges = [] ; nactions = dragging = 0 ; loadno = -1 ; prox = proa = pron = null ; altdiv = curcan = curdiv = cmap = smap = curhandle = null ; } // set up segments newseg = gensegment(xmldoc,filename) ; segments.push(newseg[0]) ; if(s0==0) { if(newseg[1].title!=null) settitle(newseg[1].title) ; else settitle('Untitled Route') ; } actions[nactions++] = [ 'load' , s0 , newseg[0].data.slice() , loadno , newseg[1] ] ; loadno = nactions-1 ; if(!newseg[1].optim.already) optimaction(segments.length-1,defparms,0) ; // find max and min lat and long for(i=0;i<segments[s0].data.length;i++) { lat = segments[s0].data[i].pos.lat() ; lon = segments[s0].data[i].pos.lng() ; if(i==0||lon<minlon) minlon = lon ; if(i==0||lon>maxlon) maxlon = lon ; if(i==0||lat<minlat) minlat = lat ; if(i==0||lat>maxlat) maxlat = lat ; } if(s0==0) centre = new google.maps.LatLng((minlat+maxlat)/2,(minlon+maxlon)/2) ; if(map==null) // all this only done on first call { opts = { zoom: 22, center: centre, scaleControl: true, rotateControl: false, streetViewControl: false, mapTypeId: google.maps.MapTypeId.TERRAIN, disableDoubleClickZoom: true, styles: [ { "featureType": "poi", "stylers": [{ "visibility": "off" }] } ], mapTypeControl:true, mapTypeControlOptions: { style:google.maps.MapTypeControlStyle.HORIZONTAL_BAR }, mapTypeIds: [ google.maps.MapTypeId.ROADMAP, google.maps.MapTypeId.TERRAIN, google.maps.MapTypeId.SATELLITE ] } ; map = new google.maps.Map(mapdiv,opts) ; // set up buttons setbtn = genbutton('settings') ; cursorbtn = genbutton('cursor') ; cursorbtn.ui.addEventListener('click',selclick) ; scissorsbtn = genbutton('scissors') ; binbtn = genbutton('bin') ; penbtn = genbutton('pen') ; undobtn = genbutton('undo') ; redobtn = genbutton('redo') ; dlbtn = genbutton('dl') ; selclick() ; } else if(s0!=0) { bounds = map.getBounds() ; sw = bounds.getSouthWest() ; ne = bounds.getNorthEast() ; if(sw.lat()<minlat) minlat = sw.lat() ; if(sw.lng()<minlon) minlon = sw.lng() ; if(ne.lat()>maxlat) maxlat = ne.lat() ; if(ne.lng()>maxlon) maxlon = ne.lng() ; } map.fitBounds(new google.maps.LatLngBounds( new google.maps.LatLng(minlat,minlon), new google.maps.LatLng(maxlat,maxlon))) ; for(i=0;i<segments[s0].data.length;i++) segments[s0].data[i].setmap(map) ; if(nactions>1) donesomething() ; // specifically, done loading & optimisation else actions.length = nactions ; // load with no optimisation hence no undo if(s0==0) { selected = [0,0] ; drawsel(1) ; } else greyout(dlbtn) ; draw(s0) ; connect(s0-1) ; connect(s0) ; reprofile() ; } /* ------------------------------- settitle --------------------------------- */ function settitle(newtitle) { routetitle = newtitle ; var h = document.getElementsByTagName('title')[0] ; while(h.firstChild) h.removeChild(h.firstChild) ; h.appendChild(document.createTextNode(routetitle)) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ------------------------------- retitle ---------------------------------- */ function retitle() { infowindow.close() ; var response = window.prompt("Modify title:",routetitle) ; if(response==null) return ; else response = response.substring(0,15) ; if(response==routetitle) return ; actions[nactions++] = ['edittitle',routetitle,response] ; actions.length = nactions ; blackout(undobtn) ; greyout(redobtn) ; settitle(response) ; } /* ------------------------------- genbutton -------------------------------- */ function genbutton(name) { var u,v,w,b,g,k,h=null,div=document.createElement('div'),act ; u = document.createElement('div') ; u.style.backgroundColor = '#ffffff' ; u.style.border = '2px solid #ffffff' ; u.style.borderRadius = '3px' ; u.style.boxShadow = '0 2px 6px rgba(0,0,0,.3)' ; if(name=='dl'||name=='settings'||name=='cursor') u.style.cursor = 'pointer' ; else u.style.cursor = 'default' ; u.style.marginBottom = '12px' ; if(name!='dl') u.style.marginRight = '4px' ; u.style.textAlign = 'center' ; div.appendChild(u) ; b = document.createElement('img') ; g = name + '.png' ; if(name=='scissors') { h = snip ; div.index = 3 ; } else if(name=='bin') { h = discard ; div.index = 4 ; } else if(name=='pen') { h = labelprompt ; div.index = 5 ; } else if(name=='undo') { h = undo ; div.index = 6 ; } else if(name=='redo') { h = redo ; div.index = 7 ; } else if(name=='dl') { h = dl ; div.index = 8 ; } else if(name=='settings') { h = popup ; div.index = 1 ; } else if(name=='cursor') { g = 'arrow.png' ; k = 'hand.png' ; div.index = 2 ; } if(name!='cursor') { k = 'black' + g ; g = 'grey' + g ; } g = resuri + g ; k = resuri + k ; if(name=='dl'||name=='settings'||name=='cursor') b.setAttribute('src',k) ; else b.setAttribute('src',g) ; b.setAttribute('width',24) ; b.setAttribute('height',24) ; u.appendChild(b) ; map.controls[google.maps.ControlPosition.BOTTOM_CENTER].push(div) ; if(name=='dl'||name=='settings') u.addEventListener('click',h) ; if(name=='dl'||name=='settings') act = 1 ; else act = 0 ; return { ui:u , btn:b , active:act , greyimg:g , blackimg:k , handler:h } ; } /* -------------------------------------------------------------------------- */
/*page*/ /* --------------- construct a segment from an xml document ----------------- */ function genseg(a) { this.data = a ; this.route = this.routehandler = this.dots = this.dothandler = null ; } /* -------------------------------------------------------------------------- */ function gensegment(xmldoc,filename) { var xmlcoords,nodeno,tcx,type,mindist,lat,lon,i,j,segment,node,alt,pos ; var ind,caption,data=[],alreadyoptimised=0,photo,time,valid ; var props = { title: null , inputlen: null , optim: { already: 0, ndel: 0, origlen: 0, parms: null } } tcx = filename.length ; if(filename.substring(tcx-4,tcx)=='.tcx') tcx = 1 ; else if(filename.substring(tcx-4,tcx)=='.gpx') tcx = 0 ; else { alert(filename+' is neither .tcx nor .gcx') ; throw '' ; } if(tcx) { // optimised? xmlcoords = xmldoc.getElementsByTagName('Optimised') ; if(xmlcoords.length) { props.optim.already = 1 ; props.optim.origlen = parseInt(xmlcoords[0].getAttribute('from')) ; props.optim.ndel = props.optim.origlen - parseInt(xmlcoords[0].getAttribute('to')) ; props.optim.parms = { tol: parseFloat(xmlcoords[0].getAttribute('tol')) , maxsep: parseFloat(xmlcoords[0].getAttribute('maxsep')) , wppenalty: parseFloat(xmlcoords[0].getAttribute('wppenalty')) , vweight: parseFloat(xmlcoords[0].getAttribute('vweight')) } ; } // photo list? if(imginfo.uri==null||imginfo.type=='tcx') { xmlcoords = xmldoc.getElementsByTagName('PhotoList') ; if(xmlcoords.length) { imginfo = new listinfo() ; getlist(xmlcoords[0].getAttribute('src'),'tcx') ; } } // route title xmlcoords = xmldoc.getElementsByTagName('Name') ; for(i=0;i<xmlcoords.length&&props.title==null;i++) if( xmlcoords[i].parentNode.nodeName=='Course' || xmlcoords[i].parentNode.nodeName=='Lap' ) props.title = xmlcoords[i].childNodes[0].textContent ; // loop over the track points to get the coords xmlcoords = xmldoc.getElementsByTagName('Trackpoint') ; for(i=0;i<xmlcoords.length;i++) { lat = lon = alt = time = null ; photo = [] ; for(valid=1,nodeno=0;nodeno<xmlcoords[i].childNodes.length;nodeno++) { node = xmlcoords[i].childNodes[nodeno] ; if(node.nodeName=='AltitudeMeters') alt = xmlfloat(node) ; else if(node.nodeName=='Time') // '1970-01-01T03:040:08Z' { time = new Date(node.childNodes[0].textContent) ; if(!isvaliddate(time)) time = null ; } else if(node.nodeName=='Position') for(j=0;j<node.childNodes.length;j++) { if(node.childNodes[j].nodeName=='LatitudeDegrees') lat = xmlfloat(node.childNodes[j]) ; else if(node.childNodes[j].nodeName=='LongitudeDegrees') lon = xmlfloat(node.childNodes[j]) ; } else if(node.nodeName=='Extensions') for(j=0;j<node.childNodes.length;j++) { if(node.childNodes[j].nodeName=='Photo') photo = node.childNodes[j].childNodes[0].textContent.split(' ') ; else if(node.childNodes[j].nodeName=='ValidTime') valid = 0 ; } } if(lat==null||lon==null) continue ; data[data.length] = new datatype(new google.maps.LatLng(lat,lon),alt) ; for(ind=0;ind<photo.length;ind++) data[data.length-1].addphoto(photo[ind]) ; if(valid) data[data.length-1].t = time ; } } else // gpx { if(xmldoc.getElementsByTagName('name').length>0) props.title = xmldoc.getElementsByTagName('name')[0].childNodes[0].textContent ; // loop over the track points to get the coords xmlcoords = xmldoc.getElementsByTagName('trkpt') ; if(xmlcoords.length==0) xmlcoords = xmldoc.getElementsByTagName('rtept') ; for(i=0;i<xmlcoords.length;i++) { lat = parseFloat(xmlcoords[i].getAttribute('lat')) ; lon = parseFloat(xmlcoords[i].getAttribute('lon')) ; for(time=alt=null,nodeno=0;nodeno<xmlcoords[i].childNodes.length;nodeno++) { node = xmlcoords[i].childNodes[nodeno] ; if(node.nodeName=='ele') alt = parseFloat(node.textContent) ; else if(node.nodeName=='time') time = new Date(node.childNodes[0].textContent) ; } data[data.length] = new datatype(new google.maps.LatLng(lat,lon),alt) ; data[data.length-1].t = time ; } } for(i=0;i<data.length;i++) if(data[i].h==null) { alert(data[i].pos+' has no altitude... unable to proceed') ; throw '' ; } props.inputlen = data.length ; for(i=0;i<data.length;i++) if((time=data[i].t)!=null) // nullify illegal times if(time.getTime()<365*24*3600000) data[i].t = null ; segment = new genseg(data) ; // loop over the course points to get the labels if(tcx) xmlcoords = xmldoc.getElementsByTagName('CoursePoint') ; else xmlcoords = xmldoc.getElementsByTagName('wpt') ; for(i=0;i<xmlcoords.length;i++) { caption = type = lat = lon = null ; if(tcx) for(nodeno=0;nodeno<xmlcoords[i].childNodes.length;nodeno++) { node = xmlcoords[i].childNodes[nodeno] ; if(node.nodeName=='Name') caption = node.childNodes[0].textContent ; else if(node.nodeName=='PointType') type = node.childNodes[0].textContent ; else if(node.nodeName=='Position') for(j=0;j<node.childNodes.length;j++) { if(node.childNodes[j].nodeName=='LatitudeDegrees') lat = xmlfloat(node.childNodes[j]) ; else if(node.childNodes[j].nodeName=='LongitudeDegrees') lon = xmlfloat(node.childNodes[j]) ; } } else { lat = parseFloat(xmlcoords[i].getAttribute('lat')) ; lon = parseFloat(xmlcoords[i].getAttribute('lon')) ; for(nodeno=0;nodeno<xmlcoords[i].childNodes.length;nodeno++) { node = xmlcoords[i].childNodes[nodeno] ; if(node.nodeName=='name') caption = node.childNodes[0].textContent ; else if(node.nodeName=='type') type = node.childNodes[0].textContent ; } } if(lat==null||lon==null||caption==null||type==null) { alert('Badly formatted course point' ) ; throw '' ; } pos = new google.maps.LatLng(lat,lon) ; for(j=0;j<data.length;j++) if(j==0||dist(pos,data[j].pos)<mindist) { mindist = dist(pos,data[j].pos) ; ind = j ; } segment.data[ind].setlabel(type,caption) ; } if(props.optim.origlen==0) props.optim.origlen = data.length ; return [ segment , props ] ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------------------------------------------------- */ /* OPTIMISATION */ /* -------------------------------------------------------------------------- */ function optimaction(segno,parms,force) { var s = segments[segno], result = optimise(s,parms) ; var ndel = s.data.length - result.length ; if((force==0&&ndel<s.data.length/2)||ndel==0) return 0 ; actions[loadno][4].optim.ndel = ndel ; actions[nactions++] = [ 'optimise' , segno , parms ] ; segments[segno] = new genseg(result) ; actions[loadno][4].optim.parms = { tol: parms.tol , maxsep: parms.maxsep , wppenalty: parms.wppenalty , vweight: parms.vweight } ; return 1 ; } /* -------------------------------------------------------------------------- */ function optimprompt() { var msg = 'Enter 4 optimisation parms (tol, maxsep, wppenalty and vweight)' ; var parmstr = defparms.tol + ' ' + defparms.maxsep.toFixed(0) + ' ' + defparms.wppenalty.toFixed(0) + ' ' + defparms.vweight.toFixed(1) ; var parms,i ; infowindow.close() ; for(i=0;;i++) { newparms = prompt(msg,parmstr) ; if(newparms==null) return ; if(newparms=='') { parms = defparms ; break ; } newparms = newparms.split(' ') ; if(newparms.length==0) { parms = defparms ; break ; } parms = { tol: parseFloat(newparms[0]) , maxsep: parseFloat(newparms[1]) , wppenalty: parseFloat(newparms[2]) , vweight: parseFloat(newparms[3]) } ; if( isNaN(parms.tol)==0 && isNaN(parms.maxsep)==0 && isNaN(parms.wppenalty)==0 && isNaN(parms.vweight)==0 ) break ; if(i==0) msg = '*** Illegal parms ***\n' + msg ; } if(optimaction(segments.length-1,parms,1)) { donesomething() ; draw(segments.length-1) ; } routeinfo() ; } /* -------------------------------------------------------------------------- */ function optimise(s,parms) { var stk,nstk,stk2,clen=s.data.length,i,j,m,step=new Array(clen-1) ; var opos,oalt,npos,nalt,ndatum,mpos,malt,arccentre,arctol,pathpos ; var legal,theta,omega,x,y,hyp,tdist,maxtheta,mintheta,d,dver,od,odver,odash ; var bearings,pi=Math.PI,tol=parms.tol ; stk = [ { data:[s.data[0]] , err:0 , pathpos:1 } ] ; for(i=0;i<clen-1;i++) step[i] = dist(s.data[i].pos,s.data[i+1].pos) ; while(stk[0].pathpos<clen) { pathpos = stk[0].pathpos ; opos = s.data[pathpos-1].pos ; oalt = s.data[pathpos-1].h ; // try extending to pathpos+i for(bearings=[],nstk=[],arctol=null,i=0;i<clen-pathpos;i++) { ndatum = s.data[pathpos+i] ; npos = ndatum.pos ; nalt = ndatum.h ; if(i==0) hyp = step[pathpos-1] ; else if((hyp=dist(opos,npos))>parms.maxsep) break ; omega = angle(opos,npos) ; // find the min and max legal bearing if(hyp>tol) { theta = Math.asin(tol/hyp) ; if(arctol==null) { arccentre = omega ; arctol = theta ; } else { odash = omega - arccentre ; while(odash>pi) odash -= 2*pi ; while(odash<-pi) odash += 2*pi ; maxtheta = Math.min(arctol,odash+theta) ; mintheta = Math.max(-arctol,odash-theta) ; if(maxtheta<mintheta) break ; arccentre += (maxtheta+mintheta) /2 ; arctol = (maxtheta-mintheta) /2 ; } } bearings[i] = { hyp:hyp , omega:omega } ; // see whether this breaches the max error on any intermediate point for(legal=1,od=odver=tdist=m=0;m<i;m++,od=d,odver=dver) { mpos = s.data[pathpos+m].pos ; malt = s.data[pathpos+m].h ; x = bearings[m].hyp ; theta = bearings[m].omega ; d = x * Math.sin(theta-omega) ; dver = 0 ; if(d*d<tol*tol&&oalt!=null&&nalt!=null&&malt!=null) { y = hyp - x*Math.cos(theta-omega) ; y = Math.sqrt(d*d+y*y) ; dver = parms.vweight * ( malt - (oalt*y+nalt*x)/(x+y) ) ; } if(d*d+dver*dver>tol*tol) { legal = 0 ; break ; } tdist += (step[pathpos-1+i]/3) * (d*d+d*od+od*od + dver*dver+odver*dver+odver*odver) ; } // if we emerge with 'legal' non-zero then we may advance to pathpos+i // and tdist is the sum of squared errors if(legal) nstk.push ( { data: stk[0].data.concat([ndatum]) , err: stk[0].err + pi*tdist + parms.wppenalty , pathpos: stk[0].pathpos+i+1 } ) ; if(ndatum.type!=null||ndatum.photo.length>0) break ; } // end loop over i for(stk2=[],i=1,j=0;i<stk.length||j<nstk.length;) if(i==stk.length) stk2.push(nstk[j++]) ; else if(j==nstk.length||stk[i].pathpos<nstk[j].pathpos) stk2.push(stk[i++]) ; else if(stk[i].pathpos>nstk[j].pathpos) stk2.push(nstk[j++]) ; else if(stk[i].err<nstk[j].err) { stk2.push(stk[i++]) ; j += 1 ; } else { stk2.push(nstk[j++]) ; i += 1 ; } stk = stk2 ; } return stk[0].data ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------------------------------------------------- */ /* THE MAIN POPUP MENU AND THE FUNCTIONS IT GOVERNS */ /* -------------------------------------------------------------------------- */ function popup() { var opts,pos,s0=selected[0],s1=selected[1] ; infowindow.close() ; if(dragging) { opts = finalbox + "Hit [return] when you've finished dragging.</div>" ; infowindow.open(opts,getbtnpos(0),'settings') ; return ; } // route options opts = textbox + active + '"routeinfo()">Route info</span><br>' ; if(altdiv==null) opts += active + '"profile()">Show altitude profile</span><br>' ; else opts += active + '"unprofile()">Hide altitude profile</span><br>' ; opts += active + '"addload(1)">Load new route</span></div>' + textbox ; // segment options if(unambig()) opts += active + '"revseg()">' ; else opts += inactive ; opts += 'Reverse segment</span><br>' ; opts += active + '"manualcal()">Calibrate segment altitudes</span><br>' ; opts += active + '"addload(0)">Load route as a new segment</span></div>' ; // waypoint options opts += textbox + active + '"wpinfo()">Waypoint info</span><br>' ; if(segments[selected[0]].data.length>1) opts += active + '"wpdel()">' ; else opts += inactive ; opts += 'Delete waypoint</span><br>' ; opts += active + '"draggit(0)">Make waypoint draggable</span><br>' ; opts += active + '"inswp(1)">Insert draggable waypoint ahead</span><br>' ; opts += active + '"inswp(-1)">Insert draggable waypoint behind</span>' ; opts += '</div>'+finalbox+active ; // tool options if( document.documentElement.requestFullscreen || document.documentElement.mozRequestFullScreen || document.documentElement.webkitRequestFullscreen || document.documentElement.msRequestFullscreen) { if( (document.fullScreenElement && document.fullScreenElement !== null) || (!document.mozFullScreenElement && !document.webkitFullScreenElement) ) opts += '"enterFullscreen()">Enter full screen</span><br>' + active ; else opts += '"exitFullscreen()">Leave full screen</span><br>' + active ; } opts += '"help()">Help</span></div>' ; infowindow.open(opts,getbtnpos(0),'settings') ; } /* ------------------------------- calwork --------------------------------- */ function calwork(s0,y) { var i,s1 ; for(s1=0;s1<segments[s0].data.length;s1++) if(segments[s0].data[s1].h!=null) segments[s0].data[s1].h += y ; reprofile() ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ------------------------------ manualcal --------------------------------- */ function manualcal() { infowindow.close() ; var x,y,s0=selected[0] ; x = prompt('Enter offset in metres to add to altidudes:') ; if(x==null) return ; y = parseFloat(x) ; if(isNaN(y)) { alert(x+' is not a number') ; return ; } calwork(s0,y) ; done(['recal',s0,y]) ; } /* --------------------------------- help ----------------------------------- */ function help() { var str ; infowindow.close() ; str = textbox + '<table cellpadding=0 cellspacing=0>' ; str += '<tr><td style="padding-bottom:4px">' ; str += '<nobr><img src="'+cursorbtn.greyimg+'" width=24 height=24>&nbsp;' ; str += '<img src="'+cursorbtn.blackimg+'" width=24 height=24></nobr>' ; str += '<td rowspan=6>&nbsp;&nbsp;&nbsp;' ; str += '<td style="padding-bottom:4px">' ; str += 'toggle between using the mouse to select waypoints and to ' ; str += 'drag the map<br>(the space bar has the same function)' ; str += '<tr><td style="padding-bottom:4px">' ; str += '<nobr><img src="'+scissorsbtn.blackimg+'" width=24 height=24>&nbsp;' ; str += '<img src="'+binbtn.blackimg+'" width=24 height=24></nobr>' ; str += '<td style="padding-bottom:4px">' ; str += 'split the current segment at the selected point<br>' ; str += 'delete the currrent segment (or use the delete or backspace key)' ; str += '<tr><td style="padding-bottom:4px">' ; str += '<img src="'+penbtn.blackimg+'" width=24 height=24>' ; str += '<td style="padding-bottom:4px">' ; str += 'add a labelled coursepoint at the current position (1-10chars)<br>' ; str += 'click on flag to edit; right-click to change symbol; ' ; str += 'delete label to delete' ; str += '<tr><td style="padding-bottom:4px">' ; str += '<nobr><img src="'+dlbtn.blackimg+'" width=24 height=24>&nbsp;' ; str += '<img src="'+setbtn.blackimg+'" width=24 height=24></nobr>' ; str += '<td style="padding-bottom:4px">download route as .tcx<br>' ; str += 'access to miscellaneous tools and functions' ; str += '<tr><td valign=top>Keyboard: <td>' ; str += '\u2190/\u2192 move the current waypoint forwards or backwards;<br>' ; str += '\u2193 centres the map on the current waypoint;<br>' ; str += '[return] makes the current waypoint draggable;<br>' ; str += '[tab] inserts a draggable waypoint;<br>[space]=toggle cursor mode;' ; str += '<br>[del], [backspace]=delete segment (bin button).' ; str += '<tr><td valign=top>Mouse: <td>when the cursor is in selection' ; str += ' mode:<br>[shift click] extends the current segment by' ; str += ' the cursor position.</table></div>' ; str += finalbox + '<a style="cursor:pointer;color:#0000bd;text-'+ 'decoration:none" href="http://www.masterlyinactivity.com/software'+ '/routemaster.html" target="_blank">Technical documentation and '+ 'source code</a>'+neutral+' (opens in new tab/window)</span></div>' ; infowindow.open(str,getbtnpos(0),'help') ; } /* -------------------------------------------------------------------------- */
/*page*/ /* --------------------------------- wpdel ---------------------------------- */ function wpdelwork(s0,s1) { var i,response=segments[s0].data[s1],clen=segments[s0].data.length ; response.setmap(null) ; for(i=s1;i<clen-1;i++) segments[s0].data[i] = segments[s0].data[i+1] ; segments[s0].data.length = clen-1 ; selected = [s0,s1] ; if(s1==segments[s0].data.length) selected[1] -= 1 ; redrawconnect(s0,s1) ; drawsel(1) ; return response ; } function wpdel() { var s0=selected[0],s1=selected[1],i ; infowindow.close() ; done(['wpdel',s0,s1,wpdelwork(s0,s1)]) ; } /* --------------------------------- revseg --------------------------------- */ function revsegwork(s0) { var i,s=segments[s0],j,x,len=s.data.length ; disconnect(s0-1) ; disconnect(s0) ; for(i=0;i<len/2;i++) { j = (len-1) - i ; x = s.data[i] ; s.data[i] = s.data[j] ; s.data[j] = x ; } for(i=0;i<s.data.length;i++) if(s.data[i].type=='Right') s.data[i].settype('Left') ; else if(s.data[i].type=='Left') s.data[i].settype('Right') ; if(s0==selected[0]) selected[1] = (len-1) - selected[1] ; connect(s0-1) ; connect(s0) ; drawsel(1) ; } /* -------------------------------------------------------------------------- */ function revseg() { infowindow.close() ; revsegwork(selected[0]) ; done(['revseg',selected[0]]) ; } /* -------------------------------------------------------------------------- */ function addload(overwrite) { var msg ; infowindow.close() ; if(overwrite) { msg = unsavedmsg(1) ; if(msg!=null) if(!confirm(msg)) return ; } infowindow.open(filedialogue(overwrite),getbtnpos(0),'addload') ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------------------------------------------------- */ /* DRAGGING A (POSSIBLY NEWLY INSERTED) WAYPOINT IS QUITE A LOT OF WORK */ /* -------------------------------------------------------------------------- */ function insert(s0,s1,n) { var i ; for(i=segments[s0].data.length+n-1;i>s1;i--) segments[s0].data[i] = segments[s0].data[i-n] ; for(i=0;i<n;i++) segments[s0].data[s1+i] = new datatype(null,null) ; } /* --------------------------------- inswp ---------------------------------- */ function inswp(dir) { var s0=selected[0],s1=selected[1],bounds,del,pos,data=segments[s0].data ; var len = data.length ; if(len==1) pos = data[0].pos ; if(dir>=0) s1 = selected[1] += 1 ; insert(s0,s1,1) ; if(len==1) { bounds = map.getBounds() ; del = bounds.getNorthEast().lng() - bounds.getSouthWest().lng() ; pos = new google.maps.LatLng(pos.lat(),pos.lng()+dir*del/10) ; } else if(s1==0) pos = interp(data[2].pos,data[1].pos,1.5) ; else if(s1<len) pos = interp(data[s1-1].pos,data[s1+1].pos,0.5) ; else pos = interp(data[s1-2].pos,data[s1-1].pos,1.5) ; data[s1].setpos(pos) ; draggit(1) ; } /* -------------------------------- draggit --------------------------------- */ // draggit makes the current waypoint draggable var l1,l2,startpos,seg0,seg1,seg2,colour,inserted ; function draggit(insparm) { var s0=selected[0],s1=selected[1],start,end,i,len=segments[s0].data.length ; startpos = segments[s0].data[s1].pos ; inserted = insparm ; infowindow.close() ; greyout(scissorsbtn) ; greyout(binbtn) ; greyout(penbtn) ; greyout(undobtn) ; greyout(redobtn) ; greyout(dlbtn) ; map.panToBounds(new google.maps.LatLngBounds(startpos,startpos)) ; sel.marker.setMap(null) ; sel.marker = new google.maps.Marker( { position: segments[s0].data[s1].pos, map: map, cursor: 'default', icon: concircle , draggable: true , zIndex: 2 } ) ; if(s0&1) colour = "#ff9999" ; else colour = "#ff0000" ; segments[s0].route.setMap(null) ; if(segments[s0].clickhandler!=null) { google.maps.event.removeListener(segments[s0].clickhandler) ; segments[s0].clickhandler = null ; } seg0 = seg2 = null; if(s1>1) { seg0 = new google.maps.Polyline(new linepath(s0,0,s1,colour)) ; seg0.setMap(map) ; } if(s1==0) start = 0 ; else start = s1-1 ; if(s1==len-1) end = s1+1 ; else end = s1+2 ; seg1 = new google.maps.Polyline(new linepath(s0,start,end,colour)) ; seg1.setMap(map) ; if(s1<segments[s0].data.length-2) { seg2 = new google.maps.Polyline(new linepath(s0,s1+1,len,colour)) ; seg2.setMap(map) ; } l1 = google.maps.event.addListener(sel.marker,'drag',function() { segments[s0].data[s1].setpos(this.getPosition()) ; seg1.setMap(null) ; seg1 = new google.maps.Polyline(new linepath(s0,start,end,colour)) ; seg1.setMap(map) ; if(s1==0&&s0>0) { disconnect(s0-1) ; connect(s0-1) ; } if(s1==len-1&&s0<segments.length-1) { disconnect(s0) ; connect(s0) ; } } ) ; dragging = 1 ; } /* ------------------------------- undraggit -------------------------------- */ // undraggit is invoked by [return] to terminate waypoint dragging function undraggit() { var s0=selected[0],s1=selected[1],i,s1dash,pos=segments[s0].data[s1].pos ; var xpos ; google.maps.event.removeListener(l1) ; dragging = 0 ; if(seg0!=null) seg0.setMap(null) ; seg1.setMap(null) ; if(seg2!=null) seg2.setMap(null) ; segments[s0].route = new google.maps.Polyline(new linepath(s0,-1,0,colour)) ; segments[s0].route.setMap(map) ; segments[s0].data[s1].h = null ; lookupalt(s0,s1) ; sel.marker.setMap(null) ; sel.marker = null ; // force a redraw drawsel(1) ; if(inserted||dist(startpos,pos)>5) done(['move',s0,s1,startpos,pos,inserted]) ; if(segments.length==1) blackout(dlbtn) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------------------------------------------------- */ /* CODE TO GET ALTITUDES FOR NEWLY INSERTED POINTS */ /* -------------------------------------------------------------------------- */ function lookupalt(s0,s1) // set up a request for the alt of the new point { var hi,lo,segno=s0,ptno=s1,lopos,hipos,datum=segments[s0].data[s1] ; for(lo=null,s0=segno,s1=ptno-1;lo==null;s1--) { if(s1<0) { s0 -= 1 ; if(s0<0) break ; s1 = segments[s0].data.length-1 ; } if(segments[s0].data[s1].h!=null) lo = [s0,s1] ; } for(hi=null,s0=segno,s1=ptno+1;hi==null;s1++) { if(s1==segments[s0].data.length) { s0 += 1 ; if(s0==segments.length) break ; s1 = 0 ; } if(segments[s0].data[s1].h!=null) hi = [s0,s1] ; } if(lo==null&&hi==null) { alert('no points left with altitudes: unable to proceed') ; throw '' ; } if(lo!=null) lopos = segments[lo[0]].data[lo[1]].pos ; if(hi!=null) hipos = segments[hi[0]].data[hi[1]].pos ; if(lo==null||(hi!=null&&dist(lopos,datum.pos)>dist(hipos,datum.pos))) { lo = hi ; lopos = hipos ; } pending.push([datum,lopos,segments[lo[0]].data[lo[1]].h]) ; elevator.getElevationForLocations({locations:[datum.pos,lopos]},calibrate) ; } /* -------------------------------------------------------------------------- */ // pending is the list of inserted points for which google altitudes are needed // note a pitfall with the elevation service - it's hard to tell which // response corresponds to which request: the coordinates may not match // because google truncates to 0.00001 deg; hence the use of the dist function. function calibrate(results,status) { var pno,flag ; if(status===google.maps.ElevationStatus.OVER_QUERY_LIMIT) alert('No calibration data available: over Google query limit') ; else if(status!==google.maps.ElevationStatus.OK||results.length!=2) alert('Calibration error') ; // now find whether any of our elevation results allow us to fix an altitude for(pno=0;pno<pending.length;pno++) if( dist(pending[pno][0].pos,results[0].location)<5 && dist(pending[pno][1],results[1].location)<5 ) { if(pno>0) alert('Warning: Google elevation results out of sequence') ; diff = results[0].elevation - results[1].elevation ; pending[pno][0].h = pending[pno][2] + diff ; } for(i=pno=0;pno<pending.length;pno++) if(pending[pno][0].h==null) { if(pno!=i) pending[i] = pending[pno] ; i += 1 ; } pending.length = i ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------------------------------------------------- */ /* FUNCTIONS FOR COMPUTING & DISPLAYING THE ALTITUDE PROFILE */ /* -------------------------------------------------------------------------- */ function profile() { infowindow.close() ; var i,sum,s0,s1,len,oldpos,pos,alt,h,amin,amax,c,y,step,ctx ; var startpos,prevpos ; for(pron=s0=0;s0<segments.length;s0++) pron += segments[s0].data.length ; prox = new Array(pron) ; proa = new Array(pron) ; smap = new Array(segments.length) ; cmap = new Array(610) ; prox[0] = 0 ; for(amax=amin=null,sum=pron=s0=0;s0<segments.length;s0++) for(len=segments[s0].data.length,s1=0;s1<len;s1++,pron++) { proa[pron] = segments[s0].data[s1].h ; if(proa[pron]!=null) { if(amax==null||proa[pron]>amax) amax = proa[pron] ; if(amin==null||proa[pron]<amin) amin = proa[pron] ; } pos = segments[s0].data[s1].pos ; if(pron) sum = prox[pron] = sum + dist(pos,oldpos) ; oldpos = pos ; } if(pron==0||amin==amax) return ; if(amin>0) { if(amax>3*amin) amin = 0 ; else amin *= 1 - (amax/amin-1)/2 ; } altdiv = document.createElement('div') ; altdiv.setAttribute ('style','position:absolute;height:190px;right:0;top:0;width:620px') ; c = document.createElement('canvas') ; c.setAttribute('width',620) ; c.setAttribute('height',200) ; altdiv.appendChild(c) ; body.appendChild(altdiv) ; ctx = c.getContext("2d") ; ctx.font = "10px Helvetica" ; ctx.lineWidth = 0 ; ctx.globalAlpha = 0.6 ; ctx.fillStyle = 'lightgray' ; ctx.lineWidth = 0 ; ctx.rect(0,0,620,200) ; ctx.fill() ; // draw a profile of each segment for(i=pron=s0=0;s0<segments.length;s0++) { len = segments[s0].data.length ; smap[s0] = new Array(len) ; if(s0&1) ctx.fillStyle = "#ff9999" ; else ctx.fillStyle = "#ff0000" ; for(startpos=opos=null,s1=0;s1<len;prevpos=[s0,s1],s1++,pron++,opos=pos) { pos = 10 + 600 * prox[pron] / sum ; if(opos!=null) for(;i<(pos+opos)/2;i++) cmap[i-10] = prevpos ; smap[s0][s1] = 0.5 + Math.floor(pos) ; if(proa[pron]!=null) { y = 10 + 180 * (amax-proa[pron]) / (amax-amin) ; if(startpos==null) { ctx.beginPath() ; ctx.moveTo(pos,y) ; startpos = pos ; } else ctx.lineTo(pos,y) ; } } ctx.lineTo(pos,190) ; ctx.lineTo(startpos,190) ; ctx.closePath() ; ctx.fill() ; } for(;i<610;i++) cmap[i] = prevpos ; // lines if(amax-amin>2500) step = 1000 ; else if(amax-amin>1250) step = 500 ; else step = 100 ; for(i=step*Math.floor(amin/step+1);i<amax;i+=step) { y = 10.5 + Math.floor(180*(amax-i)/(amax-amin)) ; ctx.beginPath() ; ctx.lineWidth = 1 ; ctx.strokeStyle = '#555' ; ctx.moveTo(10,y) ; ctx.lineTo(610,y) ; ctx.stroke() ; ctx.strokeText(i,590,y-2) ; } // cursor curdiv = document.createElement('div') ; curdiv.setAttribute ('style','position:absolute;height:190px;right:0;top:0;width:620px') ; curhandle = curdiv.addEventListener("click",function(e) { var pos = e.clientX - (window.innerWidth-610) ; if((pos-594)*(pos-594)+(e.clientY-16)*(e.clientY-16)<200) { unprofile() ; return ; } if(pos<0) pos = 0 ; else if(pos>600) pos = 600 ; selected = cmap[pos] ; drawsel(0) ; } ) ; body.appendChild(curdiv) ; curcan = null ; procur() ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ------------------------------- unprofile -------------------------------- */ function unprofile() { var i,match,node ; infowindow.close() ; if(altdiv==null) return ; curdiv.removeEventListener('click',curhandle) ; for(match=0,i=body.childNodes.length-1;match==0&&i>=0;i--) { node = body.childNodes[i] ; match = (node==altdiv) ; body.removeChild(node) ; } prox = proa = altdiv = cmap = smap = curdiv = curcan = curhandle = null ; } function reprofile() { if(altdiv!=null) { unprofile() ; profile() ; } } /* -------------------------------- procur ---------------------------------- */ function procur() { if(altdiv==null) return ; var pos = smap[selected[0]][selected[1]] , ctx , i ; if(curcan!=null) curdiv.removeChild(curcan) ; curcan = document.createElement('canvas') ; curcan.setAttribute('width',620) ; curcan.setAttribute('height',200) ; curdiv.appendChild(curcan) ; ctx = curcan.getContext("2d") ; ctx.beginPath() ; ctx.lineWidth = 1 ; ctx.moveTo(pos,10) ; ctx.lineTo(pos,190) ; ctx.stroke() ; // the circle of the 'x' ctx.beginPath() ; ctx.strokeStyle = '#555' ; ctx.fillStyle = 'white' ; ctx.lineWidth = 3 ; ctx.arc(604,16,14.1,0,2*Math.PI,false) ; ctx.stroke() ; ctx.fill() ; for(i=6;i<=26;i+=20) // the two bars of the 'x' { ctx.beginPath() ; ctx.moveTo(594,i) ; ctx.lineTo(614,32-i) ; ctx.stroke() ; } } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------------------------------------------------- */ /* ROUTEINFO IS A MENU WHICH ITSELF SUPPORTS THE COMBINE FUNCTION */ /* -------------------------------------------------------------------------- */ function routeinfo() { var s0,s1,s,d,asc,des,oalt,alt,nlabels,nowpts,i,unsaved,props,spacing,npix ; var maxsep,sep,outoforder,tlast,otime,time,tdist,ttime,ntimes ; infowindow.close() ; props = actions[loadno][4] ; tlast = null ; tdist = ttime = outoforder = 0 ; maxsep = nlabels = npix = des = asc = d = nowpts = ntimes = 0 ; for(s0=0;s0<segments.length;s0++) { nowpts += segments[s0].data.length ; for(oalt=null,s1=0;s1<segments[s0].data.length;otime=time,s1++) { if((alt=segments[s0].data[s1].h)!=null) { if(oalt!=null) { if(alt>oalt) asc += alt-oalt ; else des += oalt - alt ; } oalt = alt ; } if(segments[s0].data[s1].type!=null) nlabels += 1 ; npix += segments[s0].data[s1].photo.length ; time = segments[0].data[s1].t ; if(time!=null) { time = time.getTime() ; ntimes += 1 ; } if(tlast!=null&&time!=null&&time<tlast) outoforder = 1 ; // out of order if(time!=null) tlast = time ; if(s1) { sep = dist(segments[s0].data[s1-1].pos,segments[s0].data[s1].pos) ; d += sep ; if(sep>maxsep) maxsep = sep ; if(time!=null&&otime!=null) { tdist += sep/1000 ; ttime += (time-otime)/(3600*1000) ; } } } } s = finalbox +'<nobr>Title: <b>'+routetitle+'</b> [' ; s += active + '"retitle()">Edit</span>'+']</nobr><br>' ; if(loadno>0) { s += '<nobr>&nbsp;&nbsp;&nbsp;Last added route' ; if(props.title!=null) s += ' (' + props.title + ')' ; s += ':</nobr><br><nobr>&nbsp;&nbsp;&nbsp;' ; } else s += '<nobr>' ; s += 'Track points on input: ' + props.inputlen ; if(props.optim.already) { s += ' (previously optimised)</nobr>' ; if(nowpts!=props.inputlen) s += '<br>Now ' + nowpts + ' track points' ; } else if(props.optim.ndel==0) { if(nactions==loadno+1) s += ' [' + active + '"optimprompt()">' ; else s += ' [' + inactive ; s += 'Optimise' + neutral + ']</span></nobr>' ; } else s += ', optimised to ' + (props.inputlen-props.optim.ndel) + '</nobr>' ; /* ------------------------------------------------------------------------ */
/*page*/ /* ------------------------------------------------------------------------ */ if(!props.optim.already&&props.inputlen-props.optim.ndel!=nowpts) s += '<br>Now ' + nowpts + ' track points' ; s += '<br>' ; if(outoforder==0) { if(ntimes==0) s += 'No timings provided<br>' ; else { if(ntimes<nowpts) s += (nowpts-ntimes) + ' points have no associated timings ' ; s += '[' + active + '"deltimes()">Discard timings</span>' + neutral + ']<br>' ; } if(tdist>0&&ttime>0) s += 'Average speed = ' + (tdist/ttime).toFixed(1) + ' km/hr<br>' ; } else s += 'Times are out of sequence (will be discarded on download)<br>' ; if(nlabels>0) s += nlabels + ' labelled course point' + (nlabels>1?'s':'') + '<br>' ; if(npix>0) s += npix + ' photo' + (npix>1?'s':'') + '<br>' ; unsaved = unsavedchanges.length ; if(unsaved>0) s += unsaved + ' unsaved change' + (unsaved>1?'s':'') + '<br>' ; if(segments.length>1) s += segments.length + ' segments [' + active + '"combine()">Combine</span>' + neutral + ']<br>' + '<i>Note that segments must be combined before saving</i><br>' ; s += 'Max waypoint separation: '+maxsep.toFixed(0)+'m<br>' ; if(maxsep>=100) s += '<i>Note that separations &gt;100m are illegal on Garmin</i><br>' + '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[</span>' + active + '"extrapts()">' + 'Interpolate extra points</span>]<br>' + neutral ; s += 'Total distance: '+(d/1000).toFixed(3)+'km<br>' ; s += 'Total ascent: '+asc.toFixed(0)+'m<br>' ; s += 'Total descent: '+des.toFixed(0)+'m</div>' ; infowindow.open(s,getbtnpos(0),'routeinfo') ; } /* -------------------------------------------------------------------------- */ function deltimes() { var s0,s1,task=[] ; for(s0=0;s0<segments.length;s0++) for(s1=0;s1<segments[s0].data.length;s1++) if(segments[s0].data[s1].t!=null) { task.push([s0,s1,segments[s0].data[s1].t]) ; segments[s0].data[s1].t = null ; } infowindow.close() ; done(['deltimes',task]) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ------------------------- interpolate extra points ----------------------- */ function extrapts(opt) { var s0,s1,sep,data,n,opos,npos,i,lambda,lox,nlox,taskno,ind ; var task = [ 'extra' , selected[0] , selected[1] ] ; infowindow.close() ; for(nlox=s0=0;s0<segments.length;s0++) for(data=segments[s0].data,s1=1;s1<data.length;s1++) if((sep=dist(opos=data[s1-1].pos,npos=data[s1].pos))>100) { n = Math.floor(sep/95) ; insert(s0,s1,n) ; for(i=0;i<n;i++) { lambda = (i+1) / (n+1) ; data[s1+i].setpos(new google.maps. LatLng(lambda*npos.lat()+(1-lambda)*opos.lat(), lambda*npos.lng()+(1-lambda)*opos.lng())) ; } if(selected[0]==s0&&s1<=selected[1]) selected[1] += n ; task.push([s0,s1,data.slice(s1-1,s1+n+1)]) ; s1 += n ; nlox += n+2 ; } if(nlox==0) return ; done(task) ; xpending.push(task) ; lox = new Array(nlox) ; for(ind=0,taskno=3;taskno<task.length;taskno++) { n = task[taskno][2].length ; for(i=0;i<n;i++) lox[ind++] = task[taskno][2][i].pos ; } elevator.getElevationForLocations( {locations:lox} , function (results,status) { // assume that the results come in sequence, ie. correspond to xpending[0] var task=xpending.shift(),taskno,d0,dn,lambda ; if(status===google.maps.ElevationStatus.OVER_QUERY_LIMIT) alert('No calibration data available: over Google query limit') ; else if(status!==google.maps.ElevationStatus.OK) alert('Calibration error') ; for(ind=0,taskno=3;taskno<task.length;taskno++,ind+=n+2) { n = task[taskno][2].length-2 ; d0 = task[taskno][2][0].h - results[ind].elevation ; dn = task[taskno][2][n+1].h - results[ind+n+1].elevation ; if( dist(task[taskno][2][0].pos,results[ind].location)>5 || dist(task[taskno][2][n+1].pos,results[ind+n+1].location)>5 ) alert('Anomaly with Google elevation results') ; for(i=0;i<n;i++) { lambda = (i+1) / (n+1) ; task[taskno][2][1+i].h = results[ind+1+i].elevation + lambda*dn + (1-lambda)*d0 ; } } } ) ; routeinfo() ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ------------------------------- combine1 --------------------------------- */ function combine1(sa,sb) { var i,ca,cb,calen,cblen,la=null,lb=null,cdup=0 ; undraw(sb) ; disconnect(sb-1) ; calen = segments[sa].data.length ; cblen = segments[sb].data.length ; cb = segments[sb].data[0].pos ; cdup = ( ca = cb.equals(segments[sa].data[calen-1].pos) ) ; if(cdup) { la = segments[sa].data[calen-1] ; lb = segments[sb].data[0] ; segments[sa].data.length = ( calen -= 1 ) ; } if(selected[0]==sb) selected = [ sa , selected[1]+calen ] ; segments[sa].data = segments[sa].data.concat(segments[sb].data) ; return [ cblen , cdup , la , lb ] ; } function combinework() { var task,s0 ; for(task=['combine',segments.length],s0=1;s0<segments.length;s0++) task.push(combine1(0,s0)) ; segments.length = 1 ; return task ; } /* -------------------------------------------------------------------------- */ function combine() { infowindow.close() ; done(combinework()) ; drawsel(1) ; redraw(0) ; blackout(dlbtn) ; } function recombine() { var s0 ; for(s0=1;s0<segments.length;s0++) { undraw(s0) ; combine1(0,s0) ;} segments.length = 1 ; drawsel(1) ; redraw(0) ; blackout(dlbtn) ; } /* -------------------------------------------------------------------------- */ // combine returns [ cblen , cdup , la , lb ] ; function uncombine(task) { var i,j,llen,flag,subtask ; for(flag=0,s0=task[1]-1,i=task.length-1;i>=2;i--,s0--) { subtask = task[i] ; cblen = subtask[0] ; cdup = subtask[1] ; llen = segments[0].data.length ; segments[s0] = { data: segments[0].data.slice(llen-cblen,llen) , route: null , clickhandler: null } ; llen = segments[0].data.length = llen+cdup-cblen ; if(cdup) { segments[0].data[llen-1] = subtask[2] ; segments[s0].data[0] = subtask[3] ; } if(flag==0&&selected[1]>=llen) { selected = [ s0 , selected[1]-llen ] ; flag = 1 ; } } drawsel(1) ; undraw(0) ; for(i=0;i<segments.length;i++) { draw(i) ; connect(i) ; } greyout(dlbtn) ; } /* -------------------------------------------------------------------------- */ /* WPINFO IS A MENU GIVING ACCESS TO THE SETALT FUNCTION */ /* -------------------------------------------------------------------------- */ function wpinfo() { infowindow.close() ; var s0=selected[0],s1=selected[1],alt,nalt,s,x,lat,lng,grad,gradstr,time ; var datum = segments[s0].data[s1] , pos = datum.pos ; lat = pos.lat() ; lng = pos.lng() ; s = finalbox ; if(lat>=0) s += lat.toFixed(5) + '\u00b0 N, ' ; else { lat = -lat ; s += lat.toFixed(5) + '\u00b0 S, ' ; } if(lng>=0) s += lng.toFixed(5) + '\u00b0 E<br>' ; else { lng = -lng ; s += lng.toFixed(5) + '\u00b0 W<br>' ; } x = new LatLon(lat,lng) ; if(lat>49.9&&lat<62&&lng>-12&&lng<2.5&&lat-1.5*lng<75) s += 'OS grid ref: ' + OsGridRef.latLonToOsGrid(x) ; else s += 'UTM coords = ' + x.toUtm() ; s += '<br>' ; alt = segments[s0].data[s1].h ; if(alt!=null) s += 'Altitude: ' + alt.toFixed(0) + 'm ' + active + '"setalt(1)">[Edit]' ; else s += active + '"setalt(0)">Set altitude' ; s += '</span><br>' ; time = segments[s0].data[s1].t ; if(time!=null&&time.getFullYear()>1980) s += 'Date: ' + time.toDateString() + '<br>' + 'Time: ' + time.toTimeString() + '<br>' ; if(datum.type!=null) s += datum.type + ': ' + datum.marker.title + ' [' + active + '"labelprompt()">Edit</span>]<br>' ; if(alt==null||s1==segments[s0].data.length-1) nalt = null ; else { nalt = segments[s0].data[s1+1].h ; if(nalt!=null) x = dist(pos,segments[s0].data[s1+1].pos) ; } if(nalt!=null&&Math.abs(nalt-alt)<x) { grad = 100*Math.asin((nalt-alt)/x) ; gradstr = Math.abs(grad).toFixed(0) ; if(gradstr=='0') s += 'Flat<br>' ; else if(grad>0) s += 'Climb '+gradstr+'%<br>' ; else s += 'Descend: '+gradstr+'%<br>' ; } s += '<span style="font-size:80%">' ; if(segments.length>1) s += 'Segment '+s0+' p' ; else s += 'P' ; s += 'oint ' + s1 + '</span></div>' ; infowindow.open(s,pos,'wpinfo') ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------- setalt ---------------------------------- */ function setalt(edit) { infowindow.close() ; var s0=selected[0],s1=selected[1],x,y=null,oldalt ; oldalt = segments[s0].data[s1].h.toFixed(0) ; if(edit) x = prompt('Enter altitude (m):',oldalt) ; else x = prompt('Enter altitude (m):') ; if(x==null) return ; if(x!='') { y = parseFloat(x) ; if(isNaN(y)) { alert(x+' is not a number') ; return ; } } if(y==null&&oldalt==null) return ; if(y!=null&&Math.abs(y-oldalt)<0.1) return ; segments[s0].data[s1].h = y ; done(['setalt',s0,s1,oldalt,y]) ; reprofile() ; wpinfo() ; } /* -------------------------------------------------------------------------- */ /* THE LABELS ARE ACCESSED FROM THE PEN BUTTON OR BY CLICKING ON THE MAP */ /* -------------------------------------------------------------------------- */ function labelprompt() { var oldcaption='',oldtype,type='Generic',s0=selected[0],s1=selected[1] ; var str , flag = (infowindow.close()=='wpinfo') ; var datum = segments[s0].data[s1] ; oldtype = datum.type ; if(oldtype!=null) oldcaption = datum.marker.title ; if(oldcaption==null) oldcaption = '' ; if(oldtype!=null) type = oldtype ; if(oldtype==null) str = 'Enter' ; else str = 'Modify or delete' ; var caption = window.prompt(str+' label:',oldcaption) ; if(caption==null) { if(flag) wpinfo() ; else walkto(s0,s1,0) ; return ; } else if(caption=='') type = null ; else caption = caption.substring(0,10) ; if(caption==oldcaption) { if(flag) wpinfo() ; else walkto(s0,s1,0) ; return ; } segments[s0].data[s1].setlabel(type,caption) ; done(['editlabel',s0,s1,oldcaption,caption,oldtype,type]) ; if(flag) wpinfo() ; else walkto(s0,s1,0) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ------------------------------ labelcycle -------------------------------- */ function labelcycle() { var s0,s1,datum,oldtype,caption,type,flag=(infowindow.close()=='wpinfo') ; ; for(s0=0;s0<segments.length;s0++) for(s1=0;s1<segments[s0].data.length;s1++) { datum = segments[s0].data[s1] ; if(datum.marker!=this) continue ; oldtype = datum.type ; caption = datum.marker.title ; if(oldtype=='Generic') type = 'Left' ; else if(oldtype=='Left') type = 'Straight' ; else if(oldtype=='Straight') type = 'Right' ; else if(oldtype=='Right') type = 'Danger' ; else type = 'Generic' ; datum.settype(type) ; selected = [s0,s1] ; done(['editlabel',s0,s1,caption,caption,oldtype,type]) ; if(flag) wpinfo() ; return ; } } function photoprompt(e) { var s0=selected[0],s1=selected[1] ; if(e!=null) e.preventDefault() ; var flag = (infowindow.close()=='wpinfo') ; var datum = segments[s0].data[s1] ; var photo = window.prompt('Enter photo name:','') ; if(photo!=null&&photo!='') { done(['editphoto',s0,s1,datum.photo.length,null,photo]) ; datum.addphoto(photo) ; } if(flag) wpinfo() ; else walkto(s0,s1,0) ; } function photoedit(ind) { var s0=selected[0],s1=selected[1],i ; var flag = (infowindow.close()=='wpinfo') ; var datum = segments[s0].data[s1] ; var photo = window.prompt('New photo name:',datum.photo[ind]) ; if(photo!=null&&photo!='') for(i=0;i<datum.photo.length;i++) if(datum.photo[i]==photo) { photo = null ; break ; } if(photo!=null) { if(photo=='') photo = null ; done(['editphoto',s0,s1,ind,datum.photo[ind],photo]) ; datum.setphoto(ind,photo) ; } if(flag) wpinfo() ; else walkto(s0,s1,0) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ----------------------------- enlarge photo ------------------------------ */ function enlarge(ind,i) { document.onkeydown = imgwalk ; infowindow.close() ; imgind = ind ; selinit = [ selected[0] , selected[1] , ind ] ; imgdiv = document.createElement('div') ; imgdiv.setAttribute ('style','position:fixed;width:100%;height:100%;left:0;top:0') ; imghandle = imgdiv.addEventListener("click",imgwalk) ; imgtable = enlargetable(i) ; imgdiv.appendChild(imgtable) ; body.appendChild(imgdiv) ; } /* ------------------------------ image walk -------------------------------- */ function imgwalk(e) { var s0,s1,ind ; if(e.keyCode==16) shift = 1 ; else if(e.keyCode==39) // forwards { e.preventDefault() ; for(s0=selected[0],s1=selected[1],ind=imgind+1;;ind++) { if(ind>=segments[s0].data[s1].photo.length) { ind = 0 ; s1 += 1 ; } if(s1==segments[s0].data.length) { s0 += 1 ; if(s0==segments.length) s0 = 0 ; s1 = 0 ; } if(s0==selinit[0]&&s1==selinit[1]&&ind==selinit[2]) break ; if(imgreplace(s0,s1,ind)) return ; } } else if(e.keyCode==37) // backwards { e.preventDefault() ; for(s0=selected[0],s1=selected[1],ind=imgind-1;;ind--) { if(ind<0) { s1 -= 1 ; ind = null ; } if(s1<0) { if(s0==0) s0 = segments.length-1 ; else s0 -= 1 ; s1 = segments[s0].data.length-1 ; } if(ind==null) ind = segments[s0].data[s1].photo.length - 1 ; if(s0==selinit[0]&&s1==selinit[1]&&ind==selinit[2]) break ; if(imgreplace(s0,s1,ind)) return ; } } document.onkeydown = walk ; imgdiv.removeEventListener('click',imghandle) ; body.removeChild(imgdiv) ; walkto(selected[0],selected[1],0) ; } /* ----------------------------- image replace ------------------------------ */ function imgreplace(s0,s1,ind) { var i ; if(ind>=0&&segments[s0].data[s1].photo.length>ind) if((i=findimg(segments[s0].data[s1].photo[ind]))>=0) { imgdiv.removeChild(imgtable) ; imgtable = enlargetable(i) ; imgdiv.appendChild(imgtable) ; selected = [s0,s1] ; imgind = ind ; return 1 ; } else return 0 ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------- generate image table -------------------------- */ function enlargetable(i) { var w=window.innerWidth,h=window.innerHeight,t,td,tr,span,img,shape=[0,0] ; var k,ibest,ismall,spare,rooturi,srcset,imguri ; spare = (imginfo.list[i].caption==undefined?20:36) ; for(k=0;k<2;k++) shape[k] = imginfo.list[i].shape[k] ; // find the best size to use for(ismall=ibest=null,k=0;k<imginfo.sizes.length;k++) if(imginfo.sizes[k].type==undefined) { if(ismall==null||imginfo.sizes[k].scale<imginfo.sizes[ismall].scale) ismall = k ; r = imginfo.sizes[k].scale / imginfo.sizes[0].scale ; if(shape[0]*r<=w&&shape[1]*r+spare<=h) if(ibest==null||imginfo.sizes[k].scale>imginfo.sizes[ibest].scale) ibest = k ; } // compute the corresponding shape if(ibest==null) ibest = ismall ; r = imginfo.sizes[ibest].scale / imginfo.sizes[0].scale ; for(k=0;k<2;k++) shape[k] = Math.floor(0.5+shape[k]*r) ; // the image source uri if(imginfo.dir==null) rooturi = imginfo.uri ; else rooturi = reluri(imginfo.uri,imginfo.dir) + '/' ; imguri = reluri(rooturi,imginfo.list[i].name) + imginfo.sizes[ibest].suffix + '.jpg' ; // make an srcset for(srcset='',k=0;k<imginfo.sizes.length;k++) if(imginfo.sizes[k].type==undefined) if(imginfo.sizes[k].scale>imginfo.sizes[ibest].scale) { r = imginfo.sizes[k].scale / imginfo.sizes[ibest].scale ; srcset += reluri(rooturi,imginfo.list[i].name) + imginfo.sizes[k].suffix + '.jpg ' + r.toFixed(1) + "x, " ; } // start building the table t = document.createElement('table') ; t.setAttribute('cellspacing',0) ; t.setAttribute('cellpadding',0) ; t.setAttribute('width','100%') ; t.setAttribute('height','100%') ; t.setAttribute('bgcolor','black') ; t.appendChild(blankrow((h-(shape[1]+spare))/2)) ; // title tr = document.createElement('tr') ; r = blankcol((w-shape[0])/2,imginfo.list[i].caption==undefined?2:3) ; tr.appendChild(r) ; td = document.createElement('td') ; td.setAttribute('colspan',2) ; span = document.createElement('span') ; span.setAttribute('style','text-align:left ; font-family:helvetica ; '+ 'margin:0 0 2px; font-size:18px ; color:silver; ' + 'line-height:18px') ; span.appendChild(document.createTextNode(imginfo.list[i].title)) ; td.appendChild(span) ; tr.appendChild(td) ; t.appendChild(tr) ; // the image tr = document.createElement('tr') ; td = document.createElement('td') ; img = document.createElement('img') ; img.setAttribute('src',imguri) ; if(srcset!='') img.setAttribute('srcset',srcset.substring(0,srcset.length-2)) ; img.setAttribute('width',shape[0]) ; img.setAttribute('height',shape[1]) ; td.appendChild(img) ; tr.appendChild(td) ; tr.appendChild(blankcol((w-shape[0])/2,1)) ; t.appendChild(tr) ; // the caption if(imginfo.list[i].caption!=undefined) { tr = document.createElement('tr') ; td = document.createElement('td') ; td.setAttribute('colspan',2) ; span = document.createElement('span') ; span.setAttribute('style','text-align:left ; font-family:helvetica ; '+ 'margin:2px 0 0; font-size:14px ; color:silver; ' + 'line-height:14px') ; span.appendChild(document.createTextNode(imginfo.list[i].caption)) ; td.appendChild(span) ; tr.appendChild(td) ; t.appendChild(tr) ; } t.appendChild(blankrow((h-(shape[1]+spare))/2)) ; return t ; } function blankcol(wid,nrow) { var td = document.createElement('td') ; td.setAttribute('width',wid) ; if(nrow>1) td.setAttribute('rowspan',nrow) ; td.appendChild(document.createTextNode('\u00a0')) ; return td ; } function blankrow(ht) { var td = document.createElement('td') , tr = document.createElement('tr') ; td.setAttribute('colspan',3) ; td.setAttribute('height',ht) ; td.appendChild(document.createTextNode('\u00a0')) ; tr.appendChild(td) ; return tr ; } /* --------------------------------- photo info ----------------------------- */ function phinfo(i) { infowindow.close() ; var s0=selected[0],s1=selected[1],s,shape=[0,0],ind,hind,r,k ; for(k=0;k<2;k++) shape[k] = imginfo.list[i].shape[k] ; s = finalbox + 'Name: ' + imginfo.list[i].name + '<br>Title: ' + imginfo.list[i].title ; for(hind=null,ind=0;ind<i;ind++) if(imginfo.list[ind].name==null) hind = ind ; if(hind!=null) s += "<br>Under \u201c" + imginfo.list[hind].title + "\u201d" ; // how many sizes? for(r=ind=0;ind<imginfo.sizes.length;ind++) if(imginfo.sizes[ind].type==undefined) r += 1 ; s += '<br>Available in ' + r + ' size' ; // print the sizes for(ind=0;ind<imginfo.sizes.length;ind++) if(imginfo.sizes[ind].type==undefined) { if(ind==0&&imginfo.sizes.length==1) s += ': ' ; else if(ind==0) s += 's: ' ; else if(ind==imginfo.sizes.length-1) s += ' and ' ; else s += ', ' ; r = imginfo.sizes[ind].scale/imginfo.sizes[0].scale ; s += Math.floor(0.5+shape[0]*r) + 'x' + Math.floor(0.5+shape[1]*r) ; } if(imginfo.pixpage!=null) s += '<br><a style="cursor:pointer;color:#0000bd;text-decoration:none" '+ 'href="'+reluri(imginfo.uri,imginfo.pixpage)+'" target="_blank">'+ 'Full photo set</a>'+neutral+' (opens in new tab/window)</span>' ; if(imginfo.list[i].retid!=null) { for(hind=null,ind=0;ind<=i;ind++) if(imginfo.list[ind].retpage!=null) hind = imginfo.list[ind].retpage + '.html#' ; if(hind!=null) s += '<br><a style="cursor:pointer;color:#0000bd;'+ 'text-decoration:none" href="'+reluri(imginfo.uri,hind)+ imginfo.list[i].retid+'" target="_blank">'+ 'Route notes</a>'+neutral+' (opens in new tab/window)</span>' ; } infowindow.open(s,segments[s0].data[s1].pos,'phinfo') ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ------------------------- snip: apply scissors -------------------------- */ function snipwork(s0,s1) { var i,k,newlen ; undraw(s0) ; segments.length += 1 ; for(i=segments.length-1;i>s0+1;i--) segments[i] = segments[i-1] ; newlen = segments[s0].data.length - s1 ; segments[s0+1] = new genseg(segments[s0].data.slice(s1)) ; segments[s0+1].dots = segments[s0].dots ; segments[s0+1].dothandler = segments[s0].dothandler ; segments[s0].dots = segments[s0].dothandler = null ; segments[s0].data.length = s1 + 1 ; segments[s0].data[s1] = new datatype(segments[s0].data[s1].pos,segments[s0].data[s1].h) ; draw(s0) ; draw(s0+1) ; for(i=s0+2;i<segments.length;i++) recolour(i) ; selected = [s0+1,0] ; drawsel(1) ; greyout(dlbtn) ; } function snip() { var i,s0=selected[0],s1=selected[1] ; infowindow.close() ; done(['snip',s0,s1]) ; snipwork(s0,s1) ; } /* ------------------------ discard: bin a segment ------------------------- */ function binwork(s0) { var i ; obliterate(s0) ; for(i=s0;i<segments.length-1;i++) segments[i] = segments[i+1] ; segments.length -= 1 ; for(i=s0;i<segments.length;i++) recolour(i) ; connect(s0-1) ; selected[1] = 0 ; if(selected[0]==segments.length) selected[0] = 0 ; drawsel(1) ; if(segments.length==1) blackout(dlbtn) ; } function discard() { var i,s0=selected[0] ; infowindow.close() ; done(['bin',s0,segments[s0]]) ; binwork(s0) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------- actionname ------------------------------ */ function actionname(x) { if(x[0]=='bin') return 'delete segment' ; if(x[0]=='snip') return 'split segment' ; if(x[0]=='editlabel') { if(x[4]=='') return 'delete label' ; else if(x[3]=='') return 'label waypoint' ; else return 'edit label' ; } if(x[0]=='edittitle') return 'edit title' ; if(x[0]=='wpdel') return 'delete waypoint' ; if(x[0]=='move') { if(x[5]) return 'insert waypoint' ; else return 'drag waypoint' ; } if(x[0]=='recal') return 'recalibrate altitudes' ; if(x[0]=='setalt') return 'set waypoint altitude' ; if(x[0]=='resign') return 'change label symbol' ; if(x[0]=='combine') return 'combine '+x[1]+' segments' ; if(x[0]=='revseg') return 'reverse segment' ; if(x[0]=='interpolate') return 'interpolate missing altitudes' ; if(x[0]=='optimise') return 'optimisation' ; if(x[0]=='deltimes') return 'delete times' ; if(x[0]=='editphoto') { if(x[5]==null) return 'delete photo' ; else if(x[4]==null) return 'add photo' ; else return 'change photo' ; } if(x[0]=='extra') return 'interpolate extra points' ; } function actiontype(x) { if( x=='snip'||x=='combine'||x=='interpolate' || x=='optimise'||x=='load' ) return 0 ; else return 1 ; } /* -------------------------------------------------------------------------- */ function done(something) { if( nactions>0 && unsavedchanges.length>0 && something[0]=='editlabel' && actions[nactions-1][0]==something[0] && actions[nactions-1][1]==something[1] // don't merge change with delete && actions[nactions-1][2]==something[2] && something[6]!=null ) { actions[nactions-1][4] = something[4] ; // caption actions[nactions-1][6] = something[6] ; // type } else { actions[nactions++] = something ; donesomething() ; } } function donesomething() { actions.length = nactions ; blackout(undobtn) ; greyout(redobtn) ; if(actiontype(actions[nactions-1][0])!=0) { if(unsavedchanges.length>=3) unsavedchanges.push(null) ; else unsavedchanges.push(actionname(actions[nactions-1])) ; } } /* -------------------------------------------------------------------------- */
/*page*/ /* --------------------------------- undo ---------------------------------- */ function undo() { infowindow.close() ; var opts = active + '"confirmedundo()">Undo ' + actionname(actions[nactions-1])+'</span>' ; infowindow.open(opts,getbtnpos(5),'undo') ; } function confirmedundo() { var i,ano=nactions-1,action=actions[ano][0],s0=actions[ano][1],s1,caption ; var oldcaption,task,ind ; infowindow.close() ; if(action!='revseg'&&action!='interpolate'&&action!='deltimes') s1 = actions[ano][2] ; if(action=='bin') { disconnect(s0-1) ; for(i=segments.length;i>s0;i--) { segments[i] = segments[i-1] ; recolour(i) ; } segments[s0] = s1 ; for(s1=0;s1<segments[s0].data.length;s1++) segments[s0].data[s1].setmap(map) ; draw(s0) ; connect(s0-1) ; connect(s0) ; if(selected[0]>=s0) selected[0] += 1 ; drawsel(1) ; greyout(dlbtn) ; } else if(action=='snip') // undo snip { selected = [ s0 , segments[s0].data.length-1 ] ; combine1(s0,s0+1) ; for(i=s0+1;i<segments.length-1;i++) { segments[i] = segments[i+1] ; recolour(i) ; } segments.length -= 1 ; if(segments.length==1) blackout(dlbtn) ; draw(s0) ; drawsel(1) ; } else if(action=='editlabel') // undo create/edit/delete label segments[s0].data[s1].setlabel(actions[ano][5],actions[ano][3]) ; else if(action=='edittitle') settitle(s0) ; else if(action=='wpdel') // ['wpdel',s0,s1,wpdelwork(s0,s1)] { insert(s0,s1,1) ; segments[s0].data[s1] = actions[ano][3] ; segments[s0].data[s1].setmap(map) ; selected = [s0,s1] ; redrawconnect(s0,s1) ; drawsel(1) ; } else if(action=='move') { if(actions[ano][5]) wpdelwork(s0,s1) ; else move(s0,s1,actions[ano][3]) ; } else if(action=='recal') calwork(s0,-s1) ; else if(action=='setalt') segments[s0].data[s1].h = actions[ano][3] ; else if(action=='combine') uncombine(actions[ano]) ; else if(action=='revseg') revsegwork(s0) ; else if(action=='interpolate') for(i=0;i<s0.length;i++) segments[0].data[s0[i]].h = null ; else if(action=='deltimes') for(i=0;i<s0.length;i++) segments[s0[i][0]].data[s0[i][1]].t = s0[i][2] ; else if(action=='optimise') // [ 'load' , s0 , data.slice() , loadno , props ] { for(ano--;ano>=0&&actions[ano][0]!='load';ano--) ; segments[s0].data = actions[ano][2] ; actions[loadno][4].optim.ndel = 0 ; selected = [s0,0] ; redraw(s0) ; drawsel(1) ; } else if(action=='editphoto') { ind = actions[ano][3] ; if(actions[ano][5]==null) // undo delete for(i=segments[s0].data[s1].photo.length;i>ind;i--) segments[s0].data[s1].photo[i] = segments[s0].data[s1].photo[i-1] ; if(ind>=segments[s0].data[s1].photo.length) segments[s0].data[s1].addphoto(actions[ano][4]) ; else segments[s0].data[s1].setphoto(ind,actions[ano][4]) ; } else if(action=='extra') for(selected=[s0,s1],i=actions[ano].length-1;i>=3;i--) { task = actions[ano][i] segments[task[0]].data.splice(task[1],task[2].length-2) ; } nactions -= 1 ; if(nactions<=1) greyout(undobtn) ; blackout(redobtn) ; if(actiontype(actions[nactions][0])!=0&&unsavedchanges.length>0) unsavedchanges.length -= 1 ; ; if(action=='optimise'||action=='dltimes') routeinfo() ; else if(action=='editphoto'||action=='editlabel') walkto(s0,s1,0) ; } /* --------------------------------- move ----------------------------------- */ function move(s0,s1,pos) { segments[s0].data[s1].setpos(pos) ; redrawconnect(s0,s1) ; drawsel(1) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* --------------------------------- redo ---------------------------------- */ function redo() { infowindow.close() ; var opts = active + '"confirmedredo()">Redo ' + actionname(actions[nactions])+'</span>' ; infowindow.open(opts,getbtnpos(6),'redo') ; } function confirmedredo() { var i,action=actions[nactions][0],s0=actions[nactions][1],s1,caption,a,b,c ; var task,ind,photo ; if(action!='bin'&&action!='revseg'&&action!='interpolate'&&action!='deltimes') s1 = actions[nactions][2] ; infowindow.close() ; if(action=='bin') binwork(s0) ; else if(action=='snip') snipwork(s0,s1) ; else if(action=='editlabel') // redo create/edit/delete label segments[s0].data[s1].setlabel(actions[nactions][6],actions[nactions][4]) ; else if(action=='edittitle') settitle(s1) ; else if(action=='wpdel') wpdelwork(s0,s1) ; else if(action=='move') // ['move',s0,s1,oldpos,newpos,inserted] { if(actions[nactions][5]) insert(s0,s1,1) ; move(s0,s1,actions[nactions][4]) ; } else if(action=='recal') calwork(s0,s1) ; else if(action=='setalt') segments[s0].data[s1].h = actions[nactions][4] ; else if(action=='combine') recombine(actions[nactions]) ; else if(action=='revseg') revsegwork(s0) ; else if(action=='interpolate') interpolatework() ; else if(action=='deltimes') for(i=0;i<s0.length;i++) segments[s0[i][0]].data[s0[i][1]].t = null ; else if(action=='optimise') { result = optimise(segments[s0],actions[nactions][2]) ; actions[loadno][4].optim.ndel = segments[s0].data.length - result.length ; segments[s0].data = result ; selected = [s0,0] ; redraw(s0) ; drawsel(1) ; routeinfo() ; } else if(action=='editphoto') { ind = actions[nactions][3] ; photo = actions[nactions][5] ; if(actions[nactions][4]==null) segments[s0].data[s1].addphoto(photo) ; else segments[s0].data[s1].setphoto(ind,photo) ; } else if(action=='extra') for(selected=[s0,s1],i=3;i<actions[nactions].length;i++) { task = actions[nactions][i] ; a = segments[task[0]].data.slice(0,task[1]) ; b = task[2].slice(1,task[2].length-1) ; c = segments[task[0]].data.slice(task[1]) ; segments[task[0]].data = a.concat(b,c) ; } nactions += 1 ; if(nactions==actions.length) greyout(redobtn) ; blackout(undobtn) ; if(actiontype(actions[nactions-1][0])!=0) unsavedchanges.push(action) ; if(action=='editphoto'||action=='editlabel') walkto(s0,s1,0) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ---------------------------------- dl ----------------------------------- */ function dl() { var i,j,k,noalt,xmldoc,course,lap,datum,filename,blob,track,time,routelen ; var trackpoint,coursepoint,str,flag,clen=segments[0].data.length,tlast,di,dk ; var origlen,ndel,ano,photo,npix,thisuri,maxsep,sep,tdist,ttime,time,otime ; var distance = new Array(clen) , msecs = new Array(clen) ; var valid = new Array(clen) ; for(tlast=null,maxsep=noalt=tdist=ttime=i=flag=0;i<clen;otime=time,i++) { if(segments[0].data[i].h==null) noalt += 1 ; time = segments[0].data[i].t ; if(time!=null) time = time.getTime() ; if(tlast!=null&&time!=null&&time<tlast) flag = 1 ; // out of order if(time!=null) tlast = time ; if(i) { sep = dist(segments[0].data[i-1].pos,segments[0].data[i].pos) ; distance[i] = distance[i-1] + sep ; if(sep>maxsep) maxsep = sep ; if(time!=null&&otime!=null) { tdist += sep ; ttime += time - otime ; } } else distance[i] = 0 ; } routelen = distance[clen-1] ; if(maxsep>100&&!confirm('Some gaps between waypoints are >100m.\n'+ 'This will cause problems if used for navigation in a Garmin 500.\n'+ 'You can hit [OK] and I will proceed anyway, or\n'+ 'you can hit [Cancel] to interpolate extra points\n'+ '(recommended - go to Route Info under the cogwheel).')) return ; if(noalt&&!confirm(noalt+' waypoints have no associated altitudes.\n'+ 'You can hit [OK] and I will interpolate altitudes (not guaranteed), or\n'+ 'you can hit [Cancel] and try again later when the altitudes may be '+ 'available.')) return ; if(routetitle=='Untitled Route') filename = '' ; else { i = routetitle.indexOf(' ') ; if(i<=0) filename = routetitle ; else filename = routetitle.substring(0,i) ; } filename = prompt("Enter filename: ",filename) ; if(filename==null) return ; else if(routetitle=='Untitled Route') settitle(filename) ; i = filename.length ; if(i==0) { filename = 'route' ; i = 5 ; } if(filename.substring(i-4)!='.tcx') filename += '.tcx' ; if(noalt) interpolate() ; unsavedchanges = [] ; // interpolate/extrapolate times in msec for(i=0;i<clen;i++) { valid[i] = 1 ; if(segments[0].data[i].t==null) msecs[i] = null ; else msecs[i] = segments[0].data[i].t.getTime() ; } if(tdist==0||flag!=0) for(i=0;i<clen;i++) msecs[i] = distance[i] * 333 ; else for(i=0;i<clen;i=k) { for(;i<clen&&segments[0].data[i].t!=null;i++) ; // advance to null if(i==clen) break ; for(k=i+1;k<clen&&segments[0].data[k].t==null;k++) ; // advance to non-null for(j=i;j<k;j++) valid[i] = 0 ; if(i==0) for(time=msecs[k],j=i;j<k;j++) msecs[j] = time - (distance[k]-distance[j])*ttime/tdist ; else if(k==clen) for(time=msecs[i-1],j=i;j<clen;j++) msecs[j] = time + (distance[j]-distance[i-1])*ttime/tdist ; else for(j=i,di=distance[i-1],dk=distance[k];j<k;j++) msecs[j] = ( msecs[i-1]*(dk-distance[j]) + msecs[k]*(distance[j]-di) ) / (dk-di) ; } str = '<?xml version="1.0" encoding="UTF-8" standalone="no" ?>\n' + '<TrainingCenterDatabase xmlns="http://www.garmin.com/xmlschemas/' + 'TrainingCenterDatabase/v2"\n' + ' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"\n' + ' xsi:schemaLocation="http://www.garmin.com/' + 'xmlschemas/TrainingCenterDatabase/v2 ' + 'http://www.garmin.com/xmlschemas/TrainingCenterDatabasev2.xsd">\n' ; str += ' <Folders><Courses><CourseFolder Name="Courses">\n' ; str += ' <CourseNameRef><Id>'+routetitle+'</Id></CourseNameRef>\n' ; str += ' </CourseFolder></Courses></Folders>\n<Courses><Course>\n' ; str += ' <Name>'+routetitle+'</Name>\n <Lap>\n' + adddist(routelen) ; time = (msecs[clen-1]-msecs[0]) / 1000 ; str += ' <TotalTimeSeconds>' + time.toFixed(0) + '</TotalTimeSeconds>\n' ; str += addpos('Begin',segments[0].data[0].pos) ; str += addpos('End',segments[0].data[clen-1].pos) ; str += ' <Intensity>Active</Intensity>\n' + ' </Lap>\n <Track>\n' ; // loop over trackpoints for(npix=i=0;i<segments[0].data.length;i++) { str += ' <Trackpoint>\n' + addpos('',segments[0].data[i].pos) ; str += adddist(distance[i]) + addalt(segments[0].data[i].h) ; time = new Date(msecs[i]) ; str += ' <Time>' + time.toISOString() + '</Time>\n' ; if(segments[0].data[i].photo.length>0) { str += ' <Extensions><Photo>' ; for(k=0;k<segments[0].data[i].photo.length;k++) { if(k) str += ' ' ; str += segments[0].data[i].photo[k] ; } str += '</Photo></Extensions>\n' ; npix += segments[0].data[i].photo.length ; } if(valid[i]==0) str += ' <Extensions><ValidTime>False</ValidTime></Extensions>\n' ; str += ' <SensorState>Absent</SensorState>\n </Trackpoint>\n' ; } str += ' </Track>\n\n' ; // record optimisation and photo list for(ano=-1,ndel=origlen=0,i=loadno;i>=0;i=actions[i][3]) { ndel += actions[i][4].optim.ndel ; origlen += actions[i][4].optim.origlen ; if(actions[i][4].optim.parms!=null&&ano==-1) ano = i ; } if(ndel||(npix>0&&imginfo.status=='ready')) str += ' <Extensions>\n' ; if(ndel) { str += ' <Optimised from="'+origlen+'" to="'+(origlen-ndel) ; if(ano>=0) str += '" tol="'+actions[ano][4].optim.parms.tol.toFixed(0)+ '" maxsep="'+actions[ano][4].optim.parms.maxsep.toFixed(0)+ '" wppenalty="'+actions[ano][4].optim.parms.wppenalty.toFixed(0)+ '" vweight="'+actions[ano][4].optim.parms.vweight.toFixed(1) ; str += '"/>\n' } if(npix>0&&imginfo.status=='ready') { thisuri = imginfo.uri ; if(imginfo.type!='tcx') // vice 'uri' { thisuri = document.URL ; if((i=thisuri.lastIndexOf('?'))>=0) thisuri = thisuri.substring(0,i) ; thisuri = reluri(thisuri,imginfo.uri) ; } str += ' <PhotoList src="'+thisuri+'"/>\n' ; } if(ndel||(npix>0&&imginfo.status=='ready')) str += ' </Extensions>\n\n' ; // finally loop over coursepoints for(i=0;i<segments[0].data.length;i++) { datum = segments[0].data[i] ; if(datum.type==null) continue ; str += ' <CoursePoint><Name>'+datum.marker.title+'</Name>\n' ; str += ' <PointType>'+datum.type+'</PointType>\n' ; str += addpos('',segments[0].data[i].pos) + addalt(segments[0].data[i].h) ; time = new Date(msecs[i]) ; str += ' <Time>' + time.toISOString() + '</Time>\n </CoursePoint>\n' ; } str += '</Course></Courses></TrainingCenterDatabase>\n' ; blob = new Blob([str],{type: "text/plain;charset=utf-8"}) ; saveAs(blob,filename) ; } /* -------------------------------------------------------------------------- */ function addpos(tag,pos) { var str = ' <'+tag+'Position>\n <LatitudeDegrees>' ; str += pos.lat().toFixed(5)+'</LatitudeDegrees>\n <LongitudeDegrees>' ; str += pos.lng().toFixed(5)+'</LongitudeDegrees>\n </'+tag+'Position>\n' ; return str ; } function addalt(x) { return ' <AltitudeMeters>' + x.toFixed(0) + '</AltitudeMeters>\n' ; } function adddist(x) { return ' <DistanceMeters>' + x.toFixed(0) + '</DistanceMeters>\n' ; } /* ------------------------------ interpolate ------------------------------- */ function interpolate() { infowindow.close() ; var response = interpolatework() ; if(response.length>0) done([ 'interpolate' , response ]) ; } function interpolatework() { var i,j,k,response=[],x,y,distance,sum ; var a=segments[0].data,len=a.length ; for(i=0;i<len;i=j) { for(;i<len&&a[i].h!=null;i++) ; // advance to null for(j=i+1;j<len&&a[j].h==null;j++) response.push(j) ; // advance to non-null if(i==0) { for(y=a[j].h;i<j;i++) a[i].h = y ; continue ; } if(j==len) { for(x=a[i-1].h;i<j;i++) a[i].h = x ; continue ; } distance = new Array(1+j-i) ; for(sum=k=0;k<=j-i;k++) sum = distance[k] = sum + dist(a[i+k-1].pos,a[i+k].pos) ; for(x=a[i-1].h,y=a[j].h,k=0;k<j-i;k++) a[i+k].h = ( x*(sum-distance[i]) + y*distance[i] ) / sum ; } } /* -------------------------------------------------------------------------- */

Archived from pix.html

document.write ( '<style>body{background:black;margin:0;font-family:arial;color:silver}' + 'a{text-decoration:none;}a:link{color:#66aaaa}' + 'a:visited{color:#cc3388}a:active{color:#404040}' + 'td.nav{text-align:center;vertical-align:middle;' + 'font-size:16px;line-height:16px;width:16px;height:16px}' + '</style>') ; var thispage,full=0,nload=0,here,lind,rind,sind,bind,blink,R,pagetitle,retpage ; var nitem=[],thumbshape=[],maxthumb,windowprops,home,imagedir=null ; var llink,rlink,ulink,dlink,prevind ; // picopts is a constructor for an object containing the picture properties // both as separate fields and encoded as a string function picopts(a,b) // string or (size,fromnotes) { this.size = -1 ; this.fromnotes = 0 ; this.str = "" ; if(b==undefined) { this.str = a ; if(a.charAt(0)=='n') { if(retpage!=null) this.fromnotes = 1 ; a = a.substring(1) ; } if(a!="") { this.size = parseInt(a) ; if(isNaN(this.size)) this.size = -1 ; } } else { if(b==1&&retpage==null) b = 0 ; if(a>=0||b) { if(b) this.str = "n" ; if(a>=0) this.str += a ; } } } /* -------------------------------------------------------------------------- */
/*page*/ /* ---------------------- functions for handling fullscreen ----------------- */ function checkfull() { if(document.fullScreen||document.mozFullScreen||document.webkitIsFullScreen) return full = 1 ; else return full = 0 ; } /* -------------------------------------------------------------------------- */ function enterFullscreen() { if(document.documentElement.requestFullscreen) document.documentElement.requestFullscreen() ; else if(document.documentElement.mozRequestFullScreen) document.documentElement.mozRequestFullScreen() ; else if(document.documentElement.webkitRequestFullscreen) document.documentElement.webkitRequestFullscreen() ; else if(document.documentElement.msRequestFullscreen) document.documentElement.msRequestFullscreen() ; } /* ---------------------- event handler for screen resizes ------------------ */ // pixresize responds to a window resize as follows: // o. for a table view, if the number of columns can change, change it; // o. for an image view, if the new size permits a larger or smaller image, // enlarge or reduce; // o. or if the window is expanding or contracting but hasn't changed size // enough to change the desired image, anticipatively preload a larger or // smaller one. function pixresize() { var k,dir=null,opts=here.opts ; if(here.name!=null) { if(checkfull()!=here.full) k = -1 ; else k = getsize(here.ind) ; if(here.opts.size<0&&k!=here.size) { if(full) display(here.name,opts.str) ; else location.href = piclink(here.ind,opts.size,opts.fromnotes) ; } else if(opts.size>=0&&k==here.size) opts.size = -1 ; else if(opts.size<0&&windowprops!=null) { if( window.innerWidth>=windowprops.w && window.innerHeight>=windowprops.h ) dir = 1 ; else if( window.innerWidth<=windowprops.w && window.innerHeight<=windowprops.h ) dir = -1 ; if(dir!=null) { windowprops = null ; if((k=rescale(here.size,dir))!=null) preload(here.ind,k) ; } } } else if(checkfull()!=here.full||here.size!=(k=getncol())) { if(full) tabulate() ; else location.href = thispage ; } } /* -------------------------------------------------------------------------- */
/*page*/ /* -- sparepix finds the margins left if list[ind] is displayed at usesize -- */ function sparepix(ind,sizeno) { var shape=imgsize(list[ind].shape,sizeno),w=shape[0],h=shape[1],r ; r = Math.exp((Math.log(sizes[sizeno].scale)-R)/3) ; h += headline(r) + 8 ; if(list[ind].caption!=undefined) h += capline(r) + 2 ; r = [ window.innerHeight-48-h , window.innerHeight-h ] ; if(window.innerWidth-w<r[0]) r[0] = window.innerWidth-w ; if(window.innerWidth-w-48<r[1]) r[1] = window.innerWidth-w-48 ; return r ; } /* ------- imgsize adjusts shape according to the usesize scale factor ------ */ function imgsize(shape,sizeno) { return [ Math.floor(0.5 + shape[0]*sizes[sizeno].scale/sizes[0].scale) , Math.floor(0.5 + shape[1]*sizes[sizeno].scale/sizes[0].scale) ] ; } /* ------- getsize finds the largest image size which fits the screen ------- */ function getsize(ind) { var i,ibest,ismall,spare ; for(ismall=ibest=null,i=0;i<sizes.length;i++) if(sizes[i].type==undefined) { if(ismall==null||sizes[i].scale<sizes[ismall].scale) ismall = i ; spare = sparepix(ind,i) ; if(spare[0]>=0||spare[1]>=0) if(ibest==null||sizes[i].scale>sizes[ibest].scale) ibest = i ; } if(ibest==null) return ismall ; else return ibest ; } /* ----- getncol finds the number of table columns which fit the screen ----- */ function getncol() { var maxcol,ncol,k,ind ; // logic suggests 19 spare pix but 21 works maxcol = Math.floor((window.innerWidth)/(maxthumb+21)) ; if(maxcol<1) maxcol = 1 ; for(ncol=ind=0;ind<nitem.length;ind++) { k = Math.ceil(nitem[ind]/Math.ceil(nitem[ind]/maxcol)) ; if(k>ncol) ncol = k ; } return ncol ; } /* ------------ rescale finds the next larger/next smaller image ------------ */ function rescale(sizeno,dir) { var i,rind ; if(dir<0) { for(rind=null,i=0;i<sizes.length;i++) if(sizes[i].type==undefined&&sizes[i].scale<sizes[sizeno].scale) if(rind==null||sizes[i].scale>sizes[rind].scale) rind = i ; } else for(rind=null,i=0;i<sizes.length;i++) if(sizes[i].type==undefined&&sizes[i].scale>sizes[sizeno].scale) if(rind==null||sizes[i].scale<sizes[rind].scale) rind = i ; return rind ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ------------- utilities to determine header and caption sizes ------------ */ function headsize(r) { return Math.floor(0.5+16*r) ; } function capsize(r) { return Math.floor(0.5+13*r) ; } function headline(r) { return Math.floor(0.5+16*1.25*r) ; } function capline(r) { return Math.floor(0.5+13*1.25*r) ; } /* ---------------------- construct the jpg path name ----------------------- */ function jpg(ind,sizeno) { var jpgname = list[ind].name + sizes[sizeno].suffix + '.jpg' ; if(imagedir==null) return jpgname ; else return imagedir + '/' + jpgname ; } /* ----------------- preload list[ind] for the given size ------------------- */ function preload(ind,sizeno) { var img = new Image(),s ; if(sizeno<0) sizeno = getsize(ind) ; if((s=srcset(ind,sizeno))!='') img.srcset = s ; img.src = uu = jpg(ind,sizeno) ; // console.log(img.src+(s==""?"":' ['+s+']')) ; } /* ------------------- generate the srcset for an image --------------------- */ function srcset(ind,sizeno) { var i,s="" ; for(i=0;i<sizes.length;i++) if(sizes[i].type==undefined) if(sizes[i].scale>sizes[sizeno].scale) s += jpg(ind,i) + ' ' + (sizes[i].scale/sizes[sizeno].scale).toFixed(1) + "x, " ; if(s!='') s = s.substring(0,s.length-2) ; return s ; } /* ------------------- construct a link to an image page -------------------- */ function piclink(ind,sizeno,fromnotes) { if(sizeno>=0&&sizeno==getsize(ind)) sizeno = -1 ; var opts = new picopts(sizeno,fromnotes).str ; if(full) return 'javascript:display("' + list[ind].name + (opts==''?'")':'","'+opts+'")') ; else return thispage + '?' + list[ind].name + (opts==''?'':('+'+opts)) ; } /* ---------------- create a table cell containing an image ----------------- */ function makeimgcell(ind,sizeno,prefetch,shape) { var td=document.createElement('td'),img=document.createElement('img'),s ; td.setAttribute("align",'center') ; img.setAttribute("width",shape[0]) ; img.setAttribute("height",shape[1]) ; if((s=srcset(ind,sizeno))!='') img.setAttribute("srcset",s) ; img.setAttribute("src",jpg(ind,sizeno)) ; if(prefetch!=null) img.onload = function() { preload(prefetch,-1) ; } ; td.appendChild(img) ; return td ; } /* -------------------------------------------------------------------------- */
/*page*/ /* ---------------- create a cell containing an image title ----------------- */ function maketitle(title,fontsize,lineheight,copt) { var itd,s ; itd = document.createElement('td') ; s = 'font-size:' + fontsize + 'px;padding-top:' + (copt?2:6) + 'px;' ; s += 'line-height' + lineheight + 'px;' + (copt?"":'padding-bottom:2px') ; itd.setAttribute('style',s) ; itd.appendChild(document.createTextNode(title)) ; return itd ; } /* -------------------- create links line of the table page ----------------- */ function linkp() { var p,a,span ; p = document.createElement("p") ; p.setAttribute('style',"text-align:center;font-size:100%;margin:6px") ; if(retpage!=null) { a = document.createElement('a') ; a.setAttribute('href',list[retpage].retpage+'.html') ; a.setAttribute('style',"font-weight:normal") ; a.appendChild(document.createTextNode('notes')) ; p.appendChild(a) ; p.appendChild(document.createTextNode(' : ')) ; } if(home!=undefined&&home!=null) { a = document.createElement('a') ; a.setAttribute('href',home) ; a.setAttribute('style',"font-weight:normal") ; a.appendChild(document.createTextNode('home')) ; p.appendChild(a) ; } if(full==0) if ( document.documentElement.requestFullscreen || document.documentElement.mozRequestFullScreen || document.documentElement.webkitRequestFullscreen || document.documentElement.msRequestFullscreen ) { p.appendChild(document.createTextNode(' : ')) ; a = document.createElement('a') ; a.setAttribute('style',"font-weight:normal") ; a.setAttribute('href','javascript:{}') ; a.setAttribute('onclick','enterFullscreen()') ; a.appendChild(document.createTextNode('full screen')) ; p.appendChild(a) ; span = document.createElement('span') ; span.setAttribute('style',"color:gray") ; span.appendChild(document.createTextNode(' [f key] ')) ; p.appendChild(span) ; } return p ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------------------------------------------------- */ function display(name,optstr) { var id,titlestr,body,p,table,tr,td,itable,itr,itd,a,img,nobr,title,imgshape ; var newbig,w,h,i,k,ind,spare,r,opts=new picopts(optstr),usesize=opts.size ; var prefetch,len=list.length ; for(ind=len-1; ind>=0&&(list[ind].name==undefined||list[ind].name!=name); ind--) ; r = getsize(ind) ; if(usesize<0) usesize = r ; else if(usesize==r) opts.size = -1 ; here = { name:name , opts:opts , ind:ind , size:usesize , full:full } ; windowprops = { w:window.innerWidth , h:window.innerHeight } ; fullforce = 0 ; body = document.getElementsByTagName("body")[0] ; while(body.firstChild) body.removeChild(body.firstChild) ; title = document.getElementsByTagName("title")[0] ; while(title.firstChild) title.removeChild(title.firstChild) ; imgshape = imgsize(list[ind].shape,usesize) ; spare = sparepix(ind,usesize) ; portrait = ( spare[0] < spare[1] ) ; titlestr = document.createTextNode(list[ind].title) ; title.appendChild(titlestr) ; table = document.createElement('table') ; table.setAttribute('width','100%') ; table.setAttribute('cellspacing',0) ; table.setAttribute('cellpadding',0) ; tr = document.createElement('tr') ; td = document.createElement('td') ; if(portrait) { td.setAttribute('valign','top') ; td.setAttribute('rowspan',3) ; td.setAttribute('width',"49%") ; } // inner table for the navigation icons itable = document.createElement('table') ; itable.setAttribute('cellpadding',0) ; itable.setAttribute('cellspacing',0) ; /* ------------------------------------------------------------------------ */
/*page*/ /* --------------------------- the navigation icons ----------------------- */ // the '<' link itr = document.createElement('tr') ; itd = document.createElement('td') ; itd.setAttribute("style","text-align:left;font-size:20px;width:16px") ; itd.setAttribute("rowspan",3) ; for(lind=ind-1; lind>=0&&(list[lind].name==undefined||list[lind].display=='none'); lind--) ; if(lind<0) itd.appendChild(document.createTextNode(' ')) ; else { llink = piclink(lind,-1,opts.fromnotes) ; a = document.createElement('a') ; a.setAttribute('href',llink) ; a.setAttribute("title","prev [\u2190 key]") ; a.appendChild(document.createTextNode('<')) ; itd.appendChild(a) ; } itr.appendChild(itd) ; // the enlarge link itd = document.createElement('td') ; itd.setAttribute("class","nav") ; bind = rescale(usesize,1) ; if(bind==null) itd.appendChild(document.createTextNode(' ')) ; else { a = document.createElement('a') ; ulink = piclink(ind,bind,opts.fromnotes) ; a.setAttribute('href',ulink) ; a.setAttribute("title","enlarge [\u2191 key]") ; a.appendChild(document.createTextNode('\u2295')) ; itd.appendChild(a) ; } itr.appendChild(itd) ; itable.appendChild(itr) ; // the '>' link itd = document.createElement('td') ; itd.setAttribute("style","text-align:right;font-size:20px;width:16px") ; itd.setAttribute("rowspan",3) ; for(rind=ind+1; rind<len&&(list[rind].name==undefined||list[rind].display=='none'); rind++) ; if(rind==len) itd.appendChild(document.createTextNode(' ')) ; else { rlink = piclink(rind,-1,opts.fromnotes) ; a = document.createElement('a') ; a.setAttribute('href',rlink) ; a.setAttribute("title","next [\u2190 key]") ; a.appendChild(document.createTextNode('>')) ; itd.appendChild(a) ; } itr.appendChild(itd) ; itable.appendChild(itr) ; /* ------------------------------------------------------------------------ */
/*page*/ /* ------------------------------------------------------------------------ */ // the return link itr = document.createElement('tr') ; itd = document.createElement('td') ; itd.setAttribute("class","nav") ; a = document.createElement('a') ; if(opts.fromnotes==0) { if(full) a.setAttribute("href",'javascript:tabulate()') ; else { blink = thispage ; a.setAttribute("href",blink) ; } a.setAttribute("title","back to table [\u21b5 key]") ; } else { for(i=ind;i>=0&&list[i].retpage==undefined;i--) ; if(i<0) alert('no return page for '+list[ind].name) ; blink = list[i].retpage+'.html#' ; for(k=ind;k>=i&&list[k].retid==undefined;k--) ; if(k>=i) blink += list[k].retid ; a.setAttribute("href",blink) ; a.setAttribute("title","back to notes [\u21b5 key]") ; } a.appendChild(document.createTextNode('\u21b5')) ; itd.appendChild(a) ; itr.appendChild(itd) ; itable.appendChild(itr) ; // the reduce link itr = document.createElement('tr') ; itd = document.createElement('td') ; itd.setAttribute("class","nav") ; sind = rescale(usesize,-1) ; if(sind==null) itd.appendChild(document.createTextNode(' ')) ; else { a = document.createElement('a') ; dlink = piclink(ind,sind,opts.fromnotes) ; a.setAttribute('href',dlink) ; a.setAttribute("title","reduce [\u2193 key]") ; a.appendChild(document.createTextNode('\u2296')) ; itd.appendChild(a) ; } itr.appendChild(itd) ; itable.appendChild(itr) ; td.appendChild(itable) ; tr.appendChild(td) ; // decide which image if any to prefetch if(prevind!=null&&prevind>ind&&lind>=0) k = lind ; else if((prevind==null||prevind<=ind)&&rind<len) k = rind ; else k = null ; if(k==null) prefetch = null ; else prefetch = k ; /* ---------------------------- the text and image ------------------------ */ r = Math.exp((Math.log(sizes[usesize].scale)-R)/3) ; if(portrait==0) // landscape layout (nav above image) { table.appendChild(tr) ; tr = document.createElement('tr') ; td = document.createElement('td') ; td.setAttribute("rowspan",3) ; td.setAttribute("width",'49%') ; tr.appendChild(td) ; } /* ------------------------------------------------------------------------ */
/*page*/ /* ------------------------------------------------------------------------ */ // title td = maketitle(list[ind].title,headsize(r),headline(r),0) ; td.setAttribute("colspan",2) ; tr.appendChild(td) ; table.appendChild(tr) ; // image tr = document.createElement('tr') ; tr.appendChild(makeimgcell(ind,usesize,prefetch,imgshape)) ; td = document.createElement('td') ; td.setAttribute("width",'49%') ; tr.appendChild(td) ; table.appendChild(tr) ; // caption if(list[ind].caption!=undefined) { tr = document.createElement('tr') ; td = maketitle(list[ind].caption,capsize(r),capline(r),1) ; td.setAttribute("colspan",2) ; tr.appendChild(td) ; table.appendChild(tr) ; } body.appendChild(table) ; } /* ------------------------ navigate using the arrow keys ------------------- */ function navigate(e) { var n = here.opts.fromnotes ; if(e.keyCode==37&&lind>=0) // left arrow key { e.preventDefault() ; if(full==0) location.href= llink ; else { prevind = lind+1 ; display(list[lind].name,new picopts(-1,n).str) ; } } else if(e.keyCode==39&&rind<list.length) // right arrow key { e.preventDefault() ; if(full==0) location.href= rlink ; else { prevind = rind-1 ; display(list[rind].name,new picopts(-1,n).str) ; } } else if(e.keyCode==13&&here.name!=null) // return { e.preventDefault() ; if(full&&n==0) tabulate() ; else location.href = blink ; } else if(e.keyCode==40&&sind!=null) // down arrow key (reduce) { e.preventDefault() ; if(sind==getsize(here.ind)) sind = -1 ; if(full) display(here.name,new picopts(sind,t).str) ; else location.href= dlink ; } else if(e.keyCode==38&&bind!=null) // up arrow key (enlarge) { e.preventDefault() ; if(bind==getsize(here.ind)) bind = -1 ; if(full) display(here.name,new picopts(bind,t).str) ; else location.href= ulink ; } else if(e.keyCode==70&&full==0) // 'f' (full screen) { e.preventDefault() ; enterFullscreen() ; full = 1 ; } } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------------------------------------------------- */ function tabulate() { var ind,ncol,p,body,table,tr,td,a,img,trflag,k,s,opts,thumbind ; var prefetch,nimg,padflag,k,num ; rind = list.length ; lind = -1 ; sind = bind = null ; fullforce = 0 ; body = document.getElementsByTagName("body")[0] ; while(body.firstChild) body.removeChild(body.firstChild) ; ncol = getncol() ; opts = { size:-1 , fromnotes:0 } ; here = { name:null , opts:opts , ind:null , size:ncol , full:full } ; // prefetch will be performed when nimg thumbs have been loaded for(nimg=ind=0;ind<list.length;ind++) if(list[ind].name!=undefined&&list[ind].display!='none') nimg += 1 ; for(thumbind=1;thumbind<sizes.length;thumbind++) if(sizes[thumbind].type=='thumb') break ; if(thumbind==sizes.length) { alert("no thumb type in sizes") ; throw '' ; } p = document.createElement("p") ; p.setAttribute('style',"text-align:center;font-size:140%;margin:2px 0 6px") ; p.appendChild(document.createTextNode(pagetitle)) ; body.appendChild(p) ; body.appendChild(linkp()) ; table = document.createElement('table') ; table.setAttribute('cellspacing','0') ; table.setAttribute('cellpadding','0') ; table.setAttribute('align','center') ; for(s='',ind=0;s==''&&ind<list.length;ind++) if(list[ind].name==undefined) s = "border-bottom:1px solid #444;padding-bottom:4px;" ; table.setAttribute('style',s+'margin:0px auto') ; for(prefetch=null,nload=trflag=colno=ind=0;ind<list.length;ind++) if(list[ind].display!='none') { if(0==colno%ncol||list[ind].name==undefined) { if(trflag>0) table.appendChild(tr) ; tr = document.createElement('tr') ; trflag = 0 ; } td = document.createElement('td') ; if(list[ind].name==undefined) { td.setAttribute("style","border-top:1px solid #444") ; td.setAttribute('align','left') ; td.setAttribute('colspan',ncol) ; p = document.createElement("p") ; p.setAttribute("style","font-size:110%;padding-top:6px") ; if(list[ind].gps==undefined) p.appendChild(document.createTextNode(list[ind].title)) ; else { p.appendChild(document.createTextNode(list[ind].title+' : ')) ; a = document.createElement('a') ; a.setAttribute('href',list[ind].gps) ; a.setAttribute('style','font-size:90%;font-weight:normal') ; a.appendChild(document.createTextNode('[GPS track]')) ; p.appendChild(a) ; } td.appendChild(p) ; tr.appendChild(td) ; table.appendChild(tr) ; tr = document.createElement('tr') ; colno = trflag = 0 ; continue ; } lind = ind ; llink = piclink(lind,-1,0) ; if(rind==list.length) { rind = ind ; rlink = piclink(rind,-1,0) ; } // extra padding at the bottom before a title row if(0==colno%ncol) { padflag = 4 ; for(num=0,k=ind;k<list.length&&num<ncol&&list[k].name!=undefined;k++) if(list[k].display!='none') num += 1 ; if(k<list.length&&num<=ncol) { padflag = 8 ; if(ncol>5) padflag += 2*(ncol-5) ; } } td.setAttribute('align','center') ; if(list[ind].display=='|'&&colno%ncol>0) td.setAttribute('style','border-left:1px solid #444') ; a = document.createElement('a') ; a.setAttribute('href',piclink(ind,-1,0)) ; a.setAttribute("class","box") ; a.setAttribute("title",list[ind].title) ; img = document.createElement('img') ; img.setAttribute('src',jpg(ind,thumbind)) ; img.setAttribute('width',list[ind].thumbshape[0]) ; img.setAttribute('height',list[ind].thumbshape[1]) ; img.setAttribute('border',1) ; img.onload = function() { nload += 1 ; if(nload==nimg&&prefetch!=null) preload(prefetch,-1) ; } ; // vertical bar: margin is t-r-b-l or t-lr-b if(colno%ncol>0&&list[ind].display!='|') img.setAttribute('style',"padding:4px;margin:4px 4px "+padflag+"px 5px") ; else img.setAttribute('style',"padding:4px;margin:4px 4px "+padflag+"px") ; a.appendChild(img) ; td.appendChild(a) ; tr.appendChild(td) ; trflag = 1 ; if(prefetch==null) prefetch = ind ; colno += 1 ; } table.appendChild(tr) ; body.appendChild(table) ; body.appendChild(linkp()) ; } /* -------------------------------------------------------------------------- */
/*page*/ /* -------------------------------------------------------------------------- */ function pix(h) { var ind,query,opts="",k,r,prev,fullpage=location.href ; thispage = fullpage ; home = h ; pagetitle = document.getElementsByTagName("title")[0].textContent ; prevind = null ; ind = thispage.lastIndexOf('/') ; if(ind>=0) thispage = thispage.substring(ind+1) ; // R is the mean log scale factor for(R=k=ind=0;ind<sizes.length;ind++) if(sizes[ind].type==undefined) { R += Math.log(sizes[ind].scale) ; k += 1 ; } R /= k ; // set up the array nitem containing the number of items in each block for(nitem=[],ind=-1;ind<list.length;ind=k) { for(k=ind+1;k<list.length&&list[k].name!=undefined;k++) ; if(k>ind+1) nitem.push(k-(ind+1)) ; } // prepare to set the thumb shapes for(ind=0;ind<sizes.length&&sizes[ind].type!='thumb';ind++) ; if(ind==sizes.length) { alert('no sizes entry of type "thumb"') ; throw '' ; } if(thumbshape.length!=2) { if(sizes[ind].scale>0) r = sizes[ind].scale / sizes[0].scale ; else { alert('thumbs have size '+sizes[ind].scale) ; throw '' ; } } // set thumbshapes and set maxthumb to the width of the widest thumbnail for(maxthumb=ind=0;ind<list.length;ind++) if(list[ind].name!=undefined) { if(list[ind].thumbshape==undefined) { if(thumbshape.length==2) list[ind].thumbshape = thumbshape ; else for(list[ind].thumbshape=[0,0],k=0;k<2;k++) list[ind].thumbshape[k] = Math.floor(0.5+list[ind].shape[k]*r) ; } if(list[ind].thumbshape[0]>maxthumb) maxthumb = list[ind].thumbshape[0] ; } query = null ; ind = thispage.indexOf('?') ; if(ind>=0) { query = thispage.substring(ind+1) ; // 'garden+t2' thispage = thispage.substring(0,ind) ; fullpage = fullpage.substring(0,fullpage.length-1-query.length) ; ind = query.indexOf('+') ; if(ind>=0) { opts = query.substring(ind+1) ; query = query.substring(0,ind) ; } } else { query = null ; ind = -1 ; } if(query!=null) for(ind=0;ind<list.length&&(list[ind].name!=query);ind++) ; for(k=0; k<list.length&&(list[k].name==undefined||list[k].retpage==undefined); k++) ; if(k<list.length) retpage = k ; else retpage = null ; if(ind>=0&&ind<list.length) // return to table if there is no retpage { opts = new picopts(opts) ; if(opts.size<0||opts.size>=sizes.length||sizes[opts.size].type!=undefined) opts.size = -1 ; if(opts.fromnotes==1&&retpage==null) opts.fromnotes = 0 ; opts = new picopts(opts.size,opts.fromnotes) ; // all the following code is finding whether we're stepping backwards k = document.referrer.lastIndexOf('/') ; if(k>=0) { prev = document.referrer.substring(k+1) ; k = prev.indexOf('?') ; } if(k>=0) { prev = prev.substring(k+1) ; k = prev.indexOf('+') ; if(k>=0) prev = prev.substring(0,k) ; for(k=0;k<list.length&&list[k].name!=prev;k++) ; if(k<list.length) prevind = k ; } else if(fullpage==document.referrer) { for(k=ind+1;k<list.length&&list[ind].name==undefined;k++) ; if(k==list.length) prevind = list.length ; } } window.onresize = pixresize ; document.onkeydown = navigate ; document.addEventListener('touchstart',startswipe,false) ; document.addEventListener('touchend',endswipe,false) ; if(ind>=0&&ind<list.length) display(query,opts.str) ; else tabulate() ; } /* ---------------------------------- swipes -------------------------------- */ var xloc=null,yloc=null,fingersep=null ; function startswipe(e) { var x0=e.touches[0].clientX,y0=e.touches[0].clientY,x1,y1 ; if(e.touches.length==1) { xloc = x0 ; yloc = y0 ; } else if(e.touches.length==2) { x1 = e.touches[1].clientX ; y1 = e.touches[1].clientY ; fingersep = Math.sqrt((x1-x0)*(x1-x0)+(y1-y0)*(y1-y0)) ; } } function endswipe(e) { var x0=e.touches[0].clientX,y0=e.touches[0].clientY,x1,y1 ; if(e.touches.length==1) { if(xloc==null||yloc==null) return ; x0 -= xloc ; y0 -= yloc ; xloc = yloc = null ; if(Math.abs(x0)>100&&Math.abs(y0)<100) { if(x0>0) navigate({keyCode:39}) ; else navigate({keyCode:37}) ; } } else if(e.touches.length==2) { if(fingersep==null) return ; x1 = e.touches[1].clientX ; y1 = e.touches[1].clientY ; x1 = Math.sqrt((x1-x0)*(x1-x0)+(y1-y0)*(y1-y0)) ; x0 = fingersep ; fingersep = null ; if(x1-x0>50) navigate({keyCode:38}) ; else if(x0-x1>50) navigate({keyCode:40}) ; } }

Archived from sloping.html

#include "memory.h" #include <math.h> #include "random.h" #include "ij.h" #include "slopingbw.h" double uoamax(double (*func)(double*),double *x,int n, double xinc,double tol,int maxiter) ; int uoacond() ; double cholsqrt(double **a,double **b,int n) ; static slopingstats stats ; static int m,mdash,ndash,*ni,ncall ; /*--------------------------------------------------------------------------- */ static void ranvec(double *x,int m,double *sigma,double **rot,double *mu) { int i,j ; double q ; for(i=0;i<m;i++) x[i] = mu[i] ; for(i=0;i<m;i++) { q = sigma[i] * gaussv() ; for(j=0;j<m;j++) x[j] += q * rot[i][j] ; } } /*--------------------------------------------------------------------------- */ static int vmatrix(double *v,double **z0,int m) { int i,j,k,t ; double q,**z=matrix(m,m) ; for(t=i=0;i<m;i++) for(j=0;j<=i;j++,t++) z[i][j] = v[t] ; for(i=0;i<m;i++) for(k=0;k<=i;k++) { for(q=j=0;j<=k;j++) q += z[i][j] * z[k][j] ; z0[i][k] = z0[k][i] = q ; } freematrix(z) ; return t ; } static void vvector(double *mu,double **zb,double **zw,int m,double *v) { int i,j,t,mdash=m+1 ; double **z=matrix(m,m) ; for(t=0;t<mdash;t++) v[t] = mu[t] ; cholsqrt(zw,z,m) ; for(j=0;j<m;j++) for(i=0;i<=j;i++) v[t++] = z[i][j] ; freematrix(z) ; z = matrix(mdash,mdash) ; cholsqrt(zb,z,mdash) ; for(j=0;j<mdash;j++) for(i=0;i<=j;i++) v[t++] = z[i][j] ; } /*--------------------------------------------------------------------------- */
/*page*/ /*--------------------------------------------------------------------------- */ static double f(double *v) { int i,t ; double q,*mu=vector(mdash),**zw=matrix(m,m),**zb=matrix(mdash,mdash) ; for(t=0;t<mdash;t++) mu[t] = v[t] ; t += vmatrix(v+t,zw,m) ; t += vmatrix(v+t,zb,mdash) ; q = slopingbw(m,ndash,stats,mu,zb,zw,0,0,0,0) ; free(mu) ; freematrix(zw,zb) ; ncall += 1 ; return q ; } /*--------------------------------------------------------------------------- */ int main(int argc,char **argv) { m = argc>1?atoi(argv[1]):5 ; mdash = m + 1 ; if(argc>2) ranset(atoi(argv[2])) ; ndash = 20 ; ni = ivector(ndash) ; stats = slopingstats(m) ; int groupno,i,j,k,t,nneg,iter,targ,ndot,cond ; int vlen = mdash + (mdash*(mdash+1))/2 + (m*(m+1))/2 ; double ***y=(double ***) cjcalloc(ndash,sizeof(double**)) ; double q,q0,*eb=vector(mdash),*ew=vector(m),**xs=matrix(ndash,mdash),scale ; double **rotw=matrix(m,m),**rotb=matrix(mdash,mdash),*mu=vector(mdash) ; double *mubw=vector(mdash),**zbbw=matrix(mdash,mdash),**zwbw=matrix(m,m) ; double *muml=vector(mdash),**zbml=matrix(mdash,mdash),**zwml=matrix(m,m) ; double *v=vector(vlen),**zw=matrix(m,m),**zb=matrix(mdash,mdash) ; double *a=vector(m),qpdf,qmlpdf,q0pdf,qbwpdf ; // generate group sizes for(ndot=t=0;t<ndash/2;t++) ndot += ( ni[t] = 1 ) ; for(;t<ndash;t++) ndot += ( ni[t] = 3 ) ; isort(ni,ndash) ; scale = ndot / ndash ; // avge group size printf("%d groups with avge size = %.1f\n",ndash,scale) ; // generate random global parameters ranrot(rotw,m) ; for(i=0;i<m;i++) ew[i] = 1/(20*sqrt(ranf())) ; for(i=0;i<m;i++) for(k=0;k<m;k++) for(zw[i][k]=j=0;j<m;j++) zw[i][k] += rotw[j][i] * rotw[j][k] * ew[j] * ew[j] ; for(i=0;i<mdash;i++) mu[i] = gaussv() ; ranrot(rotb,mdash) ; for(i=0;i<mdash;i++) eb[i] = 1/(20*sqrt(ranf()/scale)) ; for(i=0;i<mdash;i++) for(k=0;k<mdash;k++) for(zb[i][k]=j=0;j<mdash;j++) zb[i][k] += rotb[j][i] * rotb[j][k] * eb[j] * eb[j] ; /*------------------------------------------------------------------------- */
/*page*/ /*------------------------------------------------------------------------- */ // generate group (x,s) vectors and observations for(groupno=0;groupno<ndash;groupno++) { ranvec(xs[groupno],mdash,eb,rotb,mu) ; xs[groupno][m] /= scale ; y[groupno] = matrix(ni[groupno],m) ; for(t=0;t<ni[groupno];t++) { for(i=0;i<m;i++) a[i] = xs[groupno][i] + t*xs[groupno][m] ; ranvec(y[groupno][t],m,ew,rotw,a) ; } stats.take(y[groupno],ni[groupno]) ; } free(ni) ; stats.sort() ; mu[m] /= scale ; for(i=0;i<mdash;i++) { zb[i][m] /= scale ; zb[m][i] /= scale ; } qpdf = slopingbw(m,ndash,stats,mu,zb,zw,0,0,0,0) ; /*----------------------------------- ML ---------------------------------- */ for(i=0;i<m;i++) for(j=0;j<m;j++) zwml[i][j] = 0 ; for(i=0;i<m;i++) { muml[i] = 0 ; zwml[i][i] = 1 ; } for(i=0;i<mdash;i++) for(j=0;j<mdash;j++) zbml[i][j] = 0 ; for(i=0;i<mdash;i++) zbml[i][i] = 1 ; vvector(muml,zbml,zwml,m,v) ; ncall = 0 ; qmlpdf = uoamax(f,v,vlen,0.1,1e-4,500000) ; cond = uoacond() ; if(cond!=0) printf("*** cond=%d ***: ",cond) ; printf("%d function evals\n",ncall) ; for(t=0;t<mdash;t++) muml[t] = v[t] ; t += vmatrix(v+t,zwml,m) ; t += vmatrix(v+t,zbml,mdash) ; /*------------------------------- Baum Welch ------------------------------ */ for(i=0;i<mdash;i++) for(j=0;j<mdash;j++) zbbw[i][j] = 0 ; for(i=0;i<mdash;i++) { mubw[i] = 0 ; zbbw[i][i] = 1 ; } for(i=0;i<m;i++) for(j=0;j<m;j++) zwbw[i][j] = 0 ; for(i=0;i<m;i++) zwbw[i][i] = 1 ; for(targ=10,iter=5;iter<1000;q=qbwpdf,iter+=5) { qbwpdf = slopingbw(m,ndash,stats,mubw,zbbw,zwbw,mubw,zbbw,zwbw,5) ; if(iter==targ) { printf("logpdf=%.6f (%d iterations)\n",qbwpdf,iter) ; targ *= 10 ; } if(iter==5||qbwpdf-q>q0) q0 = qbwpdf - q ; if(iter>1&&qbwpdf-q<q0/1000) break ; } printf("%d Baum-Welch iterations\n",iter) ; stats.release() ; /*---------------------------- print estimates ---------------------------- */ printf("pdf : %.4f\n",qpdf) ; printf("pdf (ML) : %.4f\n",qmlpdf) ; printf("pdf (BW) : %.4f\n\n",qbwpdf) ; printf("mu:\n") ; for(i=0;i<m;i++) printf("%8.3f",mu[i]) ; printf("%8.3f\n\n",100*mu[m]) ; printf("mu MLE:\n") ; for(i=0;i<m;i++) printf("%8.3f",muml[i]) ; printf("%8.3f\n\n",100*muml[m]) ; printf("mu Baum-Welch estimator:\n") ; for(i=0;i<m;i++) printf("%8.3f",mubw[i]) ; printf("%8.3f\n",100*mubw[m]) ; printf("\n-----------------------------------------\n\n") ; printf("zeta[W]*1000:\n") ; for(i=0;i<m;i++) { for(j=0;j<m;j++) printf("%8.3f",1000*zw[i][j]) ; printf("\n") ; } printf("\n") ; printf("zeta[W] MLE:\n") ; for(i=0;i<m;i++) { for(j=0;j<m;j++) printf("%8.3f",1000*zwml[i][j]) ; printf("\n") ; } printf("\n") ; printf("zeta[W] Baum-Welch:\n") ; for(i=0;i<m;i++) { for(j=0;j<m;j++) printf("%8.3f",1000*zwbw[i][j]) ; printf("\n") ; } printf("\n-----------------------------------------\n\n") ; printf("zeta[B]*10:\n") ; for(i=0;i<mdash;i++) { for(j=0;j<mdash;j++) printf("%8.3f",10*zb[i][j]) ; printf("\n") ; } printf("\n") ; printf("zeta[B] MLE:\n") ; for(i=0;i<mdash;i++) { for(j=0;j<mdash;j++) printf("%8.3f",10*zbml[i][j]) ; printf("\n") ; } printf("\n") ; printf("zeta[B] Baum-Welch:\n") ; for(i=0;i<mdash;i++) { for(j=0;j<mdash;j++) printf("%8.3f",10*zbbw[i][j]) ; printf("\n") ; } printf("\n") ; }

Archived from sloping.html

double slopingbw(int m,int n,double **S,double **ybar,double **ybardash, int *ni,double *mu,double **zb,double **zw, double *muhat,double **zbhat,double **zwhat,int niter) ; struct slopingstats { int m,N,len,*n ; double **S,**ybar,**ybardash ; slopingstats() { N = len = m = 0 ; n = 0 ; S = ybar = ybardash = 0 ; } slopingstats(int p) { this[0] = slopingstats() ; m = p ; S = matrix(m+1,m+1) ; } void take(double **y,int ni) { if(ni<=0) return ; double q=1.0/ni ; int t,j,k ; if(len==N) { len += 20 + len ; n = ivector(n,len) ; ybar = matrix(ybar,len,m) ; ybardash = matrix(ybardash,len,m) ; for(t=N;t<len;t++) { n[t] = 0 ; for(j=0;j<m;j++) ybar[t][j] = ybardash[t][j] = 0 ; } } for(t=0;t<ni;t++) for(j=0;j<m;j++) ybar[N][j] += q * y[t][j] ; if(ni>1) for(q=2/(ni*(ni-1.0)),t=0;t<ni;t++) for(j=0;j<m;j++) ybardash[N][j] += q * t * y[t][j] ; for(j=0;j<m;j++) for(k=0;k<m;k++) for(t=0;t<ni;t++) S[j][k] += (y[t][j]-ybar[N][j]) * (y[t][k]-ybar[N][k]) ; n[N++] = ni ; } void release() { free(n) ; freematrix(S,ybar,ybardash) ; this[0] = slopingstats() ; } void sort() { int t,j ; double **x=matrix(N,m) ; ij *list = ijvector(n,N) ; ijsort(list,N) ; for(t=0;t<N;t++) for(j=0;j<m;j++) x[t][j] = ybar[list[t].j][j] ; for(t=0;t<N;t++) for(j=0;j<m;j++) ybar[t][j] = ybardash[list[t].j][j] ; for(t=0;t<N;t++) n[t] = list[list[t].j].i ; freematrix(ybardash) ; ybardash = ybar ; ybar = x ; free(list) ; } } ; double slopingbw(int m,int n,slopingstats s,double *mu,double **zb,double **zw, double *muhat,double **zbhat,double **zwhat,int niter) { return slopingbw(m,n,s.S,s.ybar,s.ybardash,s.n,mu,zb,zw, muhat,zbhat,zwhat,niter) ; }

Archived from sloping.html

#include "memory.h" #include <math.h> double logcholesky(double **a,double **b,int n) ; double cholesky(double **a,double **b,int n) ; static double log2pi=log(2*3.141592653589793) ; double slopingbw(int m,int N,double **S,double **ybar,double **ybardash, int *ni,double *mu,double **zb,double **zw, double *muhat,double **zbhat,double **zwhat,int niter) { int i,j,k,t,mdash=m+1,ono,reest,dopdf,iterno,NL,NS,mlim ; double n0,n1,n2,ndot,*edot=vector(m),edotdot,qret,q,logalphadet ; double **E=matrix(mdash,mdash),*ydash=vector(mdash),*a=vector(mdash) ; double **alpha=matrix(mdash,mdash),*muzb=vector(mdash) ; double **e=matrix(m,m),**zbinv=matrix(mdash,mdash),**alphainv=alpha,**Einv=E ; double logzwdet,logzbdet,logzbxxdet,*mux=vector(mdash) ; double **a0=matrix(mdash,mdash),**a1=matrix(mdash,mdash) ; double **zbxxinv,**zbxxhat,*muzbxx,*muxhat ; for(ndot=NL=NS=t=0;t<N;t++) { if(ni[t]==1) NS += 1 ; else NL += 1 ; ndot += ni[t] ; } if(NS>0) { zbxxinv = matrix(m,m) ; zbxxhat = matrix(m,m) ; muzbxx = vector(m) ; muxhat = vector(m) ; } for(iterno=0;iterno<niter||iterno==0;iterno++) { reest = (iterno<niter) ; dopdf = (iterno>=niter-1) ; if(iterno==0) { for(i=0;i<mdash;i++) for(j=0;j<mdash;j++) zbinv[i][j] = zb[i][j] ; for(i=0;i<m;i++) for(j=0;j<m;j++) e[i][j] = zw[i][j] ; } else { for(i=0;i<mdash;i++) for(j=0;j<mdash;j++) zbinv[i][j] = zbhat[i][j] ; for(i=0;i<m;i++) for(j=0;j<m;j++) e[i][j] = zwhat[i][j] ; } logzwdet = logcholesky(e,e,m) ; if(NS>0) // block inversion of zb { logzbxxdet = logcholesky(zbinv,zbxxinv,m) ; for(i=0;i<m;i++) for(a[i]=j=0;j<m;j++) a[i] += zbxxinv[i][j] * zbinv[j][m] ; for(q=zbinv[m][m],i=0;i<m;i++) q -= zbinv[i][m] * a[i] ; logzbdet = logzbxxdet + log(q) ; zbinv[m][m] = q = 1/q ; for(i=0;i<m;i++) for(j=0;j<m;j++) zbinv[i][j] = zbxxinv[i][j] + q*a[i]*a[j] ; for(i=0;i<m;i++) zbinv[i][m] = zbinv[m][i] = -q*a[i] ; } else { logzbdet = logcholesky(zbinv,zbinv,mdash) ; logzbxxdet = 0 ; } /*----------------------------------------------------------------------- */
/*page*/ /*----------------------------------------------------------------------- */ for(i=0;i<m;i++) for(edot[i]=j=0;j<m;j++) edot[i] += e[i][j] ; for(edotdot=i=0;i<m;i++) edotdot += edot[i] ; for(i=0;i<mdash;i++) mux[i] = mu[i] ; if(dopdf) { qret = NL*logzbdet + NS*logzbxxdet + ndot*(logzwdet+m*log2pi) ; for(i=0;i<m;i++) for(j=0;j<m;j++) qret += e[i][j] * S[i][j] ; } if(reest) { for(i=0;i<m;i++) for(j=0;j<m;j++) zwhat[i][j] = S[i][j] ; for(i=0;i<mdash;i++) for(j=0;j<mdash;j++) zbhat[i][j] = 0 ; for(i=0;i<mdash;i++) for(muzb[i]=j=0;j<mdash;j++) muzb[i] += mux[j] * zbinv[i][j] ; for(i=0;i<mdash;i++) muhat[i] = 0 ; // may overwrite mu - beware! if(NS) { for(i=0;i<m;i++) for(muzbxx[i]=j=0;j<m;j++) muzbxx[i] += mux[j] * zbxxinv[i][j] ; for(i=0;i<m;i++) for(j=0;j<m;j++) zbxxhat[i][j] = 0 ; for(i=0;i<m;i++) muxhat[i] = 0 ; } } for(ono=-1,t=0;t<N;ono=ni[t],t++) if(ni[t]>0) { if(ono!=ni[t]&&ni[t]==1) { n0 = 1 ; for(i=0;i<m;i++) for(j=0;j<m;j++) alphainv[i][j] = e[i][j] + zbxxinv[i][j] ; logalphadet = -logcholesky(alphainv,alpha,m) ; if(dopdf) { for(i=0;i<m;i++) for(k=0;k<m;k++) { for(q=j=0;j<m;j++) q += e[i][j] * alpha[j][k] ; a0[i][k] = q ; } for(i=0;i<m;i++) for(k=0;k<m;k++) { for(q=j=0;j<m;j++) q += a0[i][j] * zbxxinv[j][k] ; a1[i][k] = q ; } } } if(ono!=ni[t]&&ni[t]>1) // terms whose cost is cubic in m are shared ... { n0 = ni[t] ; // ... between all groups of the same length n1 = n0*(n0-1)/2 ; n2 = n1*(2*n0-1)/3 ; // compute E for(i=0;i<m;i++) for(j=0;j<m;j++) E[i][j] = n0 * e[i][j] ; for(i=0;i<m;i++) E[i][m] = E[m][i] = n1 * edot[i] ; E[m][m] = n2 * edotdot ; // compute alpha for(i=0;i<mdash;i++) for(j=0;j<mdash;j++) alphainv[i][j] = E[i][j] + zbinv[i][j] ; logalphadet = -logcholesky(alphainv,alpha,mdash) ; if(dopdf) { for(i=0;i<mdash;i++) for(k=0;k<mdash;k++) { for(q=j=0;j<mdash;j++) q += E[i][j] * alpha[j][k] ; a0[i][k] = q ; } for(i=0;i<mdash;i++) for(k=0;k<mdash;k++) { for(q=j=0;j<mdash;j++) q += a0[i][j] * zbinv[j][k] ; a1[i][k] = q ; } cholesky(E,Einv,mdash) ; } } /*--------------------------------------------------------------------- */
/*page*/ /*--------------------------------------------------------------------- */ // compute ydash for(i=0;i<m;i++) for(ydash[i]=j=0;j<m;j++) ydash[i] += n0 * e[i][j] * ybar[t][j] ; if(n0>1) for(ydash[m]=i=0;i<m;i++) ydash[m] += n1 * edot[i] * ybardash[t][i] ; // compute pdf if(dopdf) { qret -= logalphadet ; if(n0==1) { for(i=0;i<m;i++) a[i] = mux[i] - ybar[t][i] ; for(i=0;i<m;i++) for(j=0;j<m;j++) qret += a[i] * a1[i][j] * a[j] ; } else { for(i=0;i<mdash;i++) for(a[i]=mux[i],j=0;j<mdash;j++) a[i] -= ydash[j] * Einv[i][j] ; for(i=0;i<mdash;i++) for(j=0;j<mdash;j++) qret += a[i] * a1[i][j] * a[j] ; for(q=i=0;i<mdash;i++) q += (ybar[t][i]-ybardash[t][i]) * edot[i] ; qret -= 6*n1*q*q / ((n0+1)*edotdot) ; } } if(reest==0) continue ; // compute a if(n0==1) for(i=0;i<m;i++) for(a[i]=j=0;j<m;j++) a[i] += (ydash[j]+muzbxx[j]) * alpha[i][j] ; else for(i=0;i<mdash;i++) for(a[i]=j=0;j<mdash;j++) a[i] += (ydash[j]+muzb[j]) * alpha[i][j] ; // update zwhat (first step, for all sequence lengths) for(i=0;i<m;i++) for(j=0;j<m;j++) zwhat[i][j] += n0 * ( (a[i]-ybar[t][i])*(a[j]-ybar[t][j]) + alpha[i][j] ) ; // reestimation: update muhat, zbhat if(n0==1) { for(i=0;i<m;i++) muxhat[i] += a[i] ; for(i=0;i<m;i++) for(j=0;j<m;j++) zbxxhat[i][j] += a[i]*a[j] + alpha[i][j] ; } else { for(i=0;i<mdash;i++) muhat[i] += a[i] ; for(i=0;i<mdash;i++) for(j=0;j<mdash;j++) zbhat[i][j] += a[i]*a[j] + alpha[i][j] ; for(i=0;i<m;i++) a[i] = a[m] * ( a[i]-ybardash[t][i] ) ; // update zwhat (second step) for(i=0;i<m;i++) for(j=0;j<m;j++) zwhat[i][j] += n1 * ( a[i] + a[j] + alpha[i][m] + alpha[j][m] ) + n2 * ( a[m]*a[m] + alpha[m][m] ) ; } } /*----------------------------------------------------------------------- */
/*page*/ /*----------------------------------------------------------------------- */ if(reest==0) continue ; // put new estimates together for(i=0;i<m;i++) for(j=0;j<m;j++) zwhat[i][j] /= ndot ; for(i=0;i<mdash;i++) muhat[i] /= NL ; for(i=0;i<mdash;i++) for(j=0;j<mdash;j++) zbhat[i][j] -= NL*muhat[i]*muhat[j] ; if(NS==0) { for(i=0;i<mdash;i++) for(j=0;j<mdash;j++) zbhat[i][j] /= N ; continue ; } for(i=0;i<m;i++) { muxhat[i] /= NS ; a[i] = muxhat[i] - muhat[i] ; } for(i=0;i<m;i++) for(j=0;j<m;j++) zbxxhat[i][j] += NL*NS*a[i]*a[j]/N - NS*muxhat[i]*muxhat[j] ; // zbhat is V, zbxxhat is W cholesky(zbhat,zbinv,m) ; // compute zbinv as 1/Vxx for(i=0;i<m;i++) for(j=0;j<m;j++) // new zetaBhat|xx zbhat[i][j] = (zbhat[i][j]+zbxxhat[i][j]) / N ; for(i=0;i<m;i++) for(a[i]=j=0;j<m;j++) a[i] += zbhat[j][m] * zbinv[i][j] ; // Vsx*(1/Vxx) for(i=0;i<m;i++) // (Vsx/Vxx)*zetaBxxhat { for(q=j=0;j<m;j++) q += a[j] * zbhat[i][j] ; zbhat[i][m] = q ; } for(q=i=0;i<m;i++) for(j=0;j<m;j++) q += a[i] * zbxxhat[i][j] * a[j] ; zbhat[m][m] = zbhat[m][m]/NL + q/N ; for(q=i=0;i<m;i++) q += a[i] * zbhat[m][i] ; //zbhat[m][] is Vsx zbhat[m][m] -= NS*q/(N*(double)NL) ; for(i=0;i<m;i++) zbhat[m][i] = zbhat[i][m] ; // zbhat now contains new estimate of zetab; sort out the mean cholesky(zbhat,zbinv,m) ; // compute zbinv as 1/zetaBxxhat for(i=0;i<m;i++) muhat[i] = (NL*muhat[i]+NS*muxhat[i]) / N ; for(i=0;i<m;i++) for(j=0;j<m;j++) muhat[m] -= NS * (muhat[i]-muxhat[i]) * zbinv[i][j] * zbhat[j][m] / NL ; } // free workspace free(edot,ydash,a,muzb,mux) ; freematrix(e,zbinv,E,alpha,a0,a1) ; if(NS>0) { freematrix(zbxxinv,zbxxhat) ; free(muzbxx,muxhat) ; } return -qret / (2*ndot) ; } /*--------------------------------------------------------------------------- */

Archived from sloping.html

#include "memory.h" #include <math.h> #include "random.h" double slopingbw(int m,int n,double **S,double **ybar,double **ybardash, int *ni,double *mu,double **zb,double **zw, double *muhat,double **zbhat,double **zwhat,int niter) ; double uoamax(double (*func)(double*),double *x,int n, double xinc,double tol,int maxiter) ; int uoacond() ; double cholsqrt(double **a,double **b,int n) ; static int m,mdash,ndash,*ni,ncall ; static double **ybar,**ybardash,**S ; /*--------------------------------------------------------------------------- */ static void ranvec(double *x,int m,double *sigma,double **rot,double *mu) { int i,j ; double q ; for(i=0;i<m;i++) x[i] = mu[i] ; for(i=0;i<m;i++) { q = sigma[i] * gaussv() ; for(j=0;j<m;j++) x[j] += q * rot[i][j] ; } } /*--------------------------------------------------------------------------- */ static int vmatrix(double *v,double **z0,int m) { int i,j,k,t ; double q,**z=matrix(m,m) ; for(t=i=0;i<m;i++) for(j=0;j<=i;j++,t++) z[i][j] = v[t] ; for(i=0;i<m;i++) for(k=0;k<=i;k++) { for(q=j=0;j<=k;j++) q += z[i][j] * z[k][j] ; z0[i][k] = z0[k][i] = q ; } freematrix(z) ; return t ; } static void vvector(double *mu,double **zb,double **zw,int m,double *v) { int i,j,t,mdash=m+1 ; double **z=matrix(m,m) ; for(t=0;t<mdash;t++) v[t] = mu[t] ; cholsqrt(zw,z,m) ; for(j=0;j<m;j++) for(i=0;i<=j;i++) v[t++] = z[i][j] ; freematrix(z) ; z = matrix(mdash,mdash) ; cholsqrt(zb,z,mdash) ; for(j=0;j<mdash;j++) for(i=0;i<=j;i++) v[t++] = z[i][j] ; } /*--------------------------------------------------------------------------- */
/*page*/ /*--------------------------------------------------------------------------- */ static double f(double *v) { int i,t ; double q,*mu=vector(mdash),**zw=matrix(m,m),**zb=matrix(mdash,mdash) ; for(t=0;t<mdash;t++) mu[t] = v[t] ; t += vmatrix(v+t,zw,m) ; t += vmatrix(v+t,zb,mdash) ; q = slopingbw(m,ndash,S,ybar,ybardash,ni,mu,zb,zw,0,0,0,0) ; free(mu) ; freematrix(zw,zb) ; ncall += 1 ; return q ; } /*--------------------------------------------------------------------------- */ int main(int argc,char **argv) { m = argc>1?atoi(argv[1]):5 ; mdash = m + 1 ; if(argc>2) ranset(atoi(argv[2])) ; ndash = 20 ; ybar = matrix(ndash,m) ; ybardash = matrix(ndash,m) ; S = matrix(m,m), ni = ivector(ndash) ; int groupno,i,j,k,t,nneg,iter,targ,ndot,cond ; int vlen = mdash + (mdash*(mdash+1))/2 + (m*(m+1))/2 ; double ***y=(double ***) cjcalloc(ndash,sizeof(double**)) ; double q,q0,*eb=vector(mdash),*ew=vector(m),**xs=matrix(ndash,mdash),scale ; double **rotw=matrix(m,m),**rotb=matrix(mdash,mdash),*mu=vector(mdash) ; double *mubw=vector(mdash),**zbbw=matrix(mdash,mdash),**zwbw=matrix(m,m) ; double *muml=vector(mdash),**zbml=matrix(mdash,mdash),**zwml=matrix(m,m) ; double *v=vector(vlen),**zw=matrix(m,m),**zb=matrix(mdash,mdash) ; double *a=vector(m),qpdf,qmlpdf,q0pdf,qbwpdf,n0,n1 ; // generate group sizes for(ndot=t=0;t<ndash/2;t++) ndot += ( ni[t] = 1 ) ; for(;t<ndash;t++) ndot += ( ni[t] = 3 ) ; isort(ni,ndash) ; scale = ndot / ndash ; // avge group size printf("%d groups with avge size = %.1f\n",ndash,scale) ; // generate random global parameters ranrot(rotw,m) ; for(i=0;i<m;i++) ew[i] = 1/(20*sqrt(ranf())) ; for(i=0;i<m;i++) for(k=0;k<m;k++) for(zw[i][k]=j=0;j<m;j++) zw[i][k] += rotw[j][i] * rotw[j][k] * ew[j] * ew[j] ; for(i=0;i<mdash;i++) mu[i] = gaussv() ; ranrot(rotb,mdash) ; for(i=0;i<mdash;i++) eb[i] = 1/(20*sqrt(ranf()/scale)) ; for(i=0;i<mdash;i++) for(k=0;k<mdash;k++) for(zb[i][k]=j=0;j<mdash;j++) zb[i][k] += rotb[j][i] * rotb[j][k] * eb[j] * eb[j] ; /*------------------------------------------------------------------------- */
/*page*/ /*------------------------------------------------------------------------- */ // generate group (x,s) vectors and observations for(groupno=0;groupno<ndash;groupno++) { ranvec(xs[groupno],mdash,eb,rotb,mu) ; xs[groupno][m] /= scale ; y[groupno] = matrix(ni[groupno],m) ; for(t=0;t<ni[groupno];t++) { for(i=0;i<m;i++) a[i] = xs[groupno][i] + t*xs[groupno][m] ; ranvec(y[groupno][t],m,ew,rotw,a) ; } } // compute the statistics for(groupno=0;groupno<ndash;groupno++) { n0 = ni[groupno] ; n1 = n0*(n0-1)/2 ; for(t=0;t<ni[groupno];t++) for(i=0;i<m;i++) { ybar[groupno][i] += y[groupno][t][i] ; ybardash[groupno][i] += t * y[groupno][t][i] ; } for(i=0;i<m;i++) ybar[groupno][i] /= n0 ; if(n0>1) for(i=0;i<m;i++) ybardash[groupno][i] /= n1 ; for(t=0;t<ni[groupno];t++) for(i=0;i<m;i++) for(j=0;j<m;j++) S[i][j] += (y[groupno][t][i]-ybar[groupno][i]) * (y[groupno][t][j]-ybar[groupno][j]) ; } mu[m] /= scale ; for(i=0;i<mdash;i++) { zb[i][m] /= scale ; zb[m][i] /= scale ; } qpdf = slopingbw(m,ndash,S,ybar,ybardash,ni,mu,zb,zw,0,0,0,0) ; /*----------------------------------- ML ---------------------------------- */ for(i=0;i<m;i++) for(j=0;j<m;j++) zwml[i][j] = 0 ; for(i=0;i<m;i++) { muml[i] = 0 ; zwml[i][i] = 1 ; } for(i=0;i<mdash;i++) for(j=0;j<mdash;j++) zbml[i][j] = 0 ; for(i=0;i<mdash;i++) zbml[i][i] = 1 ; vvector(muml,zbml,zwml,m,v) ; ncall = 0 ; qmlpdf = uoamax(f,v,vlen,0.1,1e-4,500000) ; cond = uoacond() ; if(cond!=0) printf("*** cond=%d ***: ",cond) ; printf("%d function evals\n",ncall) ; for(t=0;t<mdash;t++) muml[t] = v[t] ; t += vmatrix(v+t,zwml,m) ; t += vmatrix(v+t,zbml,mdash) ; /*------------------------------- Baum Welch ------------------------------ */ for(i=0;i<mdash;i++) for(j=0;j<mdash;j++) zbbw[i][j] = 0 ; for(i=0;i<mdash;i++) { mubw[i] = 0 ; zbbw[i][i] = 1 ; } for(i=0;i<m;i++) for(j=0;j<m;j++) zwbw[i][j] = 0 ; for(i=0;i<m;i++) zwbw[i][i] = 1 ; for(targ=10,iter=5;iter<1000;q=qbwpdf,iter+=5) { qbwpdf = slopingbw(m,ndash,S,ybar,ybardash,ni,mubw,zbbw,zwbw, mubw,zbbw,zwbw,5) ; if(iter==targ) { printf("logpdf=%.6f (%d iterations)\n",qbwpdf,iter) ; targ *= 10 ; } if(iter==5||qbwpdf-q>q0) q0 = qbwpdf - q ; if(iter>1&&qbwpdf-q<q0/1000) break ; } printf("%d Baum-Welch iterations\n",iter) ; /*---------------------------- print estimates ---------------------------- */ printf("pdf : %.4f\n",qpdf) ; printf("pdf (ML) : %.4f\n",qmlpdf) ; printf("pdf (BW) : %.4f\n\n",qbwpdf) ; printf("mu:\n") ; for(i=0;i<m;i++) printf("%8.3f",mu[i]) ; printf("%8.3f\n\n",100*mu[m]) ; printf("mu MLE:\n") ; for(i=0;i<m;i++) printf("%8.3f",muml[i]) ; printf("%8.3f\n\n",100*muml[m]) ; printf("mu Baum-Welch estimator:\n") ; for(i=0;i<m;i++) printf("%8.3f",mubw[i]) ; printf("%8.3f\n",100*mubw[m]) ; printf("\n-----------------------------------------\n\n") ; printf("zeta[W]*1000:\n") ; for(i=0;i<m;i++) { for(j=0;j<m;j++) printf("%8.3f",1000*zw[i][j]) ; printf("\n") ; } printf("\n") ; printf("zeta[W] MLE:\n") ; for(i=0;i<m;i++) { for(j=0;j<m;j++) printf("%8.3f",1000*zwml[i][j]) ; printf("\n") ; } printf("\n") ; printf("zeta[W] Baum-Welch:\n") ; for(i=0;i<m;i++) { for(j=0;j<m;j++) printf("%8.3f",1000*zwbw[i][j]) ; printf("\n") ; } printf("\n-----------------------------------------\n\n") ; printf("zeta[B]*10:\n") ; for(i=0;i<mdash;i++) { for(j=0;j<mdash;j++) printf("%8.3f",10*zb[i][j]) ; printf("\n") ; } printf("\n") ; printf("zeta[B] MLE:\n") ; for(i=0;i<mdash;i++) { for(j=0;j<mdash;j++) printf("%8.3f",10*zbml[i][j]) ; printf("\n") ; } printf("\n") ; printf("zeta[B] Baum-Welch:\n") ; for(i=0;i<mdash;i++) { for(j=0;j<mdash;j++) printf("%8.3f",10*zbbw[i][j]) ; printf("\n") ; } printf("\n") ; }

Archived from utils.html

#ifndef IJ_H
#define IJ_H

#ifndef MEMORY_H
#include "memory.h"
#endif

struct ij 
{ long long i,j ; 
  ij() { i = j = 0 ; } 
  ij(long long a,long long b) { i = a ; j = b ; } 
  int fprint(FILE *f) { return fprintf(f,"(%lld,%lld)",i,j) ; } 
  int print() { return fprint(stdout) ; } 
} ;
static inline void swap(ij &a,ij &b) { ij c=a ; a = b ; b = c ; } 

static void ijsort(ij *u,int n)
{ int i,j,incr ; 
  ij v ; 

  for(incr=1;1+3*incr<n;incr=1+3*incr) ; 
  for(;incr>0;incr/=3) for(i=incr;i<n;i++)
  { v = u[i] ; 
    for(j=i;j>=incr;j-=incr) 
    { if(v.i<u[j-incr].i) u[j] = u[j-incr] ; else break ; }
    u[j] = v ;
  }
}
/* allocate vectors of ijs */
static ij *ijvector(int n) { return (ij *) cjcalloc(n,sizeof(ij)) ; } 
static ij *ijvector(int *x,int n)
{ ij *u=ijvector(n) ; 
  for(int i=0;i<n;i++) u[i] = ij(x[i],i) ; 
  return u ; 
}
static ij *ijvector(long long *x,int n)
{ ij *u=ijvector(n) ; 
  for(int i=0;i<n;i++) u[i] = ij(x[i],i) ; 
  return u ; 
}
static ij *ijvector(ij *a,int n)
{ return (ij *) cjcrealloc(a,n*sizeof(ij)) ; }
#endif

Archived from sloping.html

#include "memory.h"
#include <math.h>

double logcholesky(double **a,double **b,int n) ;
double    cholesky(double **a,double **b,int n) ;
static double log2pi=log(2*3.141592653589793) ;

double slopingbw(int m,int N,double **S,double **ybar,double **ybardash,
                 int *ni,double *mu,double **zb,double **zw,
                 double *muhat,double **zbhat,double **zwhat,int niter)
{ int i,j,k,t,mdash=m+1,ono,reest,dopdf,iterno,NL,NS,mlim ;
  double n0,n1,n2,ndot,*edot=vector(m),edotdot,qret,q,logalphadet ; 
  double **E=matrix(mdash,mdash),*ydash=vector(mdash),*a=vector(mdash) ; 
  double **alpha=matrix(mdash,mdash),*muzb=vector(mdash) ; 
  double **e=matrix(m,m),**zbinv=matrix(mdash,mdash),**alphainv=alpha,**Einv=E ;
  double logzwdet,logzbdet,logzbxxdet,*mux=vector(mdash) ;
  double **a0=matrix(mdash,mdash),**a1=matrix(mdash,mdash) ; 
  double **zbxxinv,**zbxxhat,*muzbxx,*muxhat ;

  for(ndot=NL=NS=t=0;t<N;t++) 
  { if(ni[t]==1) NS += 1 ; else NL += 1 ; ndot += ni[t] ; }
  if(NS>0) 
  { zbxxinv = matrix(m,m) ; zbxxhat = matrix(m,m) ;
    muzbxx = vector(m) ; muxhat = vector(m) ; 
  }

  for(iterno=0;iterno<niter||iterno==0;iterno++)
  { reest = (iterno<niter) ;
    dopdf = (iterno>=niter-1) ;
    if(iterno==0) 
    { for(i=0;i<mdash;i++) for(j=0;j<mdash;j++) zbinv[i][j] = zb[i][j] ;
      for(i=0;i<m;i++) for(j=0;j<m;j++) e[i][j] = zw[i][j] ;
    }
    else 
    { for(i=0;i<mdash;i++) for(j=0;j<mdash;j++) zbinv[i][j] = zbhat[i][j] ;
      for(i=0;i<m;i++) for(j=0;j<m;j++) e[i][j] = zwhat[i][j] ;
    }
    logzwdet = logcholesky(e,e,m) ; 
    if(NS>0) // block inversion of zb
    { for(i=0;i<m;i++) for(j=0;j<m;j++) zbxxinv[i][j] = zbinv[i][j] ;
      logzbxxdet = logcholesky(zbxxinv,zbxxinv,m) ;
      for(i=0;i<m;i++) for(a[i]=j=0;j<m;j++) 
        a[i] += zbxxinv[i][j] * zbinv[j][m] ;
      for(q=zbinv[m][m],i=0;i<m;i++) q -= zbinv[i][m] * a[i] ;
      logzbdet = logzbxxdet + log(q) ;
      zbinv[m][m] = q = 1/q ;
      for(i=0;i<m;i++) for(j=0;j<m;j++) 
        zbinv[i][j] = zbxxinv[i][j] + q*a[i]*a[j] ;
      for(i=0;i<m;i++) zbinv[i][m] = zbinv[m][i] = -q*a[i] ;
    }
    else { logzbdet = logcholesky(zbinv,zbinv,mdash) ; logzbxxdet = 0 ; }

    /*----------------------------------------------------------------------- */
    /*page*/
    /*----------------------------------------------------------------------- */

    for(i=0;i<m;i++) for(edot[i]=j=0;j<m;j++) edot[i] += e[i][j] ; 
    for(edotdot=i=0;i<m;i++) edotdot += edot[i] ; 
    for(i=0;i<mdash;i++) mux[i] = mu[i] ; 

    if(dopdf) 
    { qret = NL*logzbdet + NS*logzbxxdet + ndot*(logzwdet+m*log2pi) ;
      for(i=0;i<m;i++) for(j=0;j<m;j++) qret += e[i][j] * S[i][j] ;
    }

    if(reest)
    { for(i=0;i<m;i++) for(j=0;j<m;j++) zwhat[i][j] = S[i][j] ; 
      for(i=0;i<mdash;i++) for(j=0;j<mdash;j++) zbhat[i][j] = 0 ; 
      for(i=0;i<mdash;i++) for(muzb[i]=j=0;j<mdash;j++)
        muzb[i] += mu[j] * zbinv[i][j] ; 
      for(i=0;i<mdash;i++) muhat[i] = 0 ; 
      if(NS) 
      { for(i=0;i<m;i++) for(muzbxx[i]=j=0;j<m;j++)
          muzbxx[i] += mu[j] * zbxxinv[i][j] ; 
        for(i=0;i<m;i++) for(j=0;j<m;j++) zbxxhat[i][j] = 0 ; 
        for(i=0;i<m;i++) muxhat[i] = 0 ; 
      }
    }

    for(ono=-1,t=0;t<N;ono=ni[t],t++) if(ni[t]>0)
    { if(ono!=ni[t]&&ni[t]==1) 
      { n0 = 1 ; 
        for(i=0;i<m;i++) for(j=0;j<m;j++) 
          alphainv[i][j] = e[i][j] + zbxxinv[i][j] ;
        logalphadet = -logcholesky(alphainv,alpha,m) ; 
        if(dopdf)
        { for(i=0;i<m;i++) for(k=0;k<m;k++) 
          { for(q=j=0;j<m;j++) q += e[i][j] * alpha[j][k] ; a0[i][k] = q ; }
          for(i=0;i<m;i++) for(k=0;k<m;k++) 
          { for(q=j=0;j<m;j++) q += a0[i][j] * zbxxinv[j][k] ; 
            a1[i][k] = q ; 
          }
        }
      }
      if(ono!=ni[t]&&ni[t]>1) // terms whose cost is cubic in m are shared ...
      { n0 = ni[t] ;          // ... between all groups of the same length
        n1 = n0*(n0-1)/2 ; 
        n2 = n1*(2*n0-1)/3 ;
        // compute E
        for(i=0;i<m;i++) for(j=0;j<m;j++) E[i][j] = n0 * e[i][j] ; 
        for(i=0;i<m;i++) E[i][m] = E[m][i] = n1 * edot[i] ; 
        E[m][m] = n2 * edotdot ; 
        // compute alpha
        for(i=0;i<mdash;i++) for(j=0;j<mdash;j++) 
          alphainv[i][j] = E[i][j] + zbinv[i][j] ; 
        logalphadet = -logcholesky(alphainv,alpha,mdash) ; 
        if(dopdf)
        { for(i=0;i<mdash;i++) for(k=0;k<mdash;k++) 
          { for(q=j=0;j<mdash;j++) q += E[i][j] * alpha[j][k] ; a0[i][k] = q ; }
          for(i=0;i<mdash;i++) for(k=0;k<mdash;k++) 
          { for(q=j=0;j<mdash;j++) q += a0[i][j] * zbinv[j][k] ; 
            a1[i][k] = q ; 
          }
          cholesky(E,Einv,mdash) ; 
        }
      }
      /*--------------------------------------------------------------------- */
      /*page*/
      /*--------------------------------------------------------------------- */

      // compute ydash
      for(i=0;i<m;i++) for(ydash[i]=j=0;j<m;j++) 
        ydash[i] += n0 * e[i][j] * ybar[t][j] ;
      if(n0>1) for(ydash[m]=i=0;i<m;i++) 
        ydash[m] += n0 * edot[i] * ybardash[t][i] ; 

      // compute pdf
      if(dopdf) qret -= logalphadet  ;
      if(dopdf&&n0==1)
      { for(i=0;i<m;i++) a[i] = mux[i] - ybar[t][i] ;
        for(i=0;i<m;i++) for(j=0;j<m;j++) qret += a[i] * a1[i][j] * a[j] ; 
      }
      if(dopdf&&n0>1)
      { for(i=0;i<mdash;i++) for(a[i]=mux[i],j=0;j<mdash;j++) 
          a[i] -= ydash[j] * Einv[i][j] ;
        for(i=0;i<mdash;i++) for(j=0;j<mdash;j++) 
          qret += a[i] * a1[i][j] * a[j] ; 
        for(q=i=0;i<mdash;i++) 
          q += ( (n0-1)*ybar[t][i]/2 - ybardash[t][i] ) * edot[i] ;
        qret -= 12*n0*q*q / ((n0*n0-1)*edotdot) ;
      }
      if(reest==0) continue ;

      // compute a
      if(n0==1) for(i=0;i<m;i++) for(a[i]=j=0;j<m;j++)
        a[i] += (ydash[j]+muzbxx[j]) * alpha[i][j] ; 
      else for(i=0;i<mdash;i++) for(a[i]=j=0;j<mdash;j++)
        a[i] += (ydash[j]+muzb[j]) * alpha[i][j] ; 

      // update zwhat (first step, for all sequence lengths)
      for(i=0;i<m;i++) for(j=0;j<m;j++) zwhat[i][j] += 
        n0 * ( (a[i]-ybar[t][i])*(a[j]-ybar[t][j]) + alpha[i][j] ) ;

      // reestimation: update muhat, zbhat
      if(n0==1) 
      { for(i=0;i<m;i++) muxhat[i] += a[i] ; 
        for(i=0;i<m;i++) for(j=0;j<m;j++) 
          zbxxhat[i][j] += a[i]*a[j] + alpha[i][j] ; 
      }
      else 
      { for(i=0;i<mdash;i++) muhat[i] += a[i] ; 
        for(i=0;i<mdash;i++) for(j=0;j<mdash;j++) 
          zbhat[i][j] += a[i]*a[j] + alpha[i][j] ; 
        // update zwhat (second step)
        for(i=0;i<m;i++) for(j=0;j<m;j++) zwhat[i][j] += 
          n1 * ( a[m]*(a[i]+a[j]) + alpha[i][m] + alpha[j][m] ) + 
          n2 * ( a[m]*a[m] + alpha[m][m] ) -
          n0 * a[m] * (ybardash[t][i]+ybardash[t][j]) ;
      }
    }
    /*----------------------------------------------------------------------- */
    /*page*/
    /*----------------------------------------------------------------------- */

    if(reest==0) continue ;

    // put new estimates together
    for(i=0;i<m;i++) for(j=0;j<m;j++) zwhat[i][j] /= ndot ; 
    for(i=0;i<mdash;i++) muhat[i] /= NL ; 
    for(i=0;i<mdash;i++) for(j=0;j<mdash;j++) 
      zbhat[i][j] -= NL*muhat[i]*muhat[j] ;

    if(NS==0) 
    { for(i=0;i<mdash;i++) for(j=0;j<mdash;j++) zbhat[i][j] /= N ; continue ; }
    
    for(i=0;i<m;i++) { muxhat[i] /= NS ; a[i] = muxhat[i] - muhat[i] ; }
    for(i=0;i<m;i++) for(j=0;j<m;j++) 
      zbxxhat[i][j] += NL*NS*a[i]*a[j]/N - NS*muxhat[i]*muxhat[j] ;
    // zbhat is V, zbxxhat is W

    cholesky(zbhat,zbinv,m) ;             // compute zbinv as 1/Vxx
    for(i=0;i<m;i++) for(j=0;j<m;j++)     // new zetaBhat|xx
      zbhat[i][j] = (zbhat[i][j]+zbxxhat[i][j]) / N ;

    for(i=0;i<m;i++) for(a[i]=j=0;j<m;j++) 
      a[i] += zbhat[j][m] * zbinv[i][j] ; // Vsx*(1/Vxx)
    for(i=0;i<m;i++)                      // (Vsx/Vxx)*zetaBxxhat
    { for(q=j=0;j<m;j++) q += a[j] * zbhat[i][j] ; zbhat[i][m] = q ; }

    for(q=i=0;i<m;i++) for(j=0;j<m;j++) q += a[i] * zbxxhat[i][j] * a[j] ;
    zbhat[m][m] = zbhat[m][m]/NL + q/N ;
    for(q=i=0;i<m;i++) q += a[i] * zbhat[m][i] ; //zbhat[m][] is Vsx
    zbhat[m][m] -= NS*q/(N*(double)NL) ;
    for(i=0;i<m;i++) zbhat[m][i] = zbhat[i][m] ;

    // zbhat now contains new estimate of zetab; sort out the mean
    cholesky(zbhat,zbinv,m) ;             // compute zbinv as 1/zetaBxxhat
    for(i=0;i<m;i++) muhat[i] = (NL*muhat[i]+NS*muxhat[i]) / N ;
    for(i=0;i<m;i++) for(j=0;j<m;j++) 
      muhat[m] -= NS * (muhat[i]-muxhat[i]) * zbinv[i][j] * zbhat[j][m] / NL ;
  }

  // free workspace
  free(edot,ydash,a,muzb,mux) ; freematrix(e,zbinv,E,alpha,a0,a1) ; 
  if(NS>0) { freematrix(zbxxinv,zbxxhat) ; free(muzbxx,muxhat) ; }
  return -qret / (2*ndot) ; 
}
/*--------------------------------------------------------------------------- */
/*page*/
/*--------------------------------------------------------------------------- */

void slopingstats(int m,int n,double **S,double **ybar,double **ybardash,
                  int *ni,double *mu,double **zb,double **zw,double ***y)
{ int groupno,t,i,j,mdash=m+1,ngrp ;
  double n0,n1,n2,ndot,t1,r,*xs=vector(mdash),lambda ;

  for(i=0;i<m;i++) for(j=0;j<m;j++) zw[i][j] = S[i][j] = 0 ; 
  for(i=0;i<mdash;i++) mu[i] = 0 ;
  for(i=0;i<mdash;i++) for(j=0;j<mdash;j++) zb[i][j] = 0 ;

  for(t1=ndot=ngrp=groupno=0;groupno<n;groupno++) if(ni[groupno]>1)
  { ngrp += 1 ;
    ndot += ( n0 = ni[groupno] ) ; 
    t1 += ( n1 = n0*(n0-1)/2 ) ; 
    n2 = n1*(2*n0-1)/3 ;
    r = 1 / ( n0*n2-n1*n1 ) ;

    for(i=0;i<m;i++) ybardash[groupno][i] = ybar[groupno][i] = 0 ; 
    for(t=0;t<ni[groupno];t++) for(i=0;i<m;i++) 
    { ybar[groupno][i] += y[groupno][t][i] ;
      ybardash[groupno][i] += t * y[groupno][t][i] ;
    }
    for(i=0;i<m;i++) 
    { ybar[groupno][i] /= ni[groupno] ; ybardash[groupno][i] /= ni[groupno] ; }

    for(xs[m]=i=0;i<m;i++) 
      xs[m] += n0*ybardash[groupno][i] - n1*ybar[groupno][i] ;
    xs[m] *= r * n0 /m ;
    for(i=0;i<m;i++) xs[i] = ybar[groupno][i]- n1*xs[m]/n0 ;
    for(i=0;i<mdash;i++) mu[i] += n1 * xs[i] ;
    for(i=0;i<mdash;i++) for(j=0;j<mdash;j++) zb[i][j] += n1 * xs[i] * xs[j] ;

    for(t=0;t<ni[groupno];t++) for(i=0;i<m;i++) for(j=0;j<m;j++)
    { S[i][j] += (y[groupno][t][i]-ybar[groupno][i]) * 
                 (y[groupno][t][j]-ybar[groupno][j]) ;
      zw[i][j] += n1 * (y[groupno][t][i]-xs[i]-t*xs[m]) * 
                       (y[groupno][t][j]-xs[j]-t*xs[m]) ;
    }
  }

  for(i=0;i<mdash;i++) mu[i] /= t1 ;
  for(i=0;i<mdash;i++) for(j=0;j<mdash;j++) 
    zb[i][j] = zb[i][j]/t1 - mu[i]*mu[j] ;
  for(i=0;i<m;i++) for(j=0;j<m;j++) zw[i][j] /= t1 ;
  free(xs) ;

  // if all group sizes are <3 the zw hint is singular, so merge in some
  // zb hint
  lambda = ngrp / ndot ; // reciprocal of average group length
  r = sqrt(lambda) ;     // zb is equivalent to a variance of (zb*r) per obsn
  for(i=0;i<m;i++) for(j=0;j<m;j++) 
    zw[i][j] = (1-lambda)*zw[i][j] + lambda*r*zb[i][j] ;
}

Archived from sloping.html

#include "memory.h"
#include <math.h>
#include "random.h"

double slopingbw(int m,int n,double **S,double **ybar,double **ybardash,
                 int *ni,double *mu,double **zb,double **zw,
                 double *muhat,double **zbhat,double **zwhat,int niter) ;
void slopingstats(int m,int n,double **S,double **ybar,double **ybardash,
                  int *ni,double *mu,double **zb,double **zw,double ***y) ;
double uoamax(double (*func)(double*),double *x,int n,
              double xinc,double tol,int maxiter) ;
int uoacond() ;
double cholsqrt(double **a,double **b,int n) ;

static int m,mdash,ndash,*ni,ncall ;
static double **ybar,**ybardash,**S ;

/*--------------------------------------------------------------------------- */

static void ranvec(double *x,int m,double *sigma,double **rot,double *mu)
{ int i,j ; 
  double q ;
  for(i=0;i<m;i++) x[i] = mu[i] ; 
  for(i=0;i<m;i++) 
  { q = sigma[i] * gaussv() ; for(j=0;j<m;j++) x[j] += q * rot[i][j] ; }
}
/*--------------------------------------------------------------------------- */

static int vmatrix(double *v,double **z0,int m) 
{ int i,j,k,t ; 
  double q,**z=matrix(m,m) ;

  for(t=i=0;i<m;i++) for(j=0;j<=i;j++,t++) z[i][j] = v[t] ; 
  for(i=0;i<m;i++) for(k=0;k<=i;k++) 
  { for(q=j=0;j<=k;j++) q += z[i][j] * z[k][j] ; z0[i][k] = z0[k][i] = q ; }
  freematrix(z) ; 
  return t ;
}
static void vvector(double *mu,double **zb,double **zw,int m,double *v)
{ int i,j,t,mdash=m+1 ;
  double **z=matrix(m,m) ;
  for(t=0;t<mdash;t++) v[t] = mu[t] ;
  cholsqrt(zw,z,m) ; 
  for(j=0;j<m;j++) for(i=0;i<=j;i++) v[t++] = z[i][j] ; 
  freematrix(z) ;
  z = matrix(mdash,mdash) ;
  cholsqrt(zb,z,mdash) ; 
  for(j=0;j<mdash;j++) for(i=0;i<=j;i++) v[t++] = z[i][j] ; 
}
/*--------------------------------------------------------------------------- */
/*page*/
/*--------------------------------------------------------------------------- */

static double f(double *v)
{ int i,t ;
  double q,*mu=vector(mdash),**zw=matrix(m,m),**zb=matrix(mdash,mdash) ;
  for(t=0;t<mdash;t++) mu[t] = v[t] ;
  t += vmatrix(v+t,zw,m) ;
  t += vmatrix(v+t,zb,mdash) ;
  q = slopingbw(m,ndash,S,ybar,ybardash,ni,mu,zb,zw,0,0,0,0) ;
  free(mu) ;
  freematrix(zw,zb) ;
  ncall += 1 ;
  return q ;
}
/*--------------------------------------------------------------------------- */

int main(int argc,char **argv)
{ m = argc>1?atoi(argv[1]):5 ;
  mdash = m + 1 ; 
  if(argc>2) ranset(atoi(argv[2])) ; 
  ndash = 10 ;
  ybar = matrix(ndash,m) ; 
  ybardash = matrix(ndash,m) ; 
  S = matrix(m,m),
  ni = ivector(ndash) ;

  int groupno,i,j,k,t,nneg,iter,targ,ndot,cond ; 
  int vlen = mdash + (mdash*(mdash+1))/2 + (m*(m+1))/2 ; 
  double ***y=(double ***) cjcalloc(ndash,sizeof(double**)) ;
  double q,q0,*eb=vector(mdash),*ew=vector(m),**xs=matrix(ndash,mdash),scale ;
  double **rotw=matrix(m,m),**rotb=matrix(mdash,mdash),*mu=vector(mdash) ;
  double *mubw=vector(mdash),**zbbw=matrix(mdash,mdash),**zwbw=matrix(m,m) ;
  double *muml=vector(mdash),**zbml=matrix(mdash,mdash),**zwml=matrix(m,m) ;
  double *mu0=vector(mdash),**zb0=matrix(mdash,mdash),**zw0=matrix(m,m) ;
  double *v=vector(vlen),**zw=matrix(m,m),**zb=matrix(mdash,mdash) ;
  double *a=vector(m),qpdf,qmlpdf,q0pdf,qbwpdf ;

  // generate group sizes
  for(ndot=t=0;t<ndash/2;t++) ndot += ( ni[t] = 1 ) ; 
  for(;t<ndash;t++) ndot += ( ni[t] = 3 ) ; 
  isort(ni,ndash) ; 
  scale = ndot / ndash ; // avge group size
  printf("%d groups with avge size = %.1f\n",ndash,scale) ;

  // generate random global parameters
  ranrot(rotw,m) ;
  for(i=0;i<m;i++) ew[i] = 1/(20*sqrt(ranf())) ; 
  for(i=0;i<m;i++) for(k=0;k<m;k++) for(zw[i][k]=j=0;j<m;j++)
    zw[i][k] += rotw[j][i] * rotw[j][k] * ew[j] * ew[j] ; 

  for(i=0;i<mdash;i++) mu[i] = gaussv() ; 
  ranrot(rotb,mdash) ; 
  for(i=0;i<mdash;i++) eb[i] = 1/(20*sqrt(ranf()/scale)) ; 
  for(i=0;i<mdash;i++) for(k=0;k<mdash;k++) for(zb[i][k]=j=0;j<mdash;j++)
    zb[i][k] += rotb[j][i] * rotb[j][k] * eb[j] * eb[j] ; 

  /*------------------------------------------------------------------------- */
  /*page*/
  /*------------------------------------------------------------------------- */

  // generate group (x,s) vectors and observations
  for(groupno=0;groupno<ndash;groupno++) 
  { ranvec(xs[groupno],mdash,eb,rotb,mu) ; 
    xs[groupno][m] /= scale ; 
    y[groupno] = matrix(ni[groupno],m) ; 
    for(t=0;t<ni[groupno];t++) 
    { for(i=0;i<m;i++) a[i] = xs[groupno][i] + t*xs[groupno][m] ;
      ranvec(y[groupno][t],m,ew,rotw,a) ; 
    } 
  }
  slopingstats(m,ndash,S,ybar,ybardash,ni,mu0,zb0,zw0,y) ;

  mu[m] /= scale ;
  for(i=0;i<mdash;i++) { zb[i][m] /= scale ; zb[m][i] /= scale ; }
  qpdf = slopingbw(m,ndash,S,ybar,ybardash,ni,mu,zb,zw,0,0,0,0) ;
  q0pdf = slopingbw(m,ndash,S,ybar,ybardash,ni,mu0,zb0,zw0,0,0,0,0) ;

  /*----------------------------------- ML ---------------------------------- */

  vvector(mu0,zb0,zw0,m,v) ;
  ncall = 0 ;
  qmlpdf = uoamax(f,v,vlen,0.1,1e-4,500000) ;
  cond = uoacond() ;
  if(cond!=0) printf("*** cond=%d ***: ",cond) ;
  printf("%d function evals\n",ncall) ;

  for(t=0;t<mdash;t++) muml[t] = v[t] ;
  t += vmatrix(v+t,zwml,m) ;
  t += vmatrix(v+t,zbml,mdash) ;
  
  /*------------------------------- Baum Welch ------------------------------ */

  for(i=0;i<mdash;i++) zbbw[i][i] = 1 ;
  for(i=0;i<m;i++) zwbw[i][i] = 1 ;
  for(i=0;i<mdash;i++) mubw[i] = 0 ; 
  vvector(mu0,zb0,zw0,m,v) ;
  for(t=0;t<mdash;t++) mubw[t] = v[t] ;
  t += vmatrix(v+t,zwbw,m) ;
  t += vmatrix(v+t,zbbw,mdash) ;

  for(targ=10,iter=5;iter<=1000;q=qbwpdf,iter+=5)
  { qbwpdf = slopingbw(m,ndash,S,ybar,ybardash,ni,mubw,zbbw,zwbw,
                                                  mubw,zbbw,zwbw,5) ;
    if(iter==targ)
    { printf("logpdf=%.6f (%d iterations)\n",qbwpdf,targ) ; targ *= 10 ; } 
    if(iter==1||qbwpdf-q>q0) q0 = qbwpdf - q ;
    if(iter>1&&qbwpdf-q<q0/1000) break ;
  } 
  printf("%d Baum-Welch iterations\n",iter) ;

  /*---------------------------- print estimates ---------------------------- */

  printf("pdf       : %.4f\n",qpdf) ;
  printf("pdf (hint): %.4f\n",q0pdf) ;
  printf("pdf  (ML) : %.4f\n",qmlpdf) ;
  printf("pdf  (BW) : %.4f\n\n",qbwpdf) ;

  printf("mu:\n") ;
  for(i=0;i<m;i++) printf("%8.3f",mu[i]) ; 
  printf("%8.3f\n\n",100*mu[m]) ;

  printf("mu hint:\n") ;
  for(i=0;i<m;i++) printf("%8.3f",mu0[i]) ; 
  printf("%8.3f\n\n",100*muml[m]) ; 

  printf("mu MLE:\n") ;
  for(i=0;i<m;i++) printf("%8.3f",muml[i]) ; 
  printf("%8.3f\n\n",100*muml[m]) ; 

  printf("mu Baum-Welch estimator:\n") ;
  for(i=0;i<m;i++) printf("%8.3f",mubw[i]) ; 
  printf("%8.3f\n",100*mubw[m]) ; 
  printf("\n-----------------------------------------\n\n") ; 

  printf("zeta[W]*1000:\n") ;
  for(i=0;i<m;i++)
  { for(j=0;j<m;j++) printf("%8.3f",1000*zw[i][j]) ; printf("\n") ; }
  printf("\n") ;

  printf("zeta[W] hint:\n") ;
  for(i=0;i<m;i++) 
  { for(j=0;j<m;j++) printf("%8.3f",1000*zw0[i][j]) ; printf("\n") ; }
  printf("\n") ; 

  printf("zeta[W] MLE:\n") ;
  for(i=0;i<m;i++) 
  { for(j=0;j<m;j++) printf("%8.3f",1000*zwml[i][j]) ; printf("\n") ; }
  printf("\n") ; 

  printf("zeta[W] Baum-Welch:\n") ;
  for(i=0;i<m;i++) 
  { for(j=0;j<m;j++) printf("%8.3f",1000*zwbw[i][j]) ; printf("\n") ; }
  printf("\n-----------------------------------------\n\n") ; 

  printf("zeta[B]*10:\n") ;
  for(i=0;i<mdash;i++)
  { for(j=0;j<mdash;j++) printf("%8.3f",10*zb[i][j]) ; printf("\n") ; }
  printf("\n") ;

  printf("zeta[B] hint:\n") ;
  for(i=0;i<mdash;i++)
  { for(j=0;j<mdash;j++) printf("%8.3f",10*zb0[i][j]) ; printf("\n") ; }
  printf("\n") ;

  printf("zeta[B] MLE:\n") ;
  for(i=0;i<mdash;i++) 
  { for(j=0;j<mdash;j++) printf("%8.3f",10*zbml[i][j]) ; printf("\n") ; }
  printf("\n") ; 

  printf("zeta[B] Baum-Welch:\n") ;
  for(i=0;i<mdash;i++) 
  { for(j=0;j<mdash;j++) printf("%8.3f",10*zbbw[i][j]) ; printf("\n") ; }
  printf("\n") ; 
}

Archived from routemaster.html

var segments=[],selected,actions,nactions,sel,dragging,loadno,shift=0 ; 
var pending,xpending,mouseopt=0,curhandle ; 
var routetitle,body,altdiv,curdiv,curcan,mapdiv,cmap,smap,prox,proa,pron ; 
var imgdiv,imgsizes,imghandle,imgtable,imgpix,imginfo ;  
var cursorbtn,scissorsbtn,binbtn,undobtn,redobtn,penbtn,setbtn,dlbtn ;
var defparms = {tol:10,maxsep:100,wppenalty:1000,vweight:1} ;

var neutral='<span style="font-family:helvetica">' ;
var active='<span style="cursor:pointer;color:#0000bd" onclick=' ;
var inactive='<span style="color:silver">' ;
var textbox='<div style="margin-bottom:2px;border-bottom:solid 1px silver;' +
            'padding-bottom:2px;font-family:helvetica">' ;
var finalbox='<div style="font-family:helvetica">' ;

var parser = new DOMParser() ;
var elevator = new google.maps.ElevationService ;
var map = null , clickhandle = null ;
var unsavedchanges = [] ; 

var infowindow = 
{ handle: null , 
  type: null , 
  open: function(s,pos,type)
  { this.handle = new google.maps.InfoWindow({content:s,position:pos}) ;
    this.handle.open(map) ; 
    google.maps.event.addListener(this.handle,'closeclick',
             function() { infowindow.handle = infowindow.type = null ; } ) ;
    this.type = type ; 
  } , 
  close: function() 
  { if(this.handle==null) return null ;
    var response = this.type ;
    this.handle.close() ; 
    this.handle = this.type = null ; 
    return response ; 
  } 
} ; 
// coursepoint icons
var flagsign = 
{ path: "M 0.5 20.5  L 0.5 0.5  12.5 6  0.5 11.5  ",
    fillColor: '#FCDFFF',
    fillOpacity: 0.7,
    strokeColor: 'purple',
    strokeWeight: 1.5,
    anchor: new google.maps.Point(0.5,20.5),
} ;
var turnleft = 
{ path: "M 18.5 20.5  L 16.5 11.5  A 2 2 0 0 0 14.5 9.5  "+
        "L 11.5 10  11.5 13.5  "+
        "6.5 7.5  11.5 1.5  11.5 5  16.5 5.5  A 3.5 3.5 0 0 1 20 9   z",
    fillColor: '#FCDFFF',
    fillOpacity: 0.7,
    strokeColor: 'purple',
    strokeWeight: 1.5,
    anchor: new google.maps.Point(18.5,20.5),
} ;
var straighton = 
{ path: "M 7.5 20.5  L 4.5 6.5  0.5 6.5  7.5 0.5  14.5 6.5  10 6.5  z",
    fillColor: '#FCDFFF',
    fillOpacity: 0.7,
    strokeColor: 'purple',
    strokeWeight: 1.5,
    anchor: new google.maps.Point(7.5,20.5),
} ;
var turnright = 
{ path: "M 3.5 20.5  L 5.5 11.5  A 2 2 0 0 1 7.5 9.5  L 10.5 10  10.5 13.5  "+
        "15.5 7.5  10.5 1.5  10.5 5  5.5 5.5  A 3.5 3.5 0 0 0 2 9   z",
    fillColor: '#FCDFFF',
    fillOpacity: 0.7,
    strokeColor: 'purple',
    strokeWeight: 1.5,
    anchor: new google.maps.Point(3.5,20.5),
} ;
var shriek = 
{ path: "M 8.5 21.5 A 2.5 2.5 0 0 1 8.5 16.5  A 2.5 2.5 0 1 1 8.5 21.5  "+
        "M 8.5 14.5   4.5 5.5  A 4.5 4.5 0 1 1 12.5 5.5 L 8.5 14.5" ,
    fillColor: '#FCDFFF',
    fillOpacity: 0.7,
    strokeColor: 'purple',
    strokeWeight: 1.5,
    anchor: new google.maps.Point(8.5,21.5),
} ;
// icon for arrow representing current waypoint
var arrow = 
{ path: "M 6 9  0 15  6 0  12 15 z",
  fillColor: 'black',
  fillOpacity: 1,
  strokeColor: 'black',
  strokeWeight: 0,
  anchor: new google.maps.Point(6,6),
  rotation: 0,
  clickable: false 
} ;
// icon for concentric circles representing draggable waypoint
var concircle = 
{ path: "M 6 0  A 6 6 0 1 0 6 12  A 6 6 0 1 0 6 0 M 6 3  " +
        "A 3 3 0 1 0 6  9   A 3 3 0 1 0 6  3",
  fillColor: 'black',
  fillOpacity: 0,
  strokeColor: 'black',
  strokeWeight: 1,
  strokeOpacity: 1,
  anchor: new google.maps.Point(6,6),
  clickable: false 
} ;
// camera icon
var camera = 
{ path: "M 0.5 4   A 1.5 1.5 0 0 1 2 2.5   L  5.5 2.5   7 0.5  11 0.5   " + 
        "12.5 2.5   14 2.5   A 1.5 1.5 0 0 1  16 3   L 20 7   16 11 " +
        "A 1.5 1.5 0 0 1 15 11.5   L 2 11.5   A 1.5 1.5 0 0 1 0.5 10  z " + 
        "M 9 4  A 3 3 0 0 1 9 10   A 3 3 0 0 1 9 4 " ,  
  fillColor: '#FCDFFF',
  fillOpacity: 0.7,
  strokeColor: 'purple',
  strokeWeight: 1.5,
  anchor: new google.maps.Point(21,7),
  clickable: false 
} ;
/* -------------------------------------------------------------------------- */
/*                                CONSTRUCTORS                                */
/* -------------------------------------------------------------------------- */

function dotpath(a,b)
{ this.path = [a,b] ;
  this.cursor = 'default' ;
  this.geodesic = true ;
  this.strokeOpacity = 0 ;
  this.icons = [ { icon:   { path: 'M 0 0 L 1 0',strokeOpacity:1,scale:1 } , 
                   offset: '1px' , 
                   repeat: '4px' 
                  } ] ;
  this.zIndex = 0 ;
}
/* -------------------------------------------------------------------------- */

function linepath(s0,start,end,colour)
{ var i,len=(start<0?segments[s0].data.length:end-start) ; 
  this.path = new Array(len) ; 
  if(start<0) for(i=0;i<len;i++) this.path[i] = segments[s0].data[i].pos ;
  else for(i=0;i<len;i++) this.path[i] = segments[s0].data[start+i].pos ; 
  this.clickable = 'false' ;
  this.cursor = 'default' ;
  this.geodesic = true ;
  this.strokeColor = colour ;
  this.strokeOpacity = 1.0 ;
  this.strokeWeight = 2 ;
  this.zIndex = 0 ;
}
/* -------------------------------------------------------------------------- */

function listinfo()
{ this.list = [] ; this.uri = this.scale = this.status = this.type = null ; }

/* ------------------------------- data structure --------------------------- */

// I found the following logic quite hard to get right. A (non-null) label
// satisfies the following constraints:
// o. the marker is non-null
// o. the map may be null, and if it is null the title may also be null and the
//    icon may be arbitrary
// o. if the type is null, the map is null
// o. the map is null if and only if the clickhandler is inactive
// the same constraints apply (mutatis mutandis) to the photo, so it follows 
// that the label may have a null map and the photo non-null (and vice versa)
//    we therefore conclude that a label must be in one of 3 states:
// o. type null, map null, handlers inactive, but marker non-null
// o. type non-null, map null, handlers inactive, marker non-null
// o. type non-null, map non-null, handlers active, marker non-null
// the state in which type is non-null and map is null is applied to all 
// labels in a segment being deleted (we preserve the information in the 
// action list but don't want the label to be displayed)

function datatype(pos,h)
{ this.pos = pos ; 
  this.h = h ; 
  this.marker = this.photomarker = this.type = this.photo = null ;
  this.caption = '' ;  
  this.clickhandler = this.righthandler = this.photohandler = null ; 
}
// member functions
datatype.prototype.geticon = function()
{ if(this.type=='Left')  return turnleft ; 
  else if(this.type=='Straight') return straighton ; 
  else if(this.type=='Right') return turnright ; 
  else if(this.type=='Danger') return shriek ; 
  else return flagsign ; 
} ;
datatype.prototype.setlabelmap = function(m) 
{ if(m==null||this.type==null) m = null ; else m = map ; 
  if(m==null&&this.marker==null) return ;
  this.marker.setMap(m) ; 
  if(m==null&&this.clickhandler!=null)
  { google.maps.event.removeListener(this.clickhandler) ;
    google.maps.event.removeListener(this.righthandler) ;
    this.clickhandler = this.righthandler = null ; 
  }
  if(m!=null&&this.clickhandler==null)
  { this.clickhandler = this.marker.addListener('click',selpoint) ;
    this.righthandler = this.marker.addListener('rightclick',labelcycle) ;
  }
} ;
datatype.prototype.setphotomap = function(m) 
{ if(m==null||this.photo==null) m = null ; else m = map ; 
  if(m==null&&this.photomarker==null) return ;
  this.photomarker.setMap(m) ;
  if(m==null&&this.photohandler!=null) 
  { google.maps.event.removeListener(this.photohandler) ;
    this.photohandler = null ; 
  }
  if(m!=null&&this.photohandler==null) 
    this.photohandler = this.photomarker.addListener('click',selpoint) ;
} ;
datatype.prototype.setlabel = function(t,c) 
{ this.type = t ; 
  if(t==null) { if(this.marker!=null) this.setlabelmap(null) ; return ; } 
  if(this.marker==null) this.marker = new google.maps.Marker
      ({ position:this.pos,map:map,icon:this.geticon(),title:c,zIndex:1 }) ;
  else { this.marker.setIcon(this.geticon()) ; this.marker.setTitle(c) ; }
  this.setlabelmap(map) ; 
} ;
datatype.prototype.setphoto = function(p) 
{ this.photo = p ; 
  if(p==null) { if(this.photomarker!=null) this.setphotomap(null) ; return ; } 
  if(this.photomarker==null) this.photomarker = new google.maps.Marker
      ({ position:this.pos,map:map,icon:camera,title:p,zIndex:1 }) ;
  else this.photomarker.setTitle(p) ; 
  this.setphotomap(map) ; 
} ;
datatype.prototype.setpos = function(p) 
{ this.pos = p ; 
  if(this.type!=null) this.marker.setPosition(p) ; 
  if(this.photo!=null) this.photomarker.setPosition(p) ; 
} ;
datatype.prototype.setmap = function(m) 
{ this.setlabelmap(m) ; this.setphotomap(m) ; } ;

datatype.prototype.settype = function(t) 
{ this.type = t ; this.marker.setIcon(this.geticon()) ; } ;

/* -------------------------------------------------------------------------- */
/*                             UTILITY FUNCTIONS                              */
/* -------------------------------------------------------------------------- */

function xmlfloat(x) { return parseFloat(x.childNodes[0].textContent) ; }

function dist(x,y)
{ return google.maps.geometry.spherical.computeDistanceBetween(x,y) ; }

function interp(x,y,lamda)
{ return google.maps.geometry.spherical.interpolate(x,y,lamda) ; }

function bearing(x,y)
{ return google.maps.geometry.spherical.computeHeading(x,y) ; }

function angle(x,y)
{ return google.maps.geometry.spherical.computeHeading(x,y)*Math.PI/180 ; }

/* --------------------------- button handlers  ----------------------------- */

function greyout(btn)
{ if(btn.active==0) return 0 ; 
  btn.btn.setAttribute('src',btn.greyimg) ; 
  btn.ui.removeEventListener('click',btn.handler) ;
  if(btn==penbtn) btn.ui.removeEventListener('contextmenu',photoprompt) ;
  btn.ui.style.cursor = 'default' ; 
  btn.active = 0 ; 
  return 1 ; 
}
function blackout(btn)
{ if(btn.active) return ; 
  btn.btn.setAttribute('src',btn.blackimg) ; 
  btn.ui.addEventListener('click',btn.handler) ;
  if(btn==penbtn) btn.ui.addEventListener('contextmenu',photoprompt) ; 
  btn.ui.style.cursor = 'pointer' ; 
  btn.active = 1 ; 
}
/* ------------------------ enter/exit full screen -------------------------- */

// Find the right method, call on correct element
function enterFullscreen() 
{ infowindow.close() ; 
  if(document.documentElement.requestFullscreen) 
    document.documentElement.requestFullscreen() ;
  else if(document.documentElement.mozRequestFullScreen) 
    document.documentElement.mozRequestFullScreen() ;
  else if(document.documentElement.webkitRequestFullscreen) 
    document.documentElement.webkitRequestFullscreen() ;
  else if(document.documentElement.msRequestFullscreen) 
    document.documentElement.msRequestFullscreen() ;
}
function exitFullscreen() 
{ infowindow.close() ; 
  if(document.exitFullscreen) document.exitFullscreen() ;
  else if(document.mozCancelFullScreen) document.mozCancelFullScreen() ;
  else if(document.webkitExitFullscreen) document.webkitExitFullscreen() ;
}
/* ---------------- find the index of a given photo name -------------------- */

function findimg(id)
{ var i ; 
  for(i=0;i<imginfo.list.length;i++) 
    if(imginfo.list[i].name!=undefined&&imginfo.list[i].name==id) return i ; 
  return -1 ; 
}
/* ------------------- message warning of unsaved changes ------------------- */

function unsavedmsg(ok)
{ var msg , len = unsavedchanges.length , i ; 
  if(len==0) return null ; 
  msg = 'You have ' + len + ' unsaved change' + (len==1?"":'s') ; 
  if(len<=3) for(i=0;i<len;i++)
     msg += (i?',':' (') + unsavedchanges[i] + (i==len-1?')':'') ;
  msg += '\nIf you hit ' + (ok?'[OK]':'[Leave page]') ; 
  return msg + (len==1?' this change':' these changes') + ' will be lost.' ;
}
/* -------------------------------------------------------------------------- */
/*page*/
/* --------------- selpoint: choose the clicked waypoint  ------------------- */

function selpoint(event)
{ var i,j,closest,d,mindist,s0=selected[0],s1=segments[s0].data.length ; 
  if(dragging) return ; 
  var flag = (infowindow.close()=='wpinfo') && (shift==0) ; 

  if(shift)
  { insert(s0,s1,1) ;
    segments[s0].data[s1].setpos(event.latLng) ;
    lookupalt(event.latLng,s0,s1) ; 
    redrawconnect(s0,s1) ;
    done(['move',s0,s1,event.latLng,event.latLng,1]) ; 
  }
  else for(s1=s0=-1,i=0;i<segments.length;i++) 
    for(j=0;j<segments[i].data.length;j++)
  { d = dist(segments[i].data[j].pos,event.latLng) ;
    if(s0<0||d<mindist) { s0 = i ; s1 = j ; mindist = d ; } 
  }
  walkto(s0,s1,flag,0) ; 
}
/* --------------------------------- walkto --------------------------------- */

// draw a selection point (and possibly an info box) at [s0,s1], bringing up
// a wpinfo window if flag != 0 and centring on the new point if opt != 0

function walkto(s0,s1,flag,opt) 
{ var s,i,ind,imguri=null,excuse ;  
  var datum = segments[s0].data[s1] , pos = datum.pos ; 
  selected = [s0,s1] ; 
  // if(opt) map.panTo(pos) ; else
  map.panToBounds(new google.maps.LatLngBounds(pos,pos)) ; 
  drawsel(0) ; 
  if(flag||(datum.type==null&&datum.photo==null)) 
  { if(flag) wpinfo() ; return ; }

  if(datum.type!=null&&datum.photo!=null) s = textbox ; else s = finalbox ; 
  if(datum.type!=null&&datum.type!='Generic') s += datum.type + ': ' ;
  if(datum.type!=null)
    s += datum.marker.title+' ['+active+ '"labelprompt()">Edit</span>'+']' ;
  if(datum.type!=null&&datum.photo!=null) s += '</div>' + finalbox ;
  if(datum.photo!=null) 
  { if(imginfo.status=='ready'&&(i=findimg(datum.photo))>=0)
    { imguri = reluri(imginfo.uri,datum.photo) + 'thumb.jpg' ; 
      s += '<img src="'+imguri+'" width='+imginfo.list[i].thumbshape[0]+
      ' height='+imginfo.list[i].thumbshape[1]+'><br><b>'+datum.photo+'</b>: ' ;
    }
    else 
    { if(imginfo.status=='ready') excuse = 'missing' ; 
      else if(imginfo.status=='waiting') excuse = 'not available' ; 
      else excuse = 'not supplied' ; 
      s += 'Photo: '+datum.photo+' ('+excuse+') ' ;
    }
    s += '['+active+ '"photoprompt(null)">Edit</span>'+']' ;
    if(imguri!=null) s += ' : ['+active+ '"phinfo('+i+')">Info</span>'+']' +
                          ' : ['+active+ '"enlarge('+i+')">Enlarge</span>'+']' ;
  }
  infowindow.open(s+'</div>',pos,'walking') ; 
}
/* -------------------------------------------------------------------------- */
/*page*/
/* -------------------------- keystroke handler  ---------------------------- */

function walk(e)
{ var s0=selected[0],s1=selected[1],slast,flag ;

  if(e.keyCode==16) shift = 1 ; 
  if(e.keyCode==40) { map.panTo(segments[s0].data[s1].pos) ; return ; } 

  if(infowindow.close()=='wpinfo') flag = 1 ; else flag = 0 ; 

  if(e.keyCode==32) { selclick() ; return ; } // space
  if(e.keyCode==13) // return 
  { if(dragging) undraggit() ; else if(s0>=0) draggit(0) ; return ; } 
  if(dragging) return ; 

  if(e.keyCode==8||e.keyCode==46) // delete/backspace
  { e.preventDefault() ; if(binbtn.active) discard() ; return ; }
  if(e.keyCode==9) { e.preventDefault() ; inswp(1) ; return ; } // tab

  if(e.keyCode==39) // forwards
  { e.preventDefault() ;  
    if(s1<segments[s0].data.length-1) s1 += 1 ; 
    else { s0 += 1 ; if(s0==segments.length) s0 = 0 ; s1 = 0 ; } 
  }
  else if(e.keyCode==37) // backwards 
  { e.preventDefault() ;  
    if(s1>0) s1 -= 1 ; 
    else 
    { s0 -= 1 ; 
      if(s0<0) s0 = segments.length-1 ; 
      s1 = segments[s0].data.length-1 ; 
    } 
  }
  else return ; 
  walkto(s0,s1,flag,1) ;
}
/* ---------------------------- relative uri  ------------------------------- */

function reluri(u1,u2) 
{ var last = u1.lastIndexOf('/') ; 
  if(last<0) return u2 ; 
  u1 = u1.substring(0,last) ; 

  while(u2.substring(0,3)=='../')
  { last = u1.lastIndexOf('/') ; 
    if(last<0) return u2 ; 
    u2 = u2.substring(3) ; 
    u1 = u1.substring(0,last) ; 
  }
  return u1 + '/' + u2 ; 
}
/* ------------------------------- getbtnpos -------------------------------- */

function getbtnpos(btnno)
{ var bounds=map.getBounds(),sw,ne,lat,lon,lam ;
  sw = bounds.getSouthWest() ; 
  ne = bounds.getNorthEast() ; 
  lam = 52.0 / window.innerHeight ; 
  lat = lam*ne.lat() + (1-lam)*sw.lat() ; 
  lam = 0.5 + (btnno*32-112.0)/window.innerWidth ;
  lon = lam*ne.lng() + (1-lam)*sw.lng() ;
  return new google.maps.LatLng(lat,lon) ; 
}
/* ----- unambig: does the selected waypoint determine a unique segment? ---- */

function unambig() // does the selected waypoint determine a unique segment? 
{ var s0=selected[0],s1=selected[1] ; 
  if(segments.length==1) return 1 ; 
  if( ( s0==segments.length-1 || s1!=segments[s0].data.length-1 ||
        ! segments[s0].data[s1].pos.equals(segments[s0+1].data[0].pos) ) && 
      ( s0==0 || s1!=0 ||
        ! segments[s0].data[s1].pos.equals
          (segments[s0-1].data[segments[s0-1].data.length-1].pos) ) )
    return 1 ; 
  else return 0 ;
}
/* --------------------- undraw & redraw segments  -------------------------- */

function undraw(i) 
{ segments[i].route.setMap(null) ; 
  if(segments[i].clickhandler!=null) 
  { google.maps.event.removeListener(segments[i].clickhandler) ;
    segments[i].clickhandler = null ; 
  }
}
function redraw(i) { undraw(i) ; draw(i) ; }

function recolour(i) 
{ if(i&1) segments[i].route.setOptions({strokeColor:"#ff9999"}) ; 
  else segments[i].route.setOptions({strokeColor:"#ff0000"}) ; 
}
function obliterate(s0) // undraw route and all labels
{ var i ;
  for(i=0;i<segments[s0].data.length;i++) segments[s0].data[i].setmap(null) ; 
  undraw(s0) ; 
  disconnect(s0-1) ; 
  disconnect(s0) ; 
}
/* ----------------------------- draw segments ------------------------------ */

function draw(i)
{ var colour ; 
  if(i&1) colour = "#ff9999" ; else colour = "#ff0000" ; 
  segments[i].route = new google.maps.Polyline(new linepath(i,-1,0,colour)) ;
  segments[i].route.setMap(map) ;
  segments[i].clickhandler = 
    google.maps.event.addListener(segments[i].route,"click",selpoint) ;
}
/* ----------------------- connect and disconnect segments ------------------ */

function disconnect(i) 
{ if(i<0||i>=segments.length-1||segments[i].dots==null) return ;
  segments[i].dots.setMap(null) ; 
  if(segments[i].dothandler!=null) 
  { google.maps.event.removeListener(segments[i].dothandler) ;
    segments[i].dothandler = null ; 
  }
}
function reconnect(i) { disconnect(i) ; connect(i) ; }

function connect(i)
{ if(i<0||i>=segments.length-1) return ; 
  var opos = segments[i].data[segments[i].data.length-1].pos ; 
  var npos = segments[i+1].data[0].pos ;
  if(opos.equals(npos)) return ;
  segments[i].dots = new google.maps.Polyline(new dotpath(opos,npos)) ;
  segments[i].dots.setMap(map) ;
  segments[i].dothandler = 
    google.maps.event.addListener(segments[i].dots,"click",selpoint) ;
}
function redrawconnect(s0,s1) 
{ redraw(s0) ; 
  if(s1==0) reconnect(s0-1) ; 
  if(s1=segments[s0].data.length-1) reconnect(s0) ; 
}
/* -------------------------------------------------------------------------- */
/*page*/
/* ---------------------- draw the selection point -------------------------- */

// note: there's no point in allowing clicking on a marker because the 
// event position is always the marker position rather than the click position

function drawsel(opt)
{ var ind,clen,s0=selected[0],s1=selected[1],pos=segments[s0].data[s1].pos ;
  if(opt) reprofile() ; 
  clen = segments[s0].data.length ; 
  if(clen==1) arrow.rotation = 90 ; 
  else
  { if(s1==clen-1) ind = s1-1 ; else ind = s1 ;
    arrow.rotation = 
      bearing(segments[s0].data[ind].pos,segments[s0].data[ind+1].pos) ;
  }

  if(sel.marker==null) sel.marker = new google.maps.Marker
      ( { position:pos, map:map, cursor:'default', icon:arrow , zIndex:2 } ) ;
  else // avoid unnecessary redraws
  { if(arrow.rotation!=sel.orientation) sel.marker.setIcon(arrow) ;
    if(!pos.equals(sel.marker.getPosition())) sel.marker.setPosition(pos) ; 
  }
  sel.orientation = arrow.rotation ; 
  procur() ; 

  blackout(penbtn) ;
  if(segments.length>1&&unambig()) blackout(binbtn) ; else greyout(binbtn) ;  
  if(s1!=0&&s1!=clen-1) blackout(scissorsbtn) ; else greyout(scissorsbtn) ;
}
/* ------------- selclick: respond to click of cursor button  --------------- */

function selclick()
{ mouseopt = 1-mouseopt ; 
  infowindow.close() ;  
  if(mouseopt)
  { map.setOptions({draggable:false, draggableCursor:'default'}) ;
    cursorbtn.btn.setAttribute('src','hand.png') ; 
    clickhandle = google.maps.event.addListener(map,"click",selpoint) ;
  }
  else
  { map.setOptions({draggable:true, draggableCursor:''}) ;
    cursorbtn.btn.setAttribute('src','arrow.png') ; 
    google.maps.event.removeListener(clickhandle) ;
  }
}
/* ------------------------------ setthumbshape ----------------------------- */

function setthumbshape(thumbscale) 
{ var ind ; 
  imginfo.scale = thumbscale ; 
  if(imginfo.status=='ready') for(ind=0;ind<imginfo.list.length;ind++) 
    if(imginfo.list[ind].name!=undefined) 
      if(imginfo.list[ind].thumbshape==undefined) 
        imginfo.list[ind].thumbshape = 
          [ imginfo.list[ind].shape[0]/thumbscale , 
            imginfo.list[ind].shape[1]/thumbscale] ; 
}
/* -------------------------------------------------------------------------- */
/*page*/
/* -------------------------------------------------------------------------- */
/*                FUNCTIONS TO GENERATE THE INITIAL MAP                       */
/* -------------------------------------------------------------------------- */

function genpage() 
{ var thispage=document.URL,xhttp,quotind,plusind,listxhttp ;
  imginfo = new listinfo() ; 
  imgdiv = null ; 
  
  window.onload = function() 
  { window.addEventListener("beforeunload",function(e) 
    { var msg = unsavedmsg(0) ; 
      if(msg==null) return undefined ; 
      (e || window.event).returnValue = msg ; //Gecko + IE
      return msg ; //Gecko + Webkit, Safari, Chrome etc. (msg is ignored by ffx)
    } ) ;
  } ;

  body = document.getElementsByTagName("body")[0] ;
  while(body.childNodes.length>0) 
    body.removeChild(body.childNodes[body.childNodes.length-1]) ;
  mapdiv = document.createElement('div') ; 
  mapdiv.setAttribute('id','map') ; 
  mapdiv.setAttribute('style','width:100%;height:100%') ; 
  body.appendChild(mapdiv) ;

  if((quotind=thispage.indexOf('?'))>=0)
  { thispage = thispage.substring(quotind+1) ; 
    if((plusind=thispage.indexOf('+'))>0) 
    { getlist(thispage.substring(plusind+1),'uri') ; 
      thispage = thispage.substring(0,plusind) ;
    }
    else if(thispage.substring(thispage.length-3)=='.js')
    { getlist(thispage,'uri') ; 
      mapdiv.appendChild(filedialogue(0)) ; 
      return ; 
    }
    xhttp = new XMLHttpRequest() ;
    xhttp.onreadystatechange = function() 
    { if(xhttp.readyState==4&&xhttp.status==200) 
      { var x = parser.parseFromString(xhttp.responseText,"application/xml") ;
        render(x,thispage,0) ; 
      }
    }
    xhttp.open("GET",thispage,true) ;
    xhttp.send() ;
  }
  else mapdiv.appendChild(filedialogue(0)) ; 
}
/* -------------------------------- getlist --------------------------------- */

function getlist(uri,imgtype) 
{ var xhttp = new XMLHttpRequest(),list=null,sizes=null,pixname=null ; 
  imginfo.status = 'waiting' ; 
  imginfo.uri = uri ;
  imginfo.type = imgtype ; 
  xhttp.onreadystatechange = function() 
  { if(xhttp.readyState==4&&xhttp.status==200) 
    { sizes = 3 ; 
      eval(xhttp.responseText) ; 
      imginfo.list = list ; 
      imgsizes = sizes ; 
      imgpix = pixname ; 
      imginfo.status = 'ready' ; 
      setthumbshape(imginfo.scale) ; 
    }
  }
  xhttp.open("GET",uri,true) ;
  xhttp.send() ;
}
/* ----------------------------- file dialogue ------------------------------ */

function filedialogue(overwrite)
{ var input = document.createElement('input') ; 
  var para = document.createElement('p') ; 
  para.appendChild(document.createTextNode
                             ('\u00a0\u00a0\u00a0Select TCX/GPX file: ')) ; 
  input.setAttribute('type','file') ; 
  input.setAttribute('accept','.tcx,.gpx') ; 
  input.addEventListener('change',function(e)
  { reader = new FileReader() ;
    reader.onload = function(e) 
    { var xmldoc = parser.parseFromString(reader.result,"application/xml") ;
      render(xmldoc,input.files[0].name,overwrite) ; 
    } 
    reader.readAsText(input.files[0]) ;	
  } ) ;
  para.appendChild(input) ; 
  return para ;
}
/* --------------------- set up the map and buttons ------------------------- */

function render(xmldoc,filename,overwrite) 
{ var i,opts,centre,lat,lon,minlon,maxlon,minlat,maxlat,newseg,s0,bounds,sw,ne ;

  infowindow.close() ;
  document.onkeydown = walk ;
  document.onkeyup = function(e) { if(e.keyCode==16) shift = 0 ; }

  if(overwrite)
  { for(i=0;i<segments.length;i++) obliterate(i) ; 
    unprofile() ; 
    segments = [] ; 
    if(imginfo.type=='tcx') imginfo = new listinfo() ; 
  } 

  s0 = segments.length ; 
  if(s0==0)
  { sel = { marker:null, orientation: null } ; 
    pending = [] ; 
    xpending = [] ; 
    actions = [] ; 
    unsavedchanges = [] ; 
    nactions = dragging = 0 ; 
    loadno = -1 ; 
    prox = proa = pron = null ; 
    altdiv = curcan = curdiv = cmap = smap = curhandle = null ;
  }

  // set up segments
  newseg = gensegment(xmldoc,filename) ; 
  segments.push(newseg[0]) ; 
  if(s0==0)
  { if(newseg[1].title!=null) settitle(newseg[1].title) ; 
    else settitle('Untitled Route') ; 
  }
  actions[nactions++] = 
    [ 'load' , s0 , newseg[0].data.slice() , loadno , newseg[1] ] ;
  loadno = nactions-1 ; 
  if(!newseg[1].optim.already) optimaction(segments.length-1,defparms,0) ; 

  // find max and min lat and long
  for(i=0;i<segments[s0].data.length;i++)
  { lat = segments[s0].data[i].pos.lat() ; 
    lon = segments[s0].data[i].pos.lng() ; 
    if(i==0||lon<minlon) minlon = lon ; 
    if(i==0||lon>maxlon) maxlon = lon ; 
    if(i==0||lat<minlat) minlat = lat ; 
    if(i==0||lat>maxlat) maxlat = lat ; 
  }

  if(s0==0)
    centre = new google.maps.LatLng((minlat+maxlat)/2,(minlon+maxlon)/2) ;

 if(map==null) // all this only done on first call
 { opts = { zoom: 22,
            center: centre,
            scaleControl: true,
            rotateControl: false,
            streetViewControl: false,
            mapTypeId: google.maps.MapTypeId.TERRAIN,
            disableDoubleClickZoom: true,
            styles: [ { "featureType": "poi", 
                        "stylers": [{ "visibility": "off" }]
                       } ],
            mapTypeControl:true,
            mapTypeControlOptions: 
              { style:google.maps.MapTypeControlStyle.HORIZONTAL_BAR }, 
            mapTypeIds: [ google.maps.MapTypeId.ROADMAP,
                          google.maps.MapTypeId.TERRAIN,
                          google.maps.MapTypeId.SATELLITE
                        ]
          } ;

    map = new google.maps.Map(mapdiv,opts) ;

    // set up buttons
    setbtn = genbutton('settings') ;
    cursorbtn = genbutton('cursor') ;
    cursorbtn.ui.addEventListener('click',selclick) ;
    scissorsbtn = genbutton('scissors') ;
    binbtn = genbutton('bin') ;
    penbtn = genbutton('pen') ;
    undobtn = genbutton('undo') ;
    redobtn = genbutton('redo') ;
    dlbtn = genbutton('dl') ;
    selclick() ;

    // navigate using the arrow keys
  }
  else if(s0!=0)
  { bounds = map.getBounds() ; 
    sw = bounds.getSouthWest() ; 
    ne = bounds.getNorthEast() ; 
    if(sw.lat()<minlat) minlat = sw.lat() ;
    if(sw.lng()<minlon) minlon = sw.lng() ;
    if(ne.lat()>maxlat) maxlat = ne.lat() ;
    if(ne.lng()>maxlon) maxlon = ne.lng() ;
  }

  map.fitBounds(new google.maps.LatLngBounds(
                  new google.maps.LatLng(minlat,minlon),
                  new google.maps.LatLng(maxlat,maxlon))) ; 

  for(i=0;i<segments[s0].data.length;i++) segments[s0].data[i].setmap(map) ;

  if(nactions>1) donesomething() ; // specifically, done loading & optimisation
  else actions.length = nactions ; // load with no optimisation hence no undo

  if(s0==0) { selected = [0,0] ; drawsel(1) ; } else greyout(dlbtn) ; 
  draw(s0) ;
  connect(s0-1) ; 
  connect(s0) ; 
  reprofile() ; 
}
/* -------------------------------------------------------------------------- */
/*page*/
/* ------------------------------- settitle --------------------------------- */

function settitle(newtitle) 
{ routetitle = newtitle ; 
  var h = document.getElementsByTagName('title')[0] ;
  while(h.firstChild) h.removeChild(h.firstChild) ;
  h.appendChild(document.createTextNode(routetitle)) ;
}
/* ------------------------------- retitle ---------------------------------- */

function retitle() 
{ infowindow.close() ; 
  var response = window.prompt("Modify title:",routetitle) ; 
  if(response==null) return ; else response = response.substring(0,15) ; 
  if(response==routetitle) return ; 
  actions[nactions++] = ['edittitle',routetitle,response] ; 
  actions.length = nactions ; 
  blackout(undobtn) ; 
  greyout(redobtn) ; 
  settitle(response) ;
}
/* ------------------------------- genbutton -------------------------------- */

function genbutton(name)
{ var u,v,w,b,g,k,h=null,div=document.createElement('div'),act ;
  u = document.createElement('div') ;
  u.style.backgroundColor = '#ffffff' ;
  u.style.border = '2px solid #ffffff' ;
  u.style.borderRadius = '3px' ;
  u.style.boxShadow = '0 2px 6px rgba(0,0,0,.3)' ;
  if(name=='dl'||name=='settings'||name=='cursor') u.style.cursor = 'pointer' ;
  else u.style.cursor = 'default' ;
  u.style.marginBottom = '12px' ;
  if(name!='dl') u.style.marginRight = '4px' ;
  u.style.textAlign = 'center' ;
  div.appendChild(u) ;

  b = document.createElement('img') ;
  g = name + '.png' ;
  if(name=='scissors') { h = snip ; div.index = 3 ; } 
  else if(name=='bin') { h = discard ; div.index = 4 ; }
  else if(name=='pen') { h = labelprompt ; div.index = 5 ; }
  else if(name=='undo') { h = undo ; div.index = 6 ; }
  else if(name=='redo') { h = redo ; div.index = 7 ; }
  else if(name=='dl') { h = dl ; div.index = 8 ; }
  else if(name=='settings') { h = popup ; div.index = 1 ; }
  else if(name=='cursor') { g = 'arrow.png' ; k = 'hand.png' ; div.index = 2 ; }
  if(name!='cursor') { k = 'black' + g ; g = 'grey' + g ; } 
  if(name=='dl'||name=='settings'||name=='cursor') b.setAttribute('src',k) ; 
  else b.setAttribute('src',g) ;
  b.setAttribute('width',24) ; 
  b.setAttribute('height',24) ; 
  u.appendChild(b) ;

  map.controls[google.maps.ControlPosition.BOTTOM_CENTER].push(div) ;

  if(name=='dl'||name=='settings') u.addEventListener('click',h) ; 
  if(name=='dl'||name=='settings') act = 1 ; else act = 0 ; 

  return { ui:u , btn:b , active:act , greyimg:g , blackimg:k , handler:h } ; 
}
/* -------------------------------------------------------------------------- */
/*page*/
/* --------------- construct a segment from an xml document ----------------- */

function genseg(a) 
{ this.data = a ; 
  this.route = this.routehandler = this.dots = this.dothandler = null ; 
}
function gensegment(xmldoc,filename)
{ var xmlcoords,nodeno,tcx,type,mindist,lat,lon,i,j,segment,node,alt,pos ;
  var ind,caption,data=[],alreadyoptimised=0,photo ; 
  var props = { title: null ,
                inputlen: null , 
                optim: { already: 0, ndel: 0, origlen: 0, parms: null } 
               }

  tcx = filename.length ; 
  if(filename.substring(tcx-4,tcx)=='.tcx') tcx = 1 ; 
  else if(filename.substring(tcx-4,tcx)=='.gpx') tcx = 0 ; 
  else { alert(filename+' is neither .tcx nor .gcx') ; throw '' ; }

  if(tcx) 
  { // optimised?
    xmlcoords = xmldoc.getElementsByTagName('Optimised') ;
    if(xmlcoords.length)
    { props.optim.already = 1 ; 
      props.optim.origlen = parseInt(xmlcoords[0].getAttribute('from')) ; 
      props.optim.ndel = 
        props.optim.origlen - parseInt(xmlcoords[0].getAttribute('to')) ; 
      props.optim.parms = 
        { tol: parseFloat(xmlcoords[0].getAttribute('tol')) ,
          maxsep: parseFloat(xmlcoords[0].getAttribute('maxsep')) ,
          wppenalty: parseFloat(xmlcoords[0].getAttribute('wppenalty')) ,
          vweight: parseFloat(xmlcoords[0].getAttribute('vweight')) } ; 
    }

    // photo list?
    if(imginfo.uri==null||imginfo.type=='tcx')
    { xmlcoords = xmldoc.getElementsByTagName('PhotoList') ;
      if(xmlcoords.length)
      { imginfo = new listinfo() ; 
        getlist(xmlcoords[0].getAttribute('src'),'tcx') ; 
      }
    }

    // route title
    xmlcoords = xmldoc.getElementsByTagName('Name') ;
    for(i=0;i<xmlcoords.length&&props.title==null;i++)
      if( xmlcoords[i].parentNode.nodeName=='Course' ||
          xmlcoords[i].parentNode.nodeName=='Lap') 
      props.title = xmlcoords[i].childNodes[0].textContent ; 

    // loop over the track points to get the coords
    xmlcoords = xmldoc.getElementsByTagName('Trackpoint') ;
    for(i=0;i<xmlcoords.length;i++)
    { lat = lon = alt = photo = null ; 
      for(nodeno=0;nodeno<xmlcoords[i].childNodes.length;nodeno++)
      { node = xmlcoords[i].childNodes[nodeno] ;

        if(node.nodeName=='AltitudeMeters') alt = xmlfloat(node) ; 
        else if(node.nodeName=='Position')
          for(j=0;j<node.childNodes.length;j++)
        { if(node.childNodes[j].nodeName=='LatitudeDegrees') 
            lat = xmlfloat(node.childNodes[j]) ; 
          else if(node.childNodes[j].nodeName=='LongitudeDegrees') 
            lon = xmlfloat(node.childNodes[j]) ;
        }
        else if(node.nodeName=='Extensions')
          for(j=0;j<node.childNodes.length;j++)
            if(node.childNodes[j].nodeName=='Photo') 
              photo = node.childNodes[j].childNodes[0].textContent ;
      }
      if(lat==null||lon==null) continue ; 
      data[data.length] = new datatype(new google.maps.LatLng(lat,lon),alt) ; 
      if(photo!=null) data[data.length-1].setphoto(photo) ; 
    }
  }
  else // gpx
  { props.title = 
      xmldoc.getElementsByTagName('name')[0].childNodes[0].textContent ;

    // loop over the track points to get the coords
    xmlcoords = xmldoc.getElementsByTagName('trkpt') ;
    if(xmlcoords.length==0)
      xmlcoords = xmldoc.getElementsByTagName('rtept') ;
    for(i=0;i<xmlcoords.length;i++)
    { lat = parseFloat(xmlcoords[i].getAttribute('lat')) ; 
      lon = parseFloat(xmlcoords[i].getAttribute('lon')) ; 

      for(alt=null,nodeno=0;nodeno<xmlcoords[i].childNodes.length;nodeno++)
        if(xmlcoords[i].childNodes[nodeno].nodeName=='ele')
          alt = xmlfloat(xmlcoords[i].childNodes[nodeno]) ; 
      data[data.length] = new datatype(new google.maps.LatLng(lat,lon),alt) ; 
    }
  }

  for(i=0;i<data.length;i++) if(data[i].h==null) 
  { alert(data[i].pos+' has no altitude... unable to proceed') ; throw '' ; } 
  props.inputlen = data.length ; 

  segment = new genseg(data) ; 

  // loop over the course points to get the labels
  if(tcx) xmlcoords = xmldoc.getElementsByTagName('CoursePoint') ;
  else xmlcoords = xmldoc.getElementsByTagName('wpt') ;
  for(i=0;i<xmlcoords.length;i++)
  { caption = type = lat = lon = null ;
    if(tcx) for(nodeno=0;nodeno<xmlcoords[i].childNodes.length;nodeno++)
    { node = xmlcoords[i].childNodes[nodeno] ;
      if(node.nodeName=='Name') caption = node.childNodes[0].textContent ; 
      else if(node.nodeName=='PointType') 
        type = node.childNodes[0].textContent ; 
      else if(node.nodeName=='Position')
        for(j=0;j<node.childNodes.length;j++)
      { if(node.childNodes[j].nodeName=='LatitudeDegrees') 
          lat = xmlfloat(node.childNodes[j]) ;
        else if(node.childNodes[j].nodeName=='LongitudeDegrees') 
          lon = xmlfloat(node.childNodes[j]) ;
      }
    }
    else
    { lat = parseFloat(xmlcoords[i].getAttribute('lat')) ; 
      lon = parseFloat(xmlcoords[i].getAttribute('lon')) ; 
      for(nodeno=0;nodeno<xmlcoords[i].childNodes.length;nodeno++)
      { node = xmlcoords[i].childNodes[nodeno] ;
        if(node.nodeName=='name') caption = node.childNodes[0].textContent ; 
        else if(node.nodeName=='type') type = node.childNodes[0].textContent ; 
      }
    }
    if(lat==null||lon==null||caption==null||type==null) 
    { alert('Badly formatted course point' ) ; throw '' ; }
    pos = new google.maps.LatLng(lat,lon) ; 
    for(j=0;j<data.length;j++) if(j==0||dist(pos,data[j].pos)<mindist) 
    { mindist = dist(pos,data[j].pos) ; ind = j ; } 
    segment.data[ind].setlabel(type,caption) ;
  }

  if(props.optim.origlen==0) props.optim.origlen = data.length ; 
  return [ segment , props ] ;
}
/* -------------------------------------------------------------------------- */
/*                             OPTIMISATION                                   */
/* -------------------------------------------------------------------------- */

function optimaction(segno,parms,force)
{ var s = segments[segno], result = optimise(s,parms) ; 
  var ndel = s.data.length - result.length ; 
  if((force==0&&ndel<s.data.length/2)||ndel==0) return 0 ; 
  actions[loadno][4].optim.ndel = ndel ; 
  actions[nactions++] = [ 'optimise' , segno , parms ] ; 
  segments[segno] = new genseg(result) ;
  actions[loadno][4].optim.parms = { tol: parms.tol , 
                                     maxsep: parms.maxsep , 
                                     wppenalty: parms.wppenalty , 
                                     vweight: parms.vweight 
                                   } ; 
  return 1 ; 
}
/* -------------------------------------------------------------------------- */

function optimprompt()
{ var msg = 'Enter 4 optimisation parms (tol, maxsep, wppenalty and vweight)' ; 
  var parmstr = defparms.tol + ' ' + 
                defparms.maxsep.toFixed(0) + ' ' +
                defparms.wppenalty.toFixed(0) + ' ' +
                defparms.vweight.toFixed(1) ;
  var parms,i ; 
  infowindow.close() ;  

  for(i=0;;i++)
  { newparms = prompt(msg,parmstr) ; 
    if(newparms==null) return ;
    if(newparms=='') { parms = defparms ; break ; }
    newparms = newparms.split(' ') ;
    if(newparms.length==0) { parms = defparms ; break ; }
    parms = { tol: parseFloat(newparms[0]) , 
              maxsep: parseFloat(newparms[1]) , 
              wppenalty: parseFloat(newparms[2]) , 
              vweight: parseFloat(newparms[3]) } ; 
    if( isNaN(parms.tol)==0 && isNaN(parms.maxsep)==0 && 
        isNaN(parms.wppenalty)==0 && isNaN(parms.vweight)==0 ) break ; 
    if(i==0) msg = '*** Illegal parms ***\n' + msg ; 
  }
  if(optimaction(segments.length-1,parms,1)) 
  { donesomething() ; draw(segments.length-1) ; } 
  routeinfo() ; 
}  
/* -------------------------------------------------------------------------- */
    
function optimise(s,parms)
{ var stk,nstk,stk2,clen=s.data.length,i,j,m,step=new Array(clen-1) ; 
  var opos,oalt,npos,nalt,ndatum,mpos,malt,arccentre,arctol,pathpos ; 
  var legal,theta,omega,x,y,hyp,tdist,maxtheta,mintheta,d,dver,od,odver,odash ; 
  var bearings,pi=Math.PI,tol=parms.tol ;

  stk = [ { data:[s.data[0]] , err:0 , pathpos:1 } ] ;
  for(i=0;i<clen-1;i++) step[i] = dist(s.data[i].pos,s.data[i+1].pos) ; 

  while(stk[0].pathpos<clen)
  { pathpos = stk[0].pathpos ;
    opos = s.data[pathpos-1].pos ;
    oalt = s.data[pathpos-1].h ; 
    // try extending to pathpos+i
    for(bearings=[],nstk=[],arctol=null,i=0;i<clen-pathpos;i++)
    { ndatum = s.data[pathpos+i] ; 
      npos = ndatum.pos ; 
      nalt = ndatum.h ; 
      if(i==0) hyp = step[pathpos-1] ;
      else if((hyp=dist(opos,npos))>parms.maxsep) break ; 
      omega = angle(opos,npos) ; 
      // find the min and max legal bearing
      if(hyp>tol) 
      { theta = Math.asin(tol/hyp) ; 
        if(arctol==null) { arccentre = omega ; arctol = theta ; } 
        else
        { odash = omega - arccentre ; 
          while(odash>pi) odash -= 2*pi ;
          while(odash<-pi) odash += 2*pi ;
          maxtheta = Math.min(arctol,odash+theta) ; 
          mintheta = Math.max(-arctol,odash-theta) ; 
          if(maxtheta<mintheta) break ; 
          arccentre += (maxtheta+mintheta) /2 ; 
          arctol     = (maxtheta-mintheta) /2 ;
        }
      } 
      bearings[i] = { hyp:hyp , omega:omega } ; 
      // see whether this breaches the max error on any intermediate point
      for(legal=1,od=odver=tdist=m=0;m<i;m++,od=d,odver=dver)
      { mpos = s.data[pathpos+m].pos ;
        malt = s.data[pathpos+m].h ; 
        x = bearings[m].hyp ; 
        theta = bearings[m].omega ; 
        d = x * Math.sin(theta-omega) ; 
        dver = 0 ;
        if(d*d<tol*tol&&oalt!=null&&nalt!=null&&malt!=null)  
        { y = hyp - x*Math.cos(theta-omega) ;
          y = Math.sqrt(d*d+y*y) ; 
          dver = parms.vweight * ( malt - (oalt*y+nalt*x)/(x+y) ) ; 
        }
        if(d*d+dver*dver>tol*tol) { legal = 0 ; break ; } 
        tdist += (step[pathpos-1+i]/3) * 
                 (d*d+d*od+od*od + dver*dver+odver*dver+odver*odver) ;
      }
      // if we emerge with 'legal' non-zero then we may advance to pathpos+i 
      // and tdist is the sum of squared errors
      if(legal) nstk.push 
      ( { data:     stk[0].data.concat([ndatum]) , 
          err:      stk[0].err + pi*tdist + parms.wppenalty , 
          pathpos:  stk[0].pathpos+i+1 
        } ) ; 
      if(ndatum.type!=null||ndatum.photo!=null) break ; 
    }  // end loop over i 
    for(stk2=[],i=1,j=0;i<stk.length||j<nstk.length;)
      if(i==stk.length) stk2.push(nstk[j++]) ; 
      else if(j==nstk.length||stk[i].pathpos<nstk[j].pathpos)  
        stk2.push(stk[i++]) ; 
      else if(stk[i].pathpos>nstk[j].pathpos) stk2.push(nstk[j++]) ; 
      else if(stk[i].err<nstk[j].err) { stk2.push(stk[i++]) ; j += 1 ; } 
      else { stk2.push(nstk[j++]) ; i += 1 ; } 
    stk = stk2 ; 
  }
  return stk[0].data ; 
}
/* -------------------------------------------------------------------------- */
/*page*/
/* -------------------------------------------------------------------------- */
/*            THE MAIN POPUP MENU AND THE FUNCTIONS IT GOVERNS                */
/* -------------------------------------------------------------------------- */

function popup()
{ var opts,pos,s0=selected[0],s1=selected[1] ; 
  infowindow.close() ;  

  if(dragging)
  { opts = finalbox + "Hit [return] when you've finished dragging.</div>" ;
    infowindow.open(opts,getbtnpos(0),'settings') ; 
    return ; 
  }

  // route options
  opts = textbox + active + '"routeinfo()">Route info</span><br>' ;
  if(altdiv==null) 
    opts += active + '"profile()">Show altitude profile</span><br>' ;
  else opts += active + '"unprofile()">Hide altitude profile</span><br>' ;
  opts += active + '"addload(1)">Load new route</span></div>' + textbox ; 

  // segment options
  if(unambig()) opts += active + '"revseg()">' ; else opts += inactive ;
  opts += 'Reverse segment</span><br>' ;
  opts += active + '"manualcal()">Calibrate segment altitudes</span><br>' ;  
  opts += active + '"addload(0)">Load route as a new segment</span></div>' ; 

  // waypoint options
  opts += textbox + active + '"wpinfo()">Waypoint info</span><br>' ;
  if(segments[selected[0]].data.length>1) opts += active + '"wpdel()">' ; 
  else opts += inactive ;
  opts += 'Delete waypoint</span><br>' ;
  opts += active + '"draggit(0)">Make waypoint draggable</span><br>' ;
  opts += active + '"inswp(1)">Insert draggable waypoint ahead</span><br>' ;
  opts += active + '"inswp(-1)">Insert draggable waypoint behind</span>' ;
  opts += '</div>'+finalbox+active ; 

  // tool options
  if( document.documentElement.requestFullscreen ||
      document.documentElement.mozRequestFullScreen ||
      document.documentElement.webkitRequestFullscreen ||
      document.documentElement.msRequestFullscreen)
  { if( (document.fullScreenElement && document.fullScreenElement !== null) 
     || (!document.mozFullScreenElement && !document.webkitFullScreenElement) )
      opts += '"enterFullscreen()">Enter full screen</span><br>' + active ; 
    else opts += '"exitFullscreen()">Leave full screen</span><br>' + active ; 
  }
  opts += '"help()">Help</span></div>' ; 

  infowindow.open(opts,getbtnpos(0),'settings') ; 
}
/* ------------------------------- calwork --------------------------------- */

function calwork(s0,y)
{ var i,s1 ; 
  for(s1=0;s1<segments[s0].data.length;s1++)
    if(segments[s0].data[s1].h!=null) segments[s0].data[s1].h += y ; 
  reprofile() ; 
}
/* -------------------------------------------------------------------------- */
/*page*/
/* ------------------------------ manualcal --------------------------------- */

function manualcal()
{ infowindow.close() ; 
  var x,y,s0=selected[0] ;
  x = prompt('Enter offset in metres to add to altidudes:') ;
  if(x==null) return ; 
  y = parseFloat(x) ; 
  if(isNaN(y)) { alert(x+' is not a number') ; return ; }
  calwork(s0,y) ; 
  done(['recal',s0,y]) ; 
}  
/* --------------------------------- help ----------------------------------- */

function help() 
{ var str ; 
  infowindow.close() ; 
  str = textbox + '<table cellpadding=0 cellspacing=0>' ; 
  str += '<tr><td style="padding-bottom:4px">' ;
  str += '<nobr><img src="'+cursorbtn.greyimg+'" width=24 height=24>' ; 
  str += '<img src="'+cursorbtn.blackimg+'" width=24 height=24></nobr>' ;
  str += '<td rowspan=6>&nbsp;&nbsp;&nbsp;' ; 
  str += '<td style="padding-bottom:4px">' ; 
  str += 'toggle between using the mouse to select waypoints and to ' ;
  str += 'drag the map<br>(the space bar has the same function)' ;

  str += '<tr><td style="padding-bottom:4px">' ;
  str += '<nobr><img src="'+scissorsbtn.blackimg+'" width=24 height=24>&nbsp;' ;
  str += '<img src="'+binbtn.blackimg+'" width=24 height=24></nobr>' ;
  str += '<td style="padding-bottom:4px">' ; 
  str += 'split the current segment at the selected point<br>' ;
  str += 'delete the currrent segment (or use the delete or backspace key)' ; 

  str += '<tr><td style="padding-bottom:4px">' ;
  str += '<nobr><img src="'+penbtn.blackimg+'" width=24 height=24></nobr>' ;
  str += '<td style="padding-bottom:4px">' ; 
  str += 'add a labelled coursepoint at the current position (1-10chars)<br>' ;
  str += 'click on flag to edit; right-click to change symbol; ' ; 
  str += 'delete label to delete' ; 

  str += '<tr><td style="padding-bottom:4px">' ;
  str += '<nobr><img src="'+dlbtn.blackimg+'" width=24 height=24></nobr>' ;
  str += '<td style="padding-bottom:4px">' ; 
  str += 'download route as .tcx' ;

  str += '<tr><td valign=top>Keyboard: <td>' ;
  str += '\u2190/\u2192 move the current waypoint forwards or backwards;<br>' ;
  str += '\u2193 centres the map on the current waypoint;<br>' ;
  str += '[return] makes the current waypoint draggable;<br>' ; 
  str += '[tab] inserts a draggable waypoint;<br>[space]=toggle cursor mode;' ;
  str += '<br>[del], [backspace]=delete segment (bin button).' ; 

  str += '<tr><td valign=top>Mouse: <td>when the cursor is in selection' ;
  str += ' mode:<br>[shift click] extends the current segment by' ;
  str += ' the cursor position.</table></div>' ; 

  str += finalbox + '<a style="cursor:pointer;color:#0000bd;text-'+
         'decoration:none" href="http://www.masterlyinactivity.com/software'+
         '/routemaster.html" target="_blank">Technical documentation and '+
         'source code</a>'+neutral+' (opens in new tab/window)</span></div>' ;

  infowindow.open(str,getbtnpos(0),'help') ; 
}
/* -------------------------------------------------------------------------- */
/*page*/
/* --------------------------------- wpdel ---------------------------------- */

function wpdelwork(s0,s1)
{ var i,response=segments[s0].data[s1],clen=segments[s0].data.length ;
  response.setmap(null) ;
  for(i=s1;i<clen-1;i++) segments[s0].data[i] = segments[s0].data[i+1] ;
  segments[s0].data.length = clen-1 ; 
  selected = [s0,s1] ; 
  if(s1==segments[s0].data.length) selected[1] -= 1 ;  
  redrawconnect(s0,s1) ; 
  drawsel(1) ; 
  return response ;
}
function wpdel()
{ var s0=selected[0],s1=selected[1],i ;
  infowindow.close() ; 
  done(['wpdel',s0,s1,wpdelwork(s0,s1)]) ; 
}
/* --------------------------------- revseg --------------------------------- */

function revsegwork(s0)
{ var i,s=segments[s0],j,x,len=s.data.length ;
  disconnect(s0-1) ; disconnect(s0) ; 
  for(i=0;i<len/2;i++)
  { j = (len-1) - i ; 
    x = s.data[i] ; s.data[i] = s.data[j] ; s.data[j] =   x ; 
  }
  for(i=0;i<s.data.length;i++) 
    if(s.data[i].type=='Right') s.data[i].settype('Left') ;
    else if(s.data[i].type=='Left') s.data[i].settype('Right') ;

  if(s0==selected[0]) selected[1] = (len-1) - selected[1] ; 
  connect(s0-1) ; connect(s0) ; 
  drawsel(1) ; 
}
/* -------------------------------------------------------------------------- */

function revseg()
{ infowindow.close() ; 
  revsegwork(selected[0]) ; 
  done(['revseg',selected[0]]) ; 
}
/* -------------------------------------------------------------------------- */

function addload(overwrite)
{ var msg ; 
  infowindow.close() ; 
  if(overwrite) 
  { msg = unsavedmsg(1) ; if(msg!=null) if(!confirm(msg)) return ; }
  infowindow.open(filedialogue(overwrite),getbtnpos(0),'addload') ; 
}
/* -------------------------------------------------------------------------- */
/*page*/
/* -------------------------------------------------------------------------- */
/*    DRAGGING A (POSSIBLY NEWLY INSERTED) WAYPOINT IS QUITE A LOT OF WORK    */
/* -------------------------------------------------------------------------- */

function insert(s0,s1,n)
{ var i ;
  for(i=segments[s0].data.length+n-1;i>s1;i--)
    segments[s0].data[i] = segments[s0].data[i-n] ;
  for(i=0;i<n;i++) segments[s0].data[s1+i] = new datatype(null,null) ;  
}
/* --------------------------------- inswp ---------------------------------- */

function inswp(dir)
{ var s0=selected[0],s1=selected[1],bounds,del,pos,data=segments[s0].data ;
  var len = data.length ;
  if(len==1) pos = data[0].pos ;

  if(dir>=0) s1 = selected[1] += 1 ; 
  insert(s0,s1,1) ;  
  if(len==1)
  { bounds = map.getBounds() ;
    del = bounds.getNorthEast().lng() - bounds.getSouthWest().lng() ; 
    pos = new google.maps.LatLng(pos.lat(),pos.lng()+dir*del/10) ; 
  }
  else if(s1==0) pos = interp(data[2].pos,data[1].pos,1.5) ; 
  else if(s1<len) pos = interp(data[s1-1].pos,data[s1+1].pos,0.5) ;
  else pos = interp(data[s1-2].pos,data[s1-1].pos,1.5) ;
  data[s1].setpos(pos) ; 
  draggit(1) ; 
}
/* -------------------------------- draggit --------------------------------- */

// draggit makes the current waypoint draggable

var l1,l2,startpos,seg0,seg1,seg2,colour,inserted ; 

function draggit(insparm)
{ var s0=selected[0],s1=selected[1],start,end,i,len=segments[s0].data.length ;
  startpos = segments[s0].data[s1].pos ;
  inserted = insparm ; 
  infowindow.close() ; 
  greyout(scissorsbtn) ;
  greyout(binbtn) ;
  greyout(penbtn) ;
  greyout(undobtn) ;
  greyout(redobtn) ;
  greyout(dlbtn) ;
  map.panToBounds(new google.maps.LatLngBounds(startpos,startpos)) ;

  sel.marker.setMap(null) ; 
  sel.marker = new google.maps.Marker(
                  { position: segments[s0].data[s1].pos,
                    map: map,
                    cursor: 'default',
                    icon: concircle ,
                    draggable: true ,
                    zIndex: 2
                  } ) ;

  if(s0&1) colour = "#ff9999" ; else colour = "#ff0000" ; 
  segments[s0].route.setMap(null) ;
  if(segments[s0].clickhandler!=null) 
  { google.maps.event.removeListener(segments[s0].clickhandler) ;
    segments[s0].clickhandler = null ; 
  }

  seg0 = seg2 = null; 
  if(s1>1)
  { seg0 = new google.maps.Polyline(new linepath(s0,0,s1,colour)) ;
    seg0.setMap(map) ;
  }

  if(s1==0) start = 0 ; else start = s1-1 ; 
  if(s1==len-1) end = s1+1 ; else end = s1+2 ; 
  seg1 = new google.maps.Polyline(new linepath(s0,start,end,colour)) ;
  seg1.setMap(map) ;

  if(s1<segments[s0].data.length-2)
  { seg2 = new google.maps.Polyline(new linepath(s0,s1+1,len,colour)) ;
    seg2.setMap(map) ;
  }

  l1 = google.maps.event.addListener(sel.marker,'drag',function()
  { segments[s0].data[s1].setpos(this.getPosition()) ; 
    seg1.setMap(null) ;
    seg1 = new google.maps.Polyline(new linepath(s0,start,end,colour)) ;
    seg1.setMap(map) ;
    if(s1==0&&s0>0) { disconnect(s0-1) ; connect(s0-1) ; }
    if(s1==len-1&&s0<segments.length-1) { disconnect(s0) ; connect(s0) ; }
  } ) ;
 
  dragging = 1 ; 
}  
/* ------------------------------- undraggit -------------------------------- */

// undraggit is invoked by [return] to terminate waypoint dragging

function undraggit()
{ var s0=selected[0],s1=selected[1],i,s1dash,pos=segments[s0].data[s1].pos ; 
  var xpos ; 
  google.maps.event.removeListener(l1) ;
  dragging = 0 ; 
  if(seg0!=null) seg0.setMap(null) ;
  seg1.setMap(null) ;
  if(seg2!=null) seg2.setMap(null) ;
  segments[s0].route = new google.maps.Polyline(new linepath(s0,-1,0,colour)) ;
  segments[s0].route.setMap(map) ;
  segments[s0].data[s1].h = null ;
 
  lookupalt(s0,s1) ;

  sel.marker.setMap(null) ; 
  sel.marker = null ; // force a redraw
  drawsel(1) ; 
  if(inserted||dist(startpos,pos)>5) 
    done(['move',s0,s1,startpos,pos,inserted]) ; 
  if(segments.length==1) blackout(dlbtn) ; 
}
/* -------------------------------------------------------------------------- */
/*page*/
/* -------------------------------------------------------------------------- */
/*            CODE TO GET ALTITUDES FOR NEWLY INSERTED POINTS                 */
/* -------------------------------------------------------------------------- */

function lookupalt(s0,s1) // set up a request for the alt of the new point
{ var hi,lo,segno=s0,ptno=s1,lopos,hipos,datum=segments[s0].data[s1] ; 
  for(lo=null,s0=segno,s1=ptno-1;lo==null;s1--)
  { if(s1<0) { s0 -= 1 ; if(s0<0) break ; s1 = segments[s0].data.length-1 ; } 
    if(segments[s0].data[s1].h!=null) lo = [s0,s1] ;
  }
  for(hi=null,s0=segno,s1=ptno+1;hi==null;s1++)
  { if(s1==segments[s0].data.length) 
    { s0 += 1 ; if(s0==segments.length) break ; s1 = 0 ; } 
    if(segments[s0].data[s1].h!=null) hi = [s0,s1] ;
  }
  if(lo==null&&hi==null) 
  { alert('no points left with altitudes: unable to proceed') ; throw '' ; }
  if(lo!=null) lopos = segments[lo[0]].data[lo[1]].pos ;
  if(hi!=null) hipos = segments[hi[0]].data[hi[1]].pos ;
  if(lo==null||(hi!=null&&dist(lopos,datum.pos)>dist(hipos,datum.pos))) 
  { lo = hi ; lopos = hipos ; } 
    
  pending.push([datum,lopos,segments[lo[0]].data[lo[1]].h]) ; 
  elevator.getElevationForLocations({locations:[datum.pos,lopos]},calibrate) ; 
}
/* -------------------------------------------------------------------------- */

// pending is the list of inserted points for which google altitudes are needed
//    note a pitfall with the elevation service - it's hard to tell which 
// response corresponds to which request: the coordinates may not match 
// because google truncates to 0.00001 deg; hence the use of the dist function.

function calibrate(results,status) 
{ var pno,flag ; 
  if(status===google.maps.ElevationStatus.OVER_QUERY_LIMIT)
    alert('No calibration data available: over Google query limit') ;  
  else if(status!==google.maps.ElevationStatus.OK||results.length!=2)
    alert('Calibration error') ;  
  // now find whether any of our elevation results allow us to fix an altitude
  for(pno=0;pno<pending.length;pno++) 
    if( dist(pending[pno][0].pos,results[0].location)<5 &&
        dist(pending[pno][1],results[1].location)<5 ) 
  { if(pno>0) alert('Warning: Google elevation results out of sequence') ; 
    diff = results[0].elevation - results[1].elevation ;
    pending[pno][0].h = pending[pno][2] + diff ;
  }
  for(i=pno=0;pno<pending.length;pno++) if(pending[pno][0].h==null)
  { if(pno!=i) pending[i] = pending[pno] ; i += 1 ; } 
  pending.length = i ; 
}
/* -------------------------------------------------------------------------- */
/*          FUNCTIONS FOR COMPUTING & DISPLAYING THE ALTITUDE PROFILE         */
/* -------------------------------------------------------------------------- */

function profile()
{ infowindow.close() ; 

  var i,sum,s0,s1,len,oldpos,pos,alt,h,amin,amax,c,y,step,ctx ;
  var startpos,prevpos ;  
  
  for(pron=s0=0;s0<segments.length;s0++) pron += segments[s0].data.length ;
  prox = new Array(pron) ; 
  proa = new Array(pron) ; 
  smap = new Array(segments.length) ;
  cmap = new Array(610) ;
  prox[0] = 0 ; 

  for(amax=amin=null,sum=pron=s0=0;s0<segments.length;s0++) 
    for(len=segments[s0].data.length,s1=0;s1<len;s1++,pron++)
  { proa[pron] = segments[s0].data[s1].h ; 
    if(proa[pron]!=null) 
    { if(amax==null||proa[pron]>amax) amax = proa[pron] ;
      if(amin==null||proa[pron]<amin) amin = proa[pron] ;
    }

    pos = segments[s0].data[s1].pos ;
    if(pron) sum = prox[pron] = sum + dist(pos,oldpos) ; 
    oldpos = pos ; 
  }
  if(pron==0||amin==amax) return ; 
  if(amin>0) { if(amax>3*amin) amin = 0 ; else amin *= 1 - (amax/amin-1)/2 ; }

  altdiv = document.createElement('div') ;
  altdiv.setAttribute
    ('style','position:absolute;height:190px;right:0;top:0;width:620px') ; 
  c = document.createElement('canvas') ; 
  c.setAttribute('width',620) ; 
  c.setAttribute('height',200) ; 
  altdiv.appendChild(c) ;
  body.appendChild(altdiv) ;
  ctx = c.getContext("2d") ; 
  ctx.font = "10px Helvetica" ;
  ctx.lineWidth = 0 ; 
  ctx.globalAlpha = 0.6 ; 

  ctx.fillStyle = 'lightgray' ;
  ctx.lineWidth = 0 ; 
  ctx.rect(0,0,620,200) ;
  ctx.fill() ; 

  // draw a profile of each segment
  for(i=pron=s0=0;s0<segments.length;s0++) 
  { len = segments[s0].data.length ;
    smap[s0] = new Array(len) ; 
    if(s0&1) ctx.fillStyle = "#ff9999" ; else ctx.fillStyle = "#ff0000" ; 
    for(startpos=opos=null,s1=0;s1<len;prevpos=[s0,s1],s1++,pron++,opos=pos)
    { pos = 10 + 600 * prox[pron] / sum ; 
      if(opos!=null) for(;i<(pos+opos)/2;i++) cmap[i-10] = prevpos ; 
      smap[s0][s1] = 0.5 + Math.floor(pos) ; 
      if(proa[pron]!=null) 
      { y = 10 + 180 * (amax-proa[pron]) / (amax-amin) ; 
        if(startpos==null) 
        { ctx.beginPath() ; ctx.moveTo(pos,y) ; startpos = pos ; }
        else ctx.lineTo(pos,y) ; 
      }
    }
    ctx.lineTo(pos,190) ; 
    ctx.lineTo(startpos,190) ; 
    ctx.closePath() ; 
    ctx.fill() ; 
  }
  for(;i<610;i++) cmap[i] = prevpos ;

  // lines
  if(amax-amin>2500) step = 1000 ; 
  else if(amax-amin>1250) step = 500 ;
  else step = 100 ; 

  for(i=step*Math.floor(amin/step+1);i<amax;i+=step) 
  { y = 10.5 + Math.floor(180*(amax-i)/(amax-amin)) ;
    ctx.beginPath() ; 
    ctx.lineWidth = 1 ; 
    ctx.strokeStyle = '#555' ; 
    ctx.moveTo(10,y) ;
    ctx.lineTo(610,y) ; 
    ctx.stroke() ; 
    ctx.strokeText(i,590,y-2) ;
  }

  // cursor
  curdiv = document.createElement('div') ;
  curdiv.setAttribute
    ('style','position:absolute;height:190px;right:0;top:0;width:620px') ; 
  curhandle = curdiv.addEventListener("click",function(e)
  { var pos = e.clientX - (window.innerWidth-610) ; 
    if((pos-594)*(pos-594)+(e.clientY-16)*(e.clientY-16)<200)
    { unprofile() ; return ; } 
    if(pos<0) pos = 0 ; else if(pos>600) pos = 600 ; 
    selected = cmap[pos] ; 
    drawsel(0) ; 
  } ) ;  
  body.appendChild(curdiv) ;
  curcan = null ; 
  procur() ; 
} 
/* ------------------------------- unprofile -------------------------------- */

function unprofile()
{ var i,match,node ; 
  infowindow.close() ; 
  if(altdiv==null) return ; 
  curdiv.removeEventListener('click',curhandle) ;
  for(match=0,i=body.childNodes.length-1;match==0&&i>=0;i--)
  { node = body.childNodes[i] ; 
    match = (node==altdiv) ; 
    body.removeChild(node) ; 
  }
  prox = proa = altdiv = cmap = smap = curdiv = curcan = curhandle = null ; 
} 
function reprofile() { if(altdiv!=null) { unprofile() ; profile() ; } } 

/* -------------------------------- procur ---------------------------------- */

function procur()
{ if(altdiv==null) return ; 
  var pos = smap[selected[0]][selected[1]] , ctx , i ;
  if(curcan!=null) curdiv.removeChild(curcan) ; 
  curcan = document.createElement('canvas') ; 
  curcan.setAttribute('width',620) ; 
  curcan.setAttribute('height',200) ; 
  curdiv.appendChild(curcan) ;
  ctx = curcan.getContext("2d") ; 
  ctx.beginPath() ; 
  ctx.lineWidth = 1 ; 
  ctx.moveTo(pos,10) ;
  ctx.lineTo(pos,190) ; 
  ctx.stroke() ; 

  // the circle of the 'x'
  ctx.beginPath() ; 
  ctx.strokeStyle = '#555' ; 
  ctx.fillStyle = 'white' ; 
  ctx.lineWidth = 3 ; 
  ctx.arc(604,16,14.1,0,2*Math.PI,false) ;
  ctx.stroke() ; 
  ctx.fill() ; 

  for(i=6;i<=26;i+=20) // the two bars of the 'x'
  { ctx.beginPath() ; 
    ctx.moveTo(594,i) ; 
    ctx.lineTo(614,32-i) ; 
    ctx.stroke() ; 
  }
}
/* -------------------------------------------------------------------------- */
/*page*/
/* -------------------------------------------------------------------------- */
/*       ROUTEINFO IS A MENU WHICH ITSELF SUPPORTS THE COMBINE FUNCTION       */
/* -------------------------------------------------------------------------- */

function routeinfo() 
{ var s0,s1,s,d,asc,des,oalt,alt,nlabels,nowpts,i,unsaved,props,spacing,npix ; 
  var maxsep,sep ;
  infowindow.close() ; 
  props = actions[loadno][4] ;

  for(maxsep=nlabels=npix=des=asc=d=nowpts=s0=0;s0<segments.length;s0++) 
  { nowpts += segments[s0].data.length ;
    for(oalt=null,s1=0;s1<segments[s0].data.length;s1++)
    { if((alt=segments[s0].data[s1].h)!=null) 
      { if(oalt!=null) 
        { if(alt>oalt) asc += alt-oalt ; else des += oalt - alt ; } 
        oalt = alt ;
      }
      if(segments[s0].data[s1].type!=null) nlabels += 1 ;
      if(segments[s0].data[s1].photo!=null) npix += 1 ;
      if(s1) 
      { sep = dist(segments[s0].data[s1-1].pos,segments[s0].data[s1].pos) ;
        d += sep ;
        if(sep>maxsep) maxsep = sep ; 
      }
    }
  }
  s = finalbox +'<nobr>Title: <b>'+routetitle+'</b> [' ; 
  s += active + '"retitle()">Edit</span>'+']</nobr><br>' ;

  if(loadno>0) 
  { s += '<nobr>&nbsp;&nbsp;&nbsp;Last added route' ;
    if(props.title!=null) s += ' (' + props.title + ')' ;
    s += ':</nobr><br><nobr>&nbsp;&nbsp;&nbsp;' ; 
  }
  else s += '<nobr>' ;

  s += 'Track points on input: ' + props.inputlen ; 
  if(props.optim.already)  
  { s += ' (previously optimised)</nobr>' ;
    if(nowpts!=props.inputlen) s += '<br>Now ' + nowpts + ' track points' ;
  }
  else if(props.optim.ndel==0) 
  { if(nactions==loadno+1) s += ' [' + active + '"optimprompt()">' ;
    else s += ' [' + inactive ;
    s += 'Optimise' + neutral + ']</span></nobr>' ;
  }
  else s += ', optimised to ' + (props.inputlen-props.optim.ndel) + '</nobr>' ;

  if(!props.optim.already&&props.inputlen-props.optim.ndel!=nowpts) 
    s += '<br>Now ' + nowpts + ' track points' ;
  s += '<br>' ; 

  if(nlabels>0) 
    s += nlabels + ' labelled course point' + (nlabels>1?'s':'') + '<br>' ; 
  if(npix>0) s += npix + ' photo' + (npix>1?'s':'') + '<br>' ; 

  unsaved = unsavedchanges.length ; 
  if(unsaved>0) s += unsaved + ' unsaved change' + (unsaved>1?'s':'') + '<br>' ;

  if(segments.length>1) s += segments.length + ' segments [' + active +
    '"combine()">Combine</span>' + neutral + ']<br>' +
    '<i>Note that segments must be combined before saving</i><br>' ;

  s += 'Max waypoint separation: '+maxsep.toFixed(0)+'m<br>' ; 
  if(maxsep>=100) 
    s += '<i>Note that separations &gt;100m are illegal on Garmin</i><br>' +
         '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[</span>' + active + '"extrapts()">' +
         'Interpolate extra points</span>]<br>' + neutral ;

  s += 'Total distance: '+(d/1000).toFixed(3)+'km<br>' ; 
  s += 'Total ascent: '+asc.toFixed(0)+'m<br>' ; 
  s += 'Total descent: '+des.toFixed(0)+'m</div>' ; 
  infowindow.open(s,getbtnpos(0),'routeinfo') ; 
}
/* -------------------------------------------------------------------------- */

/* ------------------------- interpolate extra points ----------------------- */

function extrapts(opt)
{ var s0,s1,sep,data,n,opos,npos,i,lambda,lox,nlox,taskno,ind ;
  var task = [ 'extra' , selected[0] , selected[1] ] ;
  infowindow.close() ; 

  for(nlox=s0=0;s0<segments.length;s0++) 
    for(data=segments[s0].data,s1=1;s1<data.length;s1++)
      if((sep=dist(opos=data[s1-1].pos,npos=data[s1].pos))>100) 
  { n = Math.floor(sep/100) ;
    insert(s0,s1,n) ; 
    for(i=0;i<n;i++) 
    { lambda = (i+1) / (n+1) ; 
      data[s1+i].setpos(new google.maps.
                          LatLng(lambda*npos.lat()+(1-lambda)*opos.lat(),
                                 lambda*npos.lng()+(1-lambda)*opos.lng())) ;
    }
    if(selected[0]==s0&&s1<=selected[1]) selected[1] += n ; 
    task.push([s0,s1,data.slice(s1-1,s1+n+1)]) ; 
    s1 += n ;
    nlox += n+2 ; 
  }
  if(nlox==0) return ; 

  done(task) ; 
  xpending.push(task) ; 
  lox = new Array(nlox) ; 
  for(ind=0,taskno=3;taskno<task.length;taskno++)
  { n = task[taskno][2].length ; 
    for(i=0;i<n;i++) lox[ind++] = task[taskno][2][i].pos ; 
  }

  elevator.getElevationForLocations( {locations:lox} , function (results,status)
  { // assume that the results come in sequence, ie. correspond to xpending[0]
    var task=xpending.shift(),taskno,d0,dn,lambda ;
    if(status===google.maps.ElevationStatus.OVER_QUERY_LIMIT)
      alert('No calibration data available: over Google query limit') ;  
    else if(status!==google.maps.ElevationStatus.OK)
      alert('Calibration error') ;  
    for(ind=0,taskno=3;taskno<task.length;taskno++,ind+=n+2)
    { n = task[taskno][2].length-2 ; 
      d0 = task[taskno][2][0].h - results[ind].elevation ;
      dn = task[taskno][2][n+1].h - results[ind+n+1].elevation ; 
      if( dist(task[taskno][2][0].pos,results[ind].location)>5 ||
          dist(task[taskno][2][n+1].pos,results[ind+n+1].location)>5 )
        alert('Anomaly with Google elevation results') ; 
      for(i=0;i<n;i++) 
      { lambda = (i+1) / (n+1) ; 
        task[taskno][2][1+i].h = 
          results[ind+1+i].elevation + lambda*dn + (1-lambda)*d0 ;
      }
    }
  } ) ; 
  routeinfo() ; 
}
/* -------------------------------------------------------------------------- */
/*page*/
/* ------------------------------- combine1 --------------------------------- */

function combine1(sa,sb)
{ var i,ca,cb,calen,cblen,la=null,lb=null,cdup=0 ; 
  undraw(sb) ; 
  disconnect(sb-1) ; 
  calen = segments[sa].data.length ;
  cblen = segments[sb].data.length ;
  cb = segments[sb].data[0].pos ; 
  cdup = ( ca = cb.equals(segments[sa].data[calen-1].pos) ) ; 
  if(cdup) 
  { la = segments[sa].data[calen-1] ; 
    lb = segments[sb].data[0] ; 
    segments[sa].data.length = ( calen -= 1 ) ;
  }

  if(selected[0]==sb) selected = [ sa , selected[1]+calen ] ; 
  segments[sa].data = segments[sa].data.concat(segments[sb].data) ; 

  return [ cblen , cdup , la , lb ] ; 
}
function combinework()
{ var task,s0 ;
  for(task=['combine',segments.length],s0=1;s0<segments.length;s0++) 
    task.push(combine1(0,s0)) ;
  segments.length = 1 ; 
  return task ; 
}
/* -------------------------------------------------------------------------- */

function combine()
{ infowindow.close() ; 
  done(combinework()) ; 
  drawsel(1) ; 
  redraw(0) ; 
  blackout(dlbtn) ; 
}  
function recombine()
{ var s0 ;
  for(s0=1;s0<segments.length;s0++) { undraw(s0) ; combine1(0,s0) ;}
  segments.length = 1 ; 
  drawsel(1) ;  
  redraw(0) ;
  blackout(dlbtn) ; 
}
/* -------------------------------------------------------------------------- */

//  combine returns [ cblen , cdup , la , lb ] ; 

function uncombine(task)
{ var i,j,llen,flag,subtask ; 

  for(flag=0,s0=task[1]-1,i=task.length-1;i>=2;i--,s0--)
  { subtask = task[i] ; 
    cblen = subtask[0] ; 
    cdup = subtask[1] ; 
    llen = segments[0].data.length ;
    segments[s0] = { data: segments[0].data.slice(llen-cblen,llen) , 
                     route: null , 
                     clickhandler: null
                   } ;
    llen = segments[0].data.length = llen+cdup-cblen ;
    if(cdup) 
    { segments[0].data[llen-1] = subtask[2] ; 
      segments[s0].data[0] = subtask[3] ; 
    }
    if(flag==0&&selected[1]>=llen)
    { selected = [ s0 , selected[1]-llen ] ; flag = 1 ; } 
  }
  drawsel(1) ; 
  undraw(0) ; 
  for(i=0;i<segments.length;i++) { draw(i) ; connect(i) ; } 
  greyout(dlbtn) ; 
}
/* -------------------------------------------------------------------------- */
/*           WPINFO IS A MENU GIVING ACCESS TO THE SETALT FUNCTION            */
/* -------------------------------------------------------------------------- */

function wpinfo() 
{ infowindow.close() ; 
  var s0=selected[0],s1=selected[1],alt,nalt,s,x,lat,lng,grad,gradstr ;
  var datum = segments[s0].data[s1] , pos = datum.pos ;
  lat = pos.lat() ; 
  lng = pos.lng() ; 
  s = finalbox ; 
  if(lat>=0) s += lat.toFixed(5) + '\u00b0 N, ' ; 
  else { lat = -lat ; s += lat.toFixed(5) + '\u00b0 S, ' ; }
  if(lng>=0) s += lng.toFixed(5) + '\u00b0 E<br>' ; 
  else { lng = -lng ; s += lng.toFixed(5) + '\u00b0 W<br>' ; }
    
  x = new LatLon(lat,lng) ; 
  if(lat>49.9&&lat<62&&lng>-12&&lng<2.5&&lat-1.5*lng<75) 
    s += 'OS grid ref: ' + OsGridRef.latLonToOsGrid(x) ;
  else s += 'UTM coords = ' + x.toUtm() ; 
  s += '<br>' ; 

  alt = segments[s0].data[s1].h ;
  if(alt!=null) s += 'Altitude: ' + alt.toFixed(0) + 'm ' +
    active + '"setalt(1)">[Edit]' ;
  else s += active + '"setalt(0)">Set altitude' ;
  s += '</span><br>' ;
  if(datum.type!=null) s += datum.type + ': ' + 
       datum.marker.title + ' [' + active + '"labelprompt()">Edit</span>]<br>' ;
  if(datum.photo!=null) s += 'Photo: ' + datum.photo + 
       ' [' + active + '"photoprompt(null)">Edit</span>]<br>' ;

  if(alt==null||s1==segments[s0].data.length-1) nalt = null ; 
  else 
  { nalt = segments[s0].data[s1+1].h ; 
    if(nalt!=null) x = dist(pos,segments[s0].data[s1+1].pos) ; 
  }
  if(nalt!=null&&Math.abs(nalt-alt)<x) 
  { grad = 100*Math.asin((nalt-alt)/x) ; 
    gradstr = Math.abs(grad).toFixed(0) ; 
    if(gradstr=='0') s += 'Flat<br>' ; 
    else if(grad>0) s += 'Climb '+gradstr+'%<br>' ; 
    else s += 'Descend: '+gradstr+'%<br>' ; 
  }
  s += '<span style="font-size:80%">' ; 
  if(segments.length>1) s += 'Segment '+s0+' p' ; else s += 'P' ;
  s += 'oint ' + s1 + '</span></div>' ; 
  infowindow.open(s,pos,'wpinfo') ; 
}
/* -------------------------------------------------------------------------- */
/*page*/
/* -------------------------------- setalt ---------------------------------- */

function setalt(edit)
{ infowindow.close() ; 
  var s0=selected[0],s1=selected[1],x,y=null,oldalt ; 
  oldalt = segments[s0].data[s1].h.toFixed(0) ;
  if(edit) x = prompt('Enter altitude (m):',oldalt) ;
  else x = prompt('Enter altitude (m):') ;
  if(x==null) return ; 
  if(x!='')
  { y = parseFloat(x) ; 
    if(isNaN(y)) { alert(x+' is not a number') ; return ; }
  }
  if(y==null&&oldalt==null) return ; 
  if(y!=null&&Math.abs(y-oldalt)<0.1) return ; 
  segments[s0].data[s1].h = y ; 
  done(['setalt',s0,s1,oldalt,y]) ; 
  reprofile() ; 
  wpinfo() ; 
}  
/* -------------------------------------------------------------------------- */
/*   THE LABELS ARE ACCESSED FROM THE PEN BUTTON OR BY CLICKING ON THE MAP    */
/* -------------------------------------------------------------------------- */

function labelprompt()
{ var oldcaption='',oldtype,type='Generic',s0=selected[0],s1=selected[1] ;
  var str , flag = (infowindow.close()=='wpinfo') ; 
  var datum = segments[s0].data[s1] ;
  
  oldtype = datum.type ; 
  if(oldtype!=null) oldcaption = datum.marker.title ; 
  if(oldcaption==null) oldcaption = '' ;
  if(oldtype!=null) type = oldtype ;
  if(oldtype==null) str = 'Enter' ; else str = 'Modify or delete' ; 
  var caption = window.prompt(str+' label:',oldcaption) ;

  if(caption==null) { if(flag) wpinfo() ; else walkto(s0,s1,0,0) ; return ; } 
  else if(caption=='') type = null ; 
  else caption = caption.substring(0,10) ;
  if(caption==oldcaption) 
  { if(flag) wpinfo() ; else walkto(s0,s1,0,0) ; return ; } 

  segments[s0].data[s1].setlabel(type,caption) ; 
  done(['editlabel',s0,s1,oldcaption,caption,oldtype,type]) ; 
  if(flag) wpinfo() ; else walkto(s0,s1,0,0) ; 
}
/* -------------------------------------------------------------------------- */
/*page*/
/* ------------------------------ labelcycle -------------------------------- */

function labelcycle()
{ var s0,s1,datum,oldtype,caption,type,flag=(infowindow.close()=='wpinfo') ;  ;
  for(s0=0;s0<segments.length;s0++) for(s1=0;s1<segments[s0].data.length;s1++)
  { datum = segments[s0].data[s1] ;
    if(datum.marker!=this) continue ; 
    oldtype = datum.type ;
    caption = datum.marker.title ;
    if(oldtype=='Generic') type = 'Left' ; 
    else if(oldtype=='Left') type = 'Straight' ; 
    else if(oldtype=='Straight') type = 'Right' ; 
    else if(oldtype=='Right') type = 'Danger' ; 
    else type = 'Generic' ; 
    datum.settype(type) ;
    selected = [s0,s1] ; 
    done(['editlabel',s0,s1,caption,caption,oldtype,type]) ; 
    if(flag) wpinfo() ; 
    return ;
  }
}

function photoprompt(e)
{ var oldphoto=null,s0=selected[0],s1=selected[1],str ;
  if(e!=null) e.preventDefault() ;
  var flag = (infowindow.close()=='wpinfo') ; 
  var datum = segments[s0].data[s1] ;
  oldphoto = datum.photo ; 
  if(oldphoto==null) str = 'Enter' ; else str = 'Modify or delete' ; 
  var photo = window.prompt(str+' photo:',oldphoto==null?'':oldphoto) ;

  if(photo==null) { if(flag) wpinfo() ; else walkto(s0,s1,0,0) ; return ; } 
  else if(photo=='') photo = null ; 
  if(photo==oldphoto) { if(flag) wpinfo() ; else walkto(s0,s1,0,0) ; return ; } 

  datum.setphoto(photo) ; 
  done(['editphoto',s0,s1,oldphoto,photo]) ; 
  if(flag) wpinfo() ; else walkto(s0,s1,0,0) ; 
}
/* ----------------------------- enlarge photo ------------------------------ */

function enlarge(i)
{ document.onkeydown = imgwalk ;
  infowindow.close() ; 
  imgdiv = document.createElement('div') ; 
  imgdiv.setAttribute
    ('style','position:fixed;width:100%;height:100%;left:0;top:0') ;
  imghandle = imgdiv.addEventListener("click",imgwalk) ;
  imgtable = enlargetable(i) ;
  imgdiv.appendChild(imgtable) ;
  body.appendChild(imgdiv) ;
}
/* ------------------------------ image walk -------------------------------- */

function imgwalk(e)
{ var s0,s1 ;

  if(e.keyCode==16) shift = 1 ; 
  else if(e.keyCode==39) // forwards
  { e.preventDefault() ; 
    for(s0=selected[0],s1=selected[1]+1;;s1++)
    { if(s1==segments[s0].data.length) 
      { s0 += 1 ; if(s0==segments.length) s0 = 0 ; s1 = 0 ; }
      if(s0==selected[0]&&s1==selected[1]) break ; 
      if(imgreplace(s0,s1)) return ; 
    }
  }
  else if(e.keyCode==37) // backwards
  { e.preventDefault() ; 
    for(s0=selected[0],s1=selected[1]-1;;s1--)
    { if(s1<0)
      { if(s0==0) s0 = segments.length-1 ; else s0 -= 1 ; 
        s1 = segments[s0].data.length-1 ; 
      }
      if(s0==selected[0]&&s1==selected[1]) break ; 
      if(imgreplace(s0,s1)) return ; 
    }
  }

  document.onkeydown = walk ;
  imgdiv.removeEventListener('click',imghandle) ;
  body.removeChild(imgdiv) ;
  walkto(selected[0],selected[1],0,0) ; 
}
/* ----------------------------- image replace ------------------------------ */

function imgreplace(s0,s1)
{ var i ; 
  if(segments[s0].data[s1].photo!=null)
    if((i=findimg(segments[s0].data[s1].photo))>=0)
  { imgdiv.removeChild(imgtable) ; 
    imgtable = enlargetable(i) ; 
    imgdiv.appendChild(imgtable) ; 
    selected = [s0,s1] ; 
    return 1 ;
  }
  else return 0 ; 
}
/* -------------------------- generate image table -------------------------- */

function enlargetable(i)
{ var w=window.innerWidth,h=window.innerHeight,t,td,tr,span,img,shape ;
  var imguri = reluri(imginfo.uri,imginfo.list[i].name) ; 
  var spare = (imginfo.list[i].caption==undefined?30:36) ; 
  if(imgsizes==1) { imguri += '.jpg' ; shape = imginfo.list[i].shape ; }
  else if(imginfo.list[i].shape[0]<w&&imginfo.list[i].shape[1]+20<h)
  { imguri += 'big.jpg' ; shape = imginfo.list[i].shape ; }
  else if( imgsizes>2 && imginfo.list[i].shape[0]*7<w*10 && 
          imginfo.list[i].shape[1]*7+spare<h*10 )
  { imguri += 'med.jpg' ; 
    shape = [ 7*imginfo.list[i].shape[0]/10 , 7*imginfo.list[i].shape[1]/10 ] ; 
  }
  else 
  { imguri += '.jpg' ; 
    shape = [ imginfo.list[i].shape[0]/2 , imginfo.list[i].shape[1]/2 ] ; 
  }

  t = document.createElement('table') ;
  t.setAttribute('cellspacing',0) ; 
  t.setAttribute('cellpadding',0) ; 
  t.setAttribute('width','100%') ; 
  t.setAttribute('height','100%') ; 
  t.setAttribute('bgcolor','black') ; 

  t.appendChild(blankrow((h-(shape[1]+spare))/2)) ;

  // title
  tr = document.createElement('tr') ;
  tr.appendChild(blankcol((w-shape[0])/2,spare==20?2:3)) ;
  td = document.createElement('td') ;
  td.setAttribute('colspan',2) ; 
  span = document.createElement('span') ;
  span.setAttribute('style','text-align:left ; font-family:helvetica ; '+
                    'margin:0 ; font-size:120% ; color:silver') ; 
  span.appendChild(document.createTextNode(imginfo.list[i].title)) ;
  td.appendChild(span) ;
  tr.appendChild(td) ;
  t.appendChild(tr) ;

  // the image
  tr = document.createElement('tr') ;
  td = document.createElement('td') ;
  img = document.createElement('img') ;
  img.setAttribute('src',imguri) ; 
  img.setAttribute('width',shape[0]) ; 
  img.setAttribute('height',shape[1]) ; 
  td.appendChild(img) ;
  tr.appendChild(td) ;
  tr.appendChild(blankcol((w-shape[0])/2,1)) ;
  t.appendChild(tr) ;

  // the caption
  if(spare==36)
  { tr = document.createElement('tr') ;
    td = document.createElement('td') ;
    td.setAttribute('colspan',2) ; 
    span = document.createElement('span') ;
    span.setAttribute('style','text-align:left ; font-family:helvetica ; '+
                      'margin:0 ; font-size:80% ; color:silver') ; 
    span.appendChild(document.createTextNode(imginfo.list[i].caption)) ;
    td.appendChild(span) ;
    tr.appendChild(td) ;
    t.appendChild(tr) ;
  }

  t.appendChild(blankrow((h-(shape[1]+spare))/2)) ;
  return t ; 
}
function blankcol(wid,nrow)
{ var td = document.createElement('td') ;
  td.setAttribute('width',wid) ; 
  if(nrow>1) td.setAttribute('rowspan',nrow) ; 
  td.appendChild(document.createTextNode('\u00a0')) ;
  return td ; 
} 
function blankrow(ht)
{ var td = document.createElement('td') , tr = document.createElement('tr') ;
  td.setAttribute('colspan',3) ; 
  td.setAttribute('height',ht) ; 
  td.appendChild(document.createTextNode('\u00a0')) ;
  tr.appendChild(td) ;
  return tr ; 
}
/* --------------------------------- photo info ----------------------------- */

function phinfo(i) 
{ infowindow.close() ; 
  var s0=selected[0],s1=selected[1],s,shape=imginfo.list[i].shape,ind,hind ;
  s = finalbox + 'Name: ' + imginfo.list[i].name + 
                 '<br>Title: ' + imginfo.list[i].title ;
  for(hind=null,ind=0;ind<i;ind++) if(imginfo.list[ind].name==null) hind = ind ;
  if(hind!=null) s += "<br>Under \u201c" + imginfo.list[hind].title + "\u201d" ;
  s += '<br>Available in '+imgsizes+' size' ;
  if(imgsizes==1) s += ': '+shape[0]+'x'+shape[1] ;
  else 
  { s += 's: '+shape[0]+'x'+shape[1] ;
    if(imgsizes==3) s += ', '+7*shape[0]/10+'x'+7*shape[1]/10 ;
    s += ' and '+shape[0]/2+'x'+shape[1]/2 ;
  }
  if(imgpix!=null)
    s += '<br><a style="cursor:pointer;color:#0000bd;text-decoration:none" '+
         'href="'+reluri(imginfo.uri,imgpix)+'" target="_blank">'+
         'Full photo set</a>'+neutral+' (opens in new tab/window)</span>' ;
  if(imginfo.list[i].retid!=null)
  { for(hind=null,ind=0;ind<=i;ind++) 
      if(imginfo.list[ind].retpage!=null) 
        hind = imginfo.list[ind].retpage + '.html#' ; 
    if(hind!=null) s += '<br><a style="cursor:pointer;color:#0000bd;'+
       'text-decoration:none" href="'+reluri(imginfo.uri,hind)+
       imginfo.list[i].retid+'" target="_blank">'+
       'Route notes</a>'+neutral+' (opens in new tab/window)</span>' ;
  }
  infowindow.open(s,segments[s0].data[s1].pos,'phinfo') ; 
}
/* -------------------------------------------------------------------------- */

/* ------------------------- snip: apply scissors  -------------------------- */

function snipwork(s0,s1)
{ var i,k,newlen ; 
  undraw(s0) ; 
  segments.length += 1 ; 
  for(i=segments.length-1;i>s0+1;i--) segments[i] = segments[i-1] ; 

  newlen = segments[s0].data.length - s1 ;
  segments[s0+1] =  new genseg(segments[s0].data.slice(s1)) ;
  segments[s0+1].dots = segments[s0].dots ;
  segments[s0+1].dothandler = segments[s0].dothandler ;
  segments[s0].dots = segments[s0].dothandler = null ; 

  segments[s0].data.length = s1 + 1 ; 
  segments[s0].data[s1] = 
    new datatype(segments[s0].data[s1].pos,segments[s0].data[s1].h) ; 
  draw(s0) ;
  draw(s0+1) ; 
  for(i=s0+2;i<segments.length;i++) recolour(i) ;
  selected = [s0+1,0] ; 
  drawsel(1) ; 
  greyout(dlbtn) ; 
}
function snip()
{ var i,s0=selected[0],s1=selected[1] ; 
  infowindow.close() ; 
  done(['snip',s0,s1]) ; 
  snipwork(s0,s1) ; 
}
/* ------------------------ discard: bin a segment  ------------------------- */

function binwork(s0)
{ var i ; 
  obliterate(s0) ; 
  for(i=s0;i<segments.length-1;i++) segments[i] = segments[i+1] ; 
  segments.length -= 1 ; 

  for(i=s0;i<segments.length;i++) recolour(i) ;
  connect(s0-1) ;

  selected[1] = 0 ; 
  if(selected[0]==segments.length) selected[0] = 0 ; 
  drawsel(1) ; 
  if(segments.length==1) blackout(dlbtn) ; 
}
function discard()
{ var i,s0=selected[0] ; 
  infowindow.close() ;  
  done(['bin',s0,segments[s0]]) ; 
  binwork(s0) ; 
}
/* -------------------------------------------------------------------------- */
/*page*/
/* -------------------------------- actionname ------------------------------ */

function actionname(x)
{ if(x[0]=='bin') return 'delete segment' ; 
  if(x[0]=='snip') return 'split segment' ; 
  if(x[0]=='editlabel') 
  { if(x[4]=='') return 'delete label' ; 
    else if(x[3]=='') return 'label waypoint' ;
    else return 'edit label' ; 
  }
  if(x[0]=='edittitle') return 'edit title' ; 
  if(x[0]=='wpdel') return 'delete waypoint' ; 
  if(x[0]=='move') 
  { if(x[5]) return 'insert waypoint' ; else return 'drag waypoint' ; }
  if(x[0]=='recal') return 'recalibrate altitudes' ; 
  if(x[0]=='setalt') return 'set waypoint altitude' ; 
  if(x[0]=='resign') return 'change label symbol' ; 
  if(x[0]=='combine') return 'combine '+x[1]+' segments' ; 
  if(x[0]=='revseg') return 'reverse segment' ; 
  if(x[0]=='interpolate') return 'interpolate missing altitudes' ; 
  if(x[0]=='optimise') return 'optimisation' ; 
  if(x[0]=='editphoto') 
  { if(x[4]==null) return 'delete photo' ; 
    else if(x[3]==null) return 'add photo' ;
    else return 'change photo' ; 
  }
  if(x[0]=='extra') return 'interpolate extra points' ; 
}
function actiontype(x)
{ if( x=='snip'||x=='combine'||x=='interpolate'
   || x=='optimise'||x=='load' ) return 0 ; else return 1 ;
}
/* -------------------------------------------------------------------------- */

function done(something) 
{ if( nactions>0 && unsavedchanges.length>0 && something[0]=='editlabel'
   && actions[nactions-1][0]==something[0]
   && actions[nactions-1][1]==something[1] // don't merge change with delete
   && actions[nactions-1][2]==something[2] && something[6]!=null )
  { actions[nactions-1][4] = something[4] ; // caption
    actions[nactions-1][6] = something[6] ; // type
  }
  else { actions[nactions++] = something ; donesomething() ; }
}
function donesomething()
{ actions.length = nactions ; 
  blackout(undobtn) ; 
  greyout(redobtn) ; 
  if(actiontype(actions[nactions-1][0])!=0) 
  { if(unsavedchanges.length>=3) unsavedchanges.push(null) ; 
    else unsavedchanges.push(actionname(actions[nactions-1])) ;
  }
}
/* --------------------------------- undo  ---------------------------------- */

function undo()
{ infowindow.close() ;  
  var opts = active + '"confirmedundo()">Undo ' + 
                       actionname(actions[nactions-1])+'</span>' ;
  infowindow.open(opts,getbtnpos(5),'undo') ; 
}
function confirmedundo()
{ var i,ano=nactions-1,action=actions[ano][0],s0=actions[ano][1],s1,caption ;
  var oldcaption,task ; 
  infowindow.close() ;  

  if(action!='revseg'&&action!='interpolate') s1 = actions[ano][2] ;

  if(action=='bin') 
  { disconnect(s0-1) ; 
    for(i=segments.length;i>s0;i--) 
    { segments[i] = segments[i-1] ; recolour(i) ; } 
    segments[s0] = s1 ;
    for(s1=0;s1<segments[s0].data.length;s1++)
      segments[s0].data[s1].setmap(map) ; 
    draw(s0) ; 
    connect(s0-1) ; 
    connect(s0) ; 
    if(selected[0]>=s0) selected[0] += 1 ; 
    drawsel(1) ; 
    greyout(dlbtn) ; 
  }
  else if(action=='snip') // undo snip
  { selected = [ s0 , segments[s0].data.length-1 ] ; 
    combine1(s0,s0+1) ; 
    for(i=s0+1;i<segments.length-1;i++) 
    { segments[i] = segments[i+1] ; recolour(i) ; } 
    segments.length -= 1 ; 
    if(segments.length==1) blackout(dlbtn) ; 
    draw(s0) ;
    drawsel(1) ; 
  } 
  else if(action=='editlabel')  // undo create/edit/delete label
    segments[s0].data[s1].setlabel(actions[ano][5],actions[ano][3]) ;
  else if(action=='edittitle') settitle(s0) ; 
  else if(action=='wpdel')      // ['wpdel',s0,s1,wpdelwork(s0,s1)]
  { insert(s0,s1,1) ; 
    segments[s0].data[s1] = actions[ano][3] ;
    segments[s0].data[s1].setmap(map) ;
    selected = [s0,s1] ; 
    redrawconnect(s0,s1) ;
    drawsel(1) ; 
  }
  else if(action=='move')
  { if(actions[ano][5]) wpdelwork(s0,s1) ; else move(s0,s1,actions[ano][3]) ; }
  else if(action=='recal') calwork(s0,-s1) ; 
  else if(action=='setalt') segments[s0].data[s1].h = actions[ano][3] ;
  else if(action=='combine') uncombine(actions[ano]) ; 
  else if(action=='revseg') revsegwork(s0) ; 
  else if(action=='interpolate') 
    for(i=0;i<s0.length;i++) segments[0].data[s0[i]].h = null ;  
  else if(action=='optimise') // [ 'load' , s0 , data.slice() , loadno , props ]
  { for(ano--;ano>=0&&actions[ano][0]!='load';ano--) ;
    segments[s0].data = actions[ano][2] ;
    actions[loadno][4].optim.ndel = 0 ; 
    selected = [s0,0] ; 
    redraw(s0) ;
    drawsel(1) ; 
  }
  else if(action=='editphoto') segments[s0].data[s1].setphoto(actions[ano][3]) ;
  else if(action=='extra') 
    for(selected=[s0,s1],i=actions[ano].length-1;i>=3;i--)
  { task = actions[ano][i]
    segments[task[0]].data.splice(task[1],task[2].length-2) ; 
  }

  nactions -= 1 ; 
  if(nactions<=1) greyout(undobtn) ; 
  blackout(redobtn) ; 
  if(actiontype(actions[nactions][0])!=0&&unsavedchanges.length>0)
    unsavedchanges.length -= 1 ;  ;
  if(action=='optimise') routeinfo() ;
}
/* --------------------------------- move ----------------------------------- */

function move(s0,s1,pos)
{ segments[s0].data[s1].setpos(pos) ; redrawconnect(s0,s1) ; drawsel(1) ; }

/* --------------------------------- redo  ---------------------------------- */

function redo()
{ infowindow.close() ;  
  var opts = active + '"confirmedredo()">Redo ' + 
                       actionname(actions[nactions])+'</span>' ;
  infowindow.open(opts,getbtnpos(6),'redo') ; 
}
function confirmedredo()
{ var i,action=actions[nactions][0],s0=actions[nactions][1],s1,caption,a,b,c ;
  var task ; 
  if(action!='bin'&&action!='revseg') s1 = actions[nactions][2] ;
  infowindow.close() ; 

  if(action=='bin') binwork(s0) ; 
  else if(action=='snip') snipwork(s0,s1) ; 
  else if(action=='editlabel') // redo create/edit/delete label
    segments[s0].data[s1].setlabel(actions[nactions][6],actions[nactions][4]) ;
  else if(action=='edittitle') settitle(s1) ; 
  else if(action=='wpdel') wpdelwork(s0,s1) ; 
  else if(action=='move')     // ['move',s0,s1,oldpos,newpos,inserted]
  { if(actions[nactions][5]) insert(s0,s1,1) ; 
    move(s0,s1,actions[nactions][4]) ; 
  }
  else if(action=='recal') calwork(s0,s1) ; 
  else if(action=='setalt') segments[s0].data[s1].h = actions[nactions][4] ;
  else if(action=='combine') recombine(actions[nactions]) ; 
  else if(action=='revseg') revsegwork(s0) ; 
  else if(action=='interpolate') interpolatework() ; 
  else if(action=='optimise') 
  { result = optimise(segments[s0],actions[nactions][2]) ; 
    actions[loadno][4].optim.ndel = segments[s0].data.length - result.length ;
    segments[s0].data = result ; 
    selected = [s0,0] ; 
    redraw(s0) ;
    drawsel(1) ; 
    routeinfo() ; 
  }
  else if(action=='editphoto') 
    segments[s0].data[s1].setphoto(actions[nactions][4]) ;
  else if(action=='extra') 
    for(selected=[s0,s1],i=3;i<actions[nactions].length;i++)
  { task = actions[nactions][i] ;
    a = segments[task[0]].data.slice(0,task[1]) ;
    b = task[2].slice(1,task[2].length-1) ; 
    c = segments[task[0]].data.slice(task[1]) ;
    segments[task[0]].data = a.concat(b,c) ; 
  }

  nactions += 1 ; 
  if(nactions==actions.length) greyout(redobtn) ; 
  blackout(undobtn) ; 
  if(actiontype(actions[nactions-1][0])!=0) unsavedchanges.push(action) ;
}
/* -------------------------------------------------------------------------- */
/*page*/
/* ---------------------------------- dl  ----------------------------------- */

function dl() 
{ var i,noalt,xmldoc,course,lap,datum,filename,blob,distance,track,time ; 
  var trackpoint,coursepoint,str,x4,x6,x8,clen=segments[0].data.length ; 
  var hh,mm,ss,origlen,ndel,ano,photo,npix,thisuri,maxsep,sep ; 

  for(distance=new Array(clen),maxsep=noalt=i=0;i<clen;i++) 
  { if(segments[0].data[i].h==null) noalt += 1 ; 
    if(i) 
    { sep = dist(segments[0].data[i-1].pos,segments[0].data[i].pos) ;
      distance[i] = distance[i-1] + sep ; 
      if(sep>maxsep) maxsep = sep ; 
    }
    else distance[i] = 0 ; 
  }
  if(maxsep>100&&!confirm('Some gaps between waypoints are >100m.\n'+
    'This will cause problems if used for navigation in a Garmin 500.\n'+
    'You can hit [OK] and I will proceed anyway, or\n'+
    'you can hit [Cancel] to interpolate extra points\n'+
    '(recommended - go to Route Info under the cogwheel).')) return ; 
  if(noalt&&!confirm(noalt+' waypoints have no associated altitudes.\n'+
    'You can hit [OK] and I will interpolate altitudes (not guaranteed), or\n'+
    'you can hit [Cancel] and try again later when the altitudes may be '+
    'available.')) return ; 

  if(routetitle=='Untitled Route') filename = '' ; 
  else
  { i = routetitle.indexOf(' ') ;
    if(i<=0) filename = routetitle ; else filename = routetitle.substring(0,i) ;
  }
  filename = prompt("Enter filename: ",filename) ; 
  if(filename==null) return ; 
  else if(routetitle=='Untitled Route') settitle(filename) ; 

  i = filename.length ; 
  if(i==0) { filename = 'route' ; i = 5 ; } 
  if(filename.substring(i-4)!='.tcx') filename += '.tcx' ; 

  if(noalt) interpolate() ; 
  unsavedchanges = [] ; 

  str = '<?xml version="1.0" encoding="UTF-8" standalone="no" ?>\n'+
        '<TrainingCenterDatabase xmlns="http://www.garmin.com/xmlschemas/'+
        'TrainingCenterDatabase/v2" xmlns:xsi="http://www.w3.org/2001/'+
        'XMLSchema-instance" xsi:schemaLocation="http://www.garmin.com/'+
        'xmlschemas/TrainingCenterDatabase/v2 http://www.garmin.com/'+
        'xmlschemas/TrainingCenterDatabasev2.xsd">\n<Courses><Course>\n' ; 
  str += '  <Name>'+routetitle+'</Name>\n  <Lap>\n' ; 
  str += adddist(distance[clen-1]) ; 
  str += '    <TotalTimeSeconds>'+(distance[clen-1]/3).toFixed(0) ; 
  str += '</TotalTimeSeconds>\n' + addpos('Begin',segments[0].data[0].pos) ; 
  str += addpos('End',segments[0].data[clen-1].pos) ; 
  str += '    <Intensity>Active</Intensity>\n' + '  </Lap>\n  <Track>\n' ; 

  // loop over trackpoints
  for(npix=i=0;i<segments[0].data.length;i++) 
  { str += '  <Trackpoint>\n' + addpos('',segments[0].data[i].pos) ; 
    str += adddist(distance[i]) + addalt(segments[0].data[i].h) ;
    time = new Date(distance[i]*1000/3) ; 
    hh = '0' + time.getHours() ;
    mm = '0' + time.getMinutes() ;
    ss = '0' + time.getSeconds() ;
    distance[i] = '    <Time>1970-01-01T' + hh.substring(-2) + ':' ;
    distance[i] += mm.substring(-2) + ':' + ss.substring(-2) + 'Z</Time>\n' ;
    str += distance[i] ;
    if((photo=segments[0].data[i].photo)!=null)
    { str += '    <Extensions><Photo>'+photo+'</Photo></Extensions>\n' ; 
      npix += 1 ; 
    }
    str += '    <SensorState>Absent</SensorState>\n  </Trackpoint>\n' ; 
  }
  str += '  </Track>\n\n' ;

  /* ------------------------------------------------------------------------ */
  /*page*/
  /* ------------------------------------------------------------------------ */

  // record optimisation and photo list
  for(ano=-1,ndel=origlen=0,i=loadno;i>=0;i=actions[i][3]) 
  { ndel += actions[i][4].optim.ndel ; 
    origlen += actions[i][4].optim.origlen ; 
    if(actions[i][4].optim.parms!=null&&ano==-1) ano = i ; 
  }
  if(ndel||(npix>0&&imginfo.status=='ready')) str += '  <Extensions>\n' ;
  if(ndel) 
  { str += '    <Optimised from="'+origlen+'" to="'+(origlen-ndel) ; 
    if(ano>=0) str += '" tol="'+actions[ano][4].optim.parms.tol.toFixed(0)+
              '" maxsep="'+actions[ano][4].optim.parms.maxsep.toFixed(0)+
              '" wppenalty="'+actions[ano][4].optim.parms.wppenalty.toFixed(0)+
              '" vweight="'+actions[ano][4].optim.parms.vweight.toFixed(1) ;
    str += '"/>\n'
  }
  if(npix>0&&imginfo.status=='ready')
  { if(imginfo.status=='tcx') thisuri = imginfo.uri ; 
    else 
    { thisuri = document.URL ;
      if((i=thisuri.lastIndexOf('?'))>=0) thisuri = thisuri.substring(0,i) ; 
      thisuri = reluri(thisuri,imginfo.uri) ; 
    }
    str += '    <PhotoList src="'+thisuri+'"/>\n' ; 
  }
  if(ndel||(npix>0&&imginfo.status=='ready')) str += '  </Extensions>\n\n' ;
  
  // finally loop over coursepoints
  for(i=0;i<segments[0].data.length;i++) 
  { datum = segments[0].data[i] ;
    if(datum.type==null) continue ; 
    str += '  <CoursePoint><Name>'+datum.marker.title+'</Name>\n' ; 
    str += '    <PointType>'+datum.type+'</PointType>\n' ; 
    str += addpos('',segments[0].data[i].pos) + addalt(segments[0].data[i].h) ;
    str += distance[i] + '  </CoursePoint>\n' ;
  }
  str += '</Course></Courses></TrainingCenterDatabase>\n' ; 
  blob = new Blob([str],{type: "text/plain;charset=utf-8"}) ;
  saveAs(blob,filename) ;
}
/* -------------------------------------------------------------------------- */

function addpos(tag,pos)
{ var str = '    <'+tag+'Position>\n      <LatitudeDegrees>' ; 
  str += pos.lat().toFixed(5)+'</LatitudeDegrees>\n      <LongitudeDegrees>' ;
  str += pos.lng().toFixed(5)+'</LongitudeDegrees>\n    </'+tag+'Position>\n' ;
  return str ;
}
function addalt(x)
{ return '    <AltitudeMeters>'+x.toFixed(0)+'</AltitudeMeters>\n' ; }
function adddist(x)
{ return '    <DistanceMeters>'+x.toFixed(0)+'</DistanceMeters>\n' ; }

/* -------------------------------------------------------------------------- */
/*page*/
/* ------------------------------ interpolate ------------------------------- */

function interpolate()
{ infowindow.close() ;
  var response = interpolatework() ; 
  if(response.length>0) done([ 'interpolate' , response ]) ; 
}  
function interpolatework()
{ var i,j,k,response=[],x,y,distance,sum ; 
  var a=segments[0].data,len=a.length ;

  for(i=0;i<len;i=j)
  { for(;i<len&&a[i].h!=null;i++) ;                       // advance to null
    for(j=i+1;j<len&&a[j].h==null;j++) response.push(j) ; // advance to non-null
    if(i==0) { for(y=a[j].h;i<j;i++) a[i].h = y ; continue ; } 
    if(j==len) { for(x=a[i-1].h;i<j;i++) a[i].h = x ; continue ; } 
    distance = new Array(1+j-i) ; 
    for(sum=k=0;k<=j-i;k++) 
      sum = distance[k] = sum + dist(a[i+k-1].pos,a[i+k].pos) ; 
    for(x=a[i-1].h,y=a[j].h,k=0;k<j-i;k++) 
      a[i+k].h = ( x*(sum-distance[i]) + y*distance[i] ) / sum ; 
  }
}
/* -------------------------------------------------------------------------- */

Archived from utils.html

double ranf(),gaussv(),gammav(double),betav(double,double) ; 
int rani(int) ; 
void ranset(int),ranperm(int*,int) ;
void direv(double*,int,double*),direv(double,int,double*) ;
double **ranrot(int) ; 

struct multi { int n,*alias ; double *thresh ; } ;
multi multigen(double *p,int n) ; 
int multiv(multi m) ; 
void multifree(multi m),multichk(double *p,multi m) ;

void *mgvinit(double **,int),multigaussv(double *,void *) ; 
void freemgvstate(void *) ;

Archived from optim.html

/*
  This is NEWUOA for unconstrained minimization. The codes were written
  by Powell in Fortran and then translated to C with f2c. I further
  modified the code to make it independent of libf2c and f2c.h. Please
  refer to "The NEWUOA software for unconstrained optimization without
  derivatives", which is available at www.damtp.cam.ac.uk, for more
  information.
 */
/*
  The original fortran codes are distributed without restrictions. The
  C++ codes are distributed under MIT license.
 */
/* The MIT License

   Copyright (c) 2004, by M.J.D. Powell <mjdp@cam.ac.uk>
                 2008, by Attractive Chaos <attractivechaos@aol.co.uk>
                 2015, by Colin Champion <colin.champion@masterlyinactivity.com>

   Permission is hereby granted, free of charge, to any person obtaining
   a copy of this software and associated documentation files (the
   "Software"), to deal in the Software without restriction, including
   without limitation the rights to use, copy, modify, merge, publish,
   distribute, sublicense, and/or sell copies of the Software, and to
   permit persons to whom the Software is furnished to do so, subject to
   the following conditions:

   The above copyright notice and this permission notice shall be
   included in all copies or substantial portions of the Software.

   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
   MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
   NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
   BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
   ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
   CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
   SOFTWARE.
*/

#include "memory.h"
#include <math.h>
#define pi 3.141592653589793
static int cond = -1 ; 
int uoacond() { return cond ; } 

/* -------------------------------------------------------------------------- */

static double biglag(int n,int npt,double *xopt,double **xp,double **b,
                     double **z,int idz,int knew,double delta,double *d)
{ /* N is the number of variables. NPT is the number of interpolation
   * equations. XOPT is the best interpolation point so far. XPT
   * contains the coordinates of the current interpolation
   * points. BMAT provides the last N columns of H.  ZMAT and IDZ give
   * a factorization of the first NPT by NPT submatrix of H. NDIM is
   * the first dimension of BMAT and has the value NPT+N.  KNEW is the
   * index of the interpolation point that is going to be moved. DELTA
   * is the current trust region bound. D will be set to the step from
   * XOPT to the new point. ALPHA will be set to the KNEW-th diagonal
   * element of the H matrix. HCOL, GC, GD, S and W will be used for
   * working space. */

  /* The step D is calculated in a way that attempts to maximize the
   * modulus of LFUNC(XOPT+D), subject to the bound ||D|| <= DELTA,
   * where LFUNC is the KNEW-th Lagrange function. */

  int i,j,k,iterc,isave ;
  double sp,ss,cf1,cf2,cf3,cf4,cf5,dhd,cth,tau,sth,sum,temp,step,angle,scale ;
  double denom,delsq,tempa,tempb,taubeg,tauold,taumax,dd,gg,alpha ;
  double *hcol=vector(npt),*w=vector(n),*s=vector(n),*gd=vector(n) ; 
  double *gc=vector(n) ; 

  delsq = delta * delta ;

  /* Set the first NPT components of HCOL to the leading elements of
   * the KNEW-th column of H. */
  for(k=0;k<npt;k++) hcol[k] = 0 ;
  for(j=0;j<npt-n-1;j++) 
  { temp = z[knew][j] ;
    if(j<idz) temp = -temp ;
    for(k=0;k<npt;k++) hcol[k] += temp * z[k][j] ;
  }
  alpha = hcol[knew] ;

  /* Set the unscaled initial direction D. Form the gradient of LFUNC
   * atXOPT, and multiply D by the second derivative matrix of LFUNC. */
  for(dd=i=0;i<n;i++) 
  { d[i] = xp[knew][i] - xopt[i] ; gc[i] = b[knew][i] ; dd += d[i]*d[i] ; }

  for(k=0;k<npt;k++) 
  { for(temp=sum=j=0;j<n;j++) 
    { temp += xp[k][j] * xopt[j] ; sum += xp[k][j] * d[j] ; }
    temp *= hcol[k] ;
    sum *= hcol[k] ;
    for(i=0;i<n;i++) 
    { gc[i] += temp * xp[k][i] ; gd[i] += sum * xp[k][i] ; }
  }

  /* Scale D and GD, with a sign change if required. Set S to another
   * vector in the initial two dimensional subspace. */
  for(gg=sp=dhd=i=0;i<n;i++) 
  { gg += gc[i] * gc[i] ; sp += d[i] * gc[i] ; dhd += d[i] * gd[i] ; }

  scale = delta / sqrt(dd) ;
  if(sp*dhd<0) scale = -scale ;
  if(sp*sp>dd*.99*gg) temp = 1 ; else temp = 0 ; 
  tau = scale * (fabs(sp)+0.5*scale*fabs(dhd)) ;
  if(gg*delsq<tau*.01*tau) temp = 1 ;

  for(i=0;i<n;i++) 
  { d[i] *= scale ; gd[i] *= scale ; s[i] = gc[i] + temp*gd[i] ; }

  /* Begin the iteration by overwriting S with a vector that has the
   * required length and direction, except that termination occurs if
   * the given D and S are nearly parallel. */
  for(iterc=0;iterc<n;iterc++) 
  { for(dd=sp=ss=i=0;i<n;i++) 
    { dd += d[i] * d[i] ; sp += d[i] * s[i] ; ss += s[i] * s[i] ; }
    temp = dd*ss - sp*sp ;
    if(temp<=dd*1e-8*ss) break ;
    denom = sqrt(temp);
    for(i=0;i<n;i++) { s[i] = (dd*s[i]-sp*d[i]) / denom ; w[i] = 0 ; }

    /* Calculate the coefficients of the objective function on the
     * circle, beginning with the multiplication of S by the second
     * derivative matrix. */
    for(k=0;k<npt;k++) 
    { for(sum=j=0;j<n;j++) sum += xp[k][j] * s[j] ;
      sum *= hcol[k] ; 
      for(j=0;j<n;j++) w[j] += sum * xp[k][j] ;
    }

    for(cf1=cf2=cf3=cf4=cf5=i=0;i<n;i++) 
    { cf1 += s[i] * w[i];
      cf2 += d[i] * gc[i];
      cf3 += s[i] * gc[i];
      cf4 += d[i] * gd[i];
      cf5 += s[i] * gd[i];
    }

    cf1 /= 2 ;
    cf4 = cf4/2 - cf1 ;
    /* Seek the value of the angle that maximizes the modulus of TAU. */
    taubeg = cf1 + cf2 + cf4 ;
    taumax = tauold = taubeg;
    temp = 2 * pi / 50.0 ;

    for(isave=0,i=1;i<50;tauold=tau,i++) 
    { angle = (double) i*temp;
      cth = cos(angle) ;
      sth = sin(angle) ;
      tau = cf1 + (cf2 + cf4*cth) * cth + (cf3 + cf5*cth) * sth ;
      if(fabs(tau)>fabs(taumax)) 
      { taumax = tau ; isave = i ; tempa = tauold ; } 
      else if(i==isave+1) tempb = tau ;
    }

    if(isave==0) tempa = tau ; else if(isave==49) tempb = taubeg ;
    step = 0 ;
    if(tempa!=tempb) 
    { tempa -= taumax ;
      tempb -= taumax ;
      step = 0.5 * (tempa-tempb) / (tempa+tempb) ;
    }
    angle = temp * (isave+step) ;
    /* Calculate the new D and GD. Then test for convergence. */
    cth = cos(angle) ;
    sth = sin(angle) ;
    tau = cf1 + (cf2 + cf4*cth) * cth + (cf3 + cf5*cth) * sth ;

    for(i=0;i<n;i++) 
    { d[i] = cth*d[i] + sth*s[i] ;
      gd[i] = cth*gd[i] + sth*w[i] ;
      s[i] = gc[i] + gd[i] ;
    }
    if(fabs(tau)<=fabs(taubeg)*1.1) break ;
  }
  free(hcol,w,s,gd,gc) ; 
  return alpha ; 
}
/* -------------------------------------------------------------------------- */
/*page*/
/* -------------------------------------------------------------------------- */

static double bigden(int n,int npt,double *xopt,double **xp,double **b, 
                     double **z,int idz,int kopt,int knew, double *d,double *w,
                     double *vlag)
{ /* N is the number of variables.
   * NPT is the number of interpolation equations.
   * XOPT is the best interpolation point so far.
   * XPT contains the coordinates of the current interpolation points.
   * BMAT provides the last N columns of H.
   * ZMAT and IDZ give a factorization of the first NPT by NPT
     submatrix of H.
   * NDIMis the first dimension of BMAT and has the value NPT+N.
   * KOPT is the index of the optimal interpolation point.
   * KNEW is the index of the interpolation point that is going to be
     moved.
   * D will be set to the step from XOPT to the new point, and on entry it
   * should be the D that was calculated by the last call of BIGLAG. 
     The length of the initial D provides a trust region bound on the final D.
   * W will be set to Wcheck for the final choice of D.
   * VLAG will be set to Theta*Wcheck+e_b for the final choice of D.
   * D is calculated in a way that should provide a denominator with a
     large modulus in the updating formula when the KNEW-th
     interpolation point is shifted to the new position XOPT+D. 
   * the RETURN value beta will be found in the updating formula when
     the KNEW-th interpolation point is moved to its new position.
*/
  int i,j,k,isave,iterc,jc,nw,ksav,ndim=npt+n ;
  double dd,ds,ss,den[9],par[9],tau,sum,diff,temp,step,beta,alpha,angle ;
  double denex[9],tempa,tempb,tempc,ssden,dtest,xoptd,xopts,denold,denmax ; 
  double densav,dstemp,sumold,sstemp,xoptsq ;
  double **prod=matrix(ndim,5),**wvec=matrix(ndim,5),*s=vector(n) ;

  /* Store the first NPT elements of the KNEW-th column of H in W(N+1)
   * to W(N+NPT). */
  for(k=0;k<npt;k++) w[n+k] = 0 ; 
  for(j=0;j<npt-n-1;j++) 
  { temp = z[knew][j] ;
    if(j<idz) temp = -temp ;
    for(k=0;k<npt;k++) w[n+k] += temp * z[k][j] ;
  }
  alpha = w[n+knew] ;
  /* The initial search direction D is taken from the last call of
   * BIGBDLAG, and the initial S is set below, usually to the direction
   * from X_OPT to X_KNEW, but a different direction to an
   * interpolation point may be chosen, in order to prevent S from
   * being nearly parallel to D. */
  for(dd=ds=ss=xoptsq=i=0;i<n;i++)
  { dd += d[i]*d[i] ; 
    s[i] = xp[knew][i] - xopt[i] ;
    ds += d[i] * s[i] ;
    ss += s[i] * s[i] ;
    xoptsq += xopt[i] * xopt[i];
  }

  if(ds*ds>dd*.99*ss) 
  { ksav = knew ;
    dtest = ds * ds / ss ;
    for(k=0;k<npt;k++) if(k!=kopt)
    { for(dstemp=sstemp=i=0;i<n;i++) 
      { diff = xp[k][i] - xopt[i] ;
        dstemp += d[i] * diff ; 
        sstemp += diff*diff ; 
      }
      if(dstemp*dstemp/sstemp<dtest) 
      { ksav = k ;
        dtest = dstemp * dstemp / sstemp ;
        ds = dstemp ;
        ss = sstemp ;
      }
    }
    for(i=0;i<n;i++) s[i] = xp[ksav][i] - xopt[i] ;
  }

  ssden = dd*ss - ds*ds ;
  densav = 0 ;
  /* Begin the iteration by overwriting S with a vector that has the
   * required length and direction. */

  /* ------------------------------------------------------------------------ */

  for(iterc=1;;iterc++)
  { temp = 1.0 / sqrt(ssden) ;
    for(xoptd=xopts=i=0;i<n;i++) 
    { s[i] = temp * (dd*s[i]-ds*d[i]) ;
      xoptd += xopt[i] * d[i] ;
      xopts += xopt[i] * s[i] ;
    }
    /* Set the coefficients of the first 2 terms of BETA. */
    tempa = 0.5 * xoptd * xoptd ;
    tempb = 0.5 * xopts * xopts ;
    den[0] = dd * (xoptsq + 0.5*dd) + tempa + tempb ;
    den[1] = 2 * xoptd * dd ;
    den[2] = 2 * xopts * dd ;
    den[3] = tempa - tempb ;
    den[4] = xoptd * xopts ;
    for(i=4;i<8;i++) den[i+1] = 0 ; 

    /* Put the coefficients of Wcheck in WVEC. */
    for(k=0;k<npt;k++)
    { for(tempa=tempb=tempc=i=0;i<n;i++)
      { tempa += xp[k][i] * d[i];
        tempb += xp[k][i] * s[i];
        tempc += xp[k][i] * xopt[i];
      }
      wvec[k][0] = 0.25 * (tempa*tempa + tempb*tempb) ;
      wvec[k][1] = tempa * tempc ;
      wvec[k][2] = tempb * tempc ;
      wvec[k][3] = 0.25 * (tempa*tempa - tempb*tempb) ;
      wvec[k][4] = 0.5 * tempa * tempb ;
    }
    for(i=0;i<n;i++)
    { wvec[i+npt][0] = 0 ;
      wvec[i+npt][1] = d[i] ;
      wvec[i+npt][2] = s[i] ;
      wvec[i+npt][3] = 0 ;
      wvec[i+npt][4] = 0 ;
    }

    /* Put the coefficents of THETA*Wcheck in PROD. */
    for(jc=0;jc<5;jc++)
    { if(jc==2||jc==3) nw = ndim ; else nw = npt ; 
      for(k=0;k<npt;k++) prod[k][jc] = 0 ;
      for(j=0;j<npt-n-1;j++) 
      { for(sum=k=0;k<npt;k++) sum += z[k][j] * wvec[k][jc] ;
        if(j<idz) sum = -sum ;
        for(k=0;k<npt;k++) prod[k][jc] += sum * z[k][j] ;
      }
      if(nw==ndim) for(k=0;k<npt;k++) 
      { for(sum=j=0;j<n;j++) sum += b[k][j] * wvec[npt+j][jc] ;
        prod[k][jc] += sum ;
      }
      for(j=0;j<n;j++) 
      { for(sum=i=0;i<nw;i++) sum += b[i][j] * wvec[i][jc] ;
        prod[npt+j][jc] = sum ;
      }
    }

    /* Include in DEN the part of BETA that depends on THETA. */
    for(k=0;k<ndim;k++)
    { for(sum=i=0;i<5;i++) sum += ( par[i] = 0.5 * prod[k][i] * wvec[k][i] ) ;
      den[0] -= par[0] + sum ;

      tempa = prod[k][0] * wvec[k][1] + prod[k][1] * wvec[k][0] ;
      tempb = prod[k][1] * wvec[k][3] + prod[k][3] * wvec[k][1] ;
      tempc = prod[k][2] * wvec[k][4] + prod[k][4] * wvec[k][2] ;
      den[1] -= tempa + 0.5 * (tempb+tempc) ;
      den[5] -= 0.5 * (tempb-tempc) ;

      tempa = prod[k][0] * wvec[k][2] + prod[k][2] * wvec[k][0] ;
      tempb = prod[k][1] * wvec[k][4] + prod[k][4] * wvec[k][1] ;
      tempc = prod[k][2] * wvec[k][3] + prod[k][3] * wvec[k][2] ;
      den[2] -= tempa + 0.5 * (tempb-tempc) ;
      den[6] -= 0.5 * (tempb+tempc) ;

      tempa = prod[k][0] * wvec[k][3] + prod[k][3] * wvec[k][0] ;
      den[3] -= tempa + par[1] - par[2] ;

      tempa = prod[k][0] * wvec[k][4] + prod[k][4] * wvec[k][0] ;
      tempb = prod[k][1] * wvec[k][2] + prod[k][2] * wvec[k][1] ;
      den[4] -= tempa + 0.5 * tempb ;
      den[7] -= par[3] - par[4] ;

      tempa = prod[k][3] * wvec[k][4] + prod[k][4] * wvec[k][3] ;
      den[8] -= 0.5 * tempa ;
    }

    /* Extend DEN so that it holds all the coefficients of DENOM. */
    for(sum=i=0;i<5;i++) 
    { tempa = prod[knew][i] ; sum += ( par[i] = 0.5 * tempa * tempa ) ; }
    denex[0] = alpha*den[0] + par[0] + sum ;

    tempa = 2 * prod[knew][0] * prod[knew][1] ;
    tempb = prod[knew][1] * prod[knew][3] ;
    tempc = prod[knew][2] * prod[knew][4] ;
    denex[1] = alpha*den[1] + tempa + tempb + tempc ;
    denex[5] = alpha*den[5] + tempb - tempc ;

    tempa = 2 * prod[knew][0] * prod[knew][2] ;
    tempb = prod[knew][1] * prod[knew][4] ;
    tempc = prod[knew][2] * prod[knew][3] ;
    denex[2] = alpha*den[2] + tempa + tempb - tempc ;
    denex[6] = alpha*den[6] + tempb + tempc ;

    tempa = 2 * prod[knew][0] * prod[knew][3] ;
    denex[3] = alpha*den[3] + tempa + par[1] - par[2] ;

    tempa = 2 * prod[knew][0] * prod[knew][4] ;
    denex[4] = alpha*den[4] + tempa + prod[knew][1]*prod[knew][2] ;
    denex[7] = alpha*den[7] + par[3] - par[4] ;
    denex[8] = alpha*den[8] + prod[knew][3]*prod[knew][4] ;

    /* Seek the value of the angle that maximizes the modulus of DENOM. */
    sum = denex[0] + denex[1] + denex[3] + denex[5] + denex[7];
    denold = denmax = sum ;
    temp = 2 * pi / 50 ;
    par[0] = 1 ;
    for(isave=0,i=1;i<50;i++)
    { angle = i * temp ;
      par[1] = cos(angle) ;
      par[2] = sin(angle) ;
      for(j=4;j<9;j+=2)
      { par[j-1] = par[1]*par[j-3] - par[2]*par[j-2] ;
        par[j] = par[1]*par[j-2] + par[2]*par[j-3] ;
      }
      sumold = sum ;
      for(sum=j=0;j<9;j++) sum += denex[j]*par[j] ;
      if(fabs(sum)>fabs(denmax)) { denmax = sum ; isave = i ; tempa = sumold ; }
      else if(i==isave+1) tempb = sum ;
    }

    if(isave==0) tempa = sum ; else if(isave==49) tempb = denold ;
    step = 0 ;
    if(tempa!=tempb) 
    { tempa -= denmax ;
      tempb -= denmax ;
      step = 0.5 * (tempa-tempb) / (tempa+tempb) ;
    }

    /* Calculate the new parameters of the denominator, the new VLAG
     * vector and the new D. Then test for convergence. */
    angle = temp * (isave+step) ;
    par[1] = cos(angle) ;
    par[2] = sin(angle) ;
    for(j=4;j<9;j+=2) 
    { par[j-1] = par[1]*par[j-3] - par[2]*par[j-2] ;
      par[j] = par[1]*par[j-2] + par[2]*par[j-3] ;
    }
    for(denmax=beta=j=0;j<9;j++)
    { beta += den[j]*par[j] ; denmax += denex[j]*par[j] ; } 

    for(k=0;k<ndim;k++) for(vlag[k]=j=0;j<5;j++)
      vlag[k] += prod[k][j] * par[j] ;

    tau = vlag[knew] ;
    for(dd=tempa=tempb=i=0;i<n;i++)
    { d[i] = par[1]*d[i] + par[2]*s[i] ;
      w[i] = xopt[i] + d[i] ;
      dd += d[i] * d[i] ;
      tempa += d[i] * w[i] ;
      tempb += w[i] * w[i] ;
    }

    if(iterc>=n) break ;
    if(iterc>1) densav = fmax(densav, denold) ;
    if(fabs(denmax)<=fabs(densav)*1.1) break ;
    densav = denmax ;

    /* Set S to 0.5 the gradient of the denominator with respect to
     * D. Then branch for the next iteration. */
    for(i=0;i<n;i++)
    { temp = tempa*xopt[i] + tempb*d[i] - vlag[npt+i] ;
      s[i] = tau*b[knew][i] + alpha*temp ;
    }

    for(k=0;k<npt;k++)
    { for(sum=j=0;j<n;j++) sum += xp[k][j] * w[j] ;
      temp = (tau*w[n+k]-alpha*vlag[k]) * sum ;
      for(i=0;i<n;i++) s[i] += temp * xp[k][i] ;
    }

    for(ss=ds=i=0;i<n;i++) { ss += s[i]*s[i] ; ds += d[i]*s[i] ; }
    ssden = dd*ss - ds*ds ;
    if(ssden<dd*1e-8*ss) break ;
  }

  /* Set the vector W before the RETURN from the subroutine. */
  for(k=0;k<ndim;k++) for(w[k]=j=0;j<5;j++) w[k] +=  wvec[k][j] * par[j] ;
  vlag[kopt] += 1 ;
  freematrix(prod,wvec) ; free(s) ; 
  return beta ;
}
/* -------------------------------------------------------------------------- */
/*page*/
/* -------------------------------------------------------------------------- */

/* The following instructions set the vector HD to the vector D multiplied 
   by the second derivative matrix of Q.  */

static void 
  sethd(double *hd,int n,double **xp,double *d,double *hq,double *pq,int npt)
{ int i,j,k,ih ; 
  double temp ; 

  for(i=0;i<n;i++) hd[i] = 0 ; 
  for(k=0;k<npt;k++) 
  { for(temp=j=0;j<n;j++) temp += xp[k][j] * d[j] ;
    temp *= pq[k] ; 
    for(i=0;i<n;i++) hd[i] += temp * xp[k][i] ;
  }
  for(ih=j=0;j<n;j++) for(i=0;i<=j;ih++,i++) 
  { if(i<j) hd[j] += hq[ih] * d[i] ; hd[i] += hq[ih]*d[j] ; }
}
/* -------------------------------------------------------------------------- */

static double trsapp(int n, int npt, double * xopt, double **xp, double * gq,  
                     double * hq, double * pq,double delsq, double * step)
{ /* The arguments NPT, XOPT, XPT, GQ, HQ and PQ have their usual meanings, 
   * in order to define the current quadratic model Q.
   * DELTA is the trust region radius, and has to be positive. STEP
   * will be set to the calculated trial step. CRVMIN will be returned as the
   * least curvature of H aint the conjugate directions that occur,
   * except that it is set to 0 ifSTEP goes all the way to the trust
   * region boundary. The calculation of STEP begins with the
   * truncated conjugate gradient method. If the boundary of the trust
   * region is reached, then further changes to STEP may be made, each
   * one being in the 2D space spanned by the current STEP and the
   * corresponding gradient of Q. Thus STEP should provide a
   * substantial reduction to Q within the trust region. */

  int i,j,iterc,isave,itermax ;
  double dd,cf,dg,gg,ds,sg,ss,dhd,dhs,cth,sgk,shs,sth,qadd,qbeg,qred,qmin ;
  double temp,qsav,qnew,ggbeg,alpha,angle,reduc,ggsav,tempa,tempb,bstep ;
  double ratio,angtest,crvmin ;
  double *d=vector(n),*g=vector(n),*hd=vector(n),*hs=vector(n) ; 

  itermax = n ;
  for(i=0;i<n;i++) d[i] = xopt[i] ; 
  sethd(hd,n,xp,d,hq,pq,npt) ;
  /* Prepare for the first line search. */
  for(qred=dd=i=0;i<n;i++)
  { step[i] = hs[i] = 0 ; 
    g[i] = gq[i] + hd[i] ;
    d[i] = -g[i] ; 
    dd += d[i]*d[i] ;
  }
  if(dd==0) { free(d,g,hd,hs) ; return 0 ; }
  ggbeg = gg = dd ;

  /* ------------------------------------------------------------------------ */
  /*page*/
  /* -- Calculate the step to the trust region boundary and the product HD -- */

  for(ds=ss=0,iterc=1;ss<delsq;iterc++)
  { temp = delsq - ss;
    bstep = temp / (ds + sqrt(ds * ds + dd * temp));
    sethd(hd,n,xp,d,hq,pq,npt) ;

    for(dhd=j=0;j<n;j++) dhd += d[j] * hd[j] ;
    /* Update CRVMIN and set the step-length ATRLPHA. */
    alpha = bstep ;
    if(dhd>0) 
    { if(iterc==1) crvmin = dhd/dd ; else crvmin = fmin(crvmin,dhd/dd) ;
      alpha = fmin(alpha,gg/dhd) ;
    }
    qred += ( qadd = alpha * (gg - 0.5*alpha*dhd) ) ;

    /* Update STEP and HS. */
    for(ggsav=gg,gg=i=0;i<n;i++)
    { step[i] += alpha * d[i] ; 
      hs[i] += alpha * hd[i] ; 
      temp = g[i] + hs[i] ; 
      gg += temp * temp ; 
    }

    /* Begin another conjugate direction iteration if required. */
    if(alpha>=bstep) break ; 
    ds = 0 ; 
    if(qadd>qred*.01&&gg>ggbeg*1e-4&&iterc<itermax) 
    { temp = gg / ggsav ;
      for(dd=ss=i=0;i<n;i++)
      { d[i] = temp*d[i] - g[i] - hs[i] ; 
        dd += d[i]*d[i] ; 
        ds += d[i]*step[i] ; 
        ss += step[i]*step[i] ;
      }
    }
    if(ds<=0) { free(d,g,hd,hs) ; return crvmin ; }
  }
  /* ------------------------------------------------------------------------ */
  /*page*/
  /* ------------ Test whether an alternative iteration is required --------- */

  for(iterc++;gg>ggbeg*1e-4;iterc++)
  { for(sg=shs=i=0;i<n;i++) { sg += step[i]*g[i] ; shs += step[i]*hs[i] ; } 
    sgk = sg + shs ;
    angtest = sgk / sqrt(gg*delsq) ;
    if(angtest<=-.99) break ; 
    /* Begin the alternative iteration by calculating D and HD and some
     * scalar products. */
    temp = sqrt(delsq*gg - sgk*sgk) ;
    tempa = delsq / temp ;
    tempb = sgk / temp ;
    for(i=0;i<n;i++) d[i] = tempa*(g[i]+hs[i]) - tempb*step[i] ; 
    sethd(hd,n,xp,d,hq,pq,npt) ;

    for(dg=dhd=dhs=i=0;i<n;i++)
    { dg += d[i]*g[i] ; dhd += hd[i]*d[i] ; dhs += hd[i]*step[i] ; }

    /* Seek the value of the angle that minimizes Q. */
    cf = 0.5 * (shs-dhd) ;
    qbeg = sg + cf ;
    qsav = qmin = qbeg ;
    temp = 2 * pi / 50 ;

    for(isave=0,i=1;i<50;i++)
    { angle = i * temp ;
      cth = cos(angle) ;
      sth = sin(angle) ;
      qnew = (sg+cf*cth)*cth + (dg+dhs*cth)*sth ;
      if(qnew<qmin) { qmin = qnew ; isave = i ; tempa = qsav ; }
      else if(i==isave+1) tempb = qnew ;
      qsav = qnew ;
    }
    if(isave==0) tempa = qnew ; else if(isave==49) tempb = qbeg ;
    angle = 0 ;
    if(tempa!=tempb) 
    { tempa -= qmin ; 
      tempb -= qmin ;
      angle = 0.5 * (tempa-tempb) / (tempa+tempb) ;
    }
    angle = temp * (isave+angle) ;

    /* Calculate the new STEP and HS. Then test for convergence. */
    cth = cos(angle) ;
    sth = sin(angle) ;
    reduc = qbeg - (sg + cf * cth) * cth - (dg + dhs * cth) * sth ;
    for(gg=i=0;i<n;i++)
    { step[i] = cth*step[i] + sth*d[i] ; 
      hs[i] = cth*hs[i] + sth*hd[i] ; 
      temp = g[i] + hs[i] ;
      gg += temp*temp ;
    }

    qred += reduc ;
    ratio = reduc / qred ;
    if(iterc>=itermax||ratio<=0.01) break ; 
  }
  free(d,g,hd,hs) ; 
  return 0 ; 
}
/* -------------------------------------------------------------------------- */
/*page*/
/* -------------------------------------------------------------------------- */

static int update(int n, int npt, double **b, double **z, int idz, 
                  double *vlag,double beta, int knew)
{
    /* The arrays BMAT and ZMAT with IDZ are updated, in order to shift
     * the interpolation point that has index KNEW. On entry, VLAG
     * contains the components of the vector Theta*Wcheck+e_b of the
     * updating formula (6.11), and BETA holds the value of the
     * parameter that has this name. */

  int i,j, ja, jb, jl, jp, iflag;
  double tau,temp,scala,scalb,alpha,denom,tempa,tempb,tausq,*w=vector(npt+n) ;

    /* Apply the rotations that put zeros in the KNEW-th row of ZMAT. */

  for(jl=0,j=1;j<npt-n-1;j++)
  { if(j==idz)  jl = j ; 
    else if(z[knew][j]!=0) 
    { tempa = z[knew][jl];
      tempb = z[knew][j];
      temp = sqrt(tempa*tempa+tempb*tempb) ;
      tempa /= temp ;
      tempb /= temp ;
      for(i=0;i<npt;i++)
      { temp = tempa*z[i][jl] + tempb*z[i][j] ;
        z[i][j] = tempa*z[i][j] - tempb*z[i][jl] ;
        z[i][jl] = temp ;
      }
      z[knew][j] = 0;
    }
  }

  /* Put the first NPT components of the KNEW-th column of HLAG into
   * W, and calculate the parameters of the updating formula. */
  tempa = z[knew][0] ;
  if(idz>=1) tempa = -tempa ;
  for(i=0;i<npt;i++) w[i] = tempa * z[i][0] ; 
  if(jl>0) for(tempb=z[knew][jl],i=0;i<npt;i++) w[i] += tempb * z[i][jl] ; 

  alpha = w[knew] ;
  tau = vlag[knew] ;
  tausq = tau * tau ;
  denom = alpha*beta + tausq ;
  vlag[knew] -= 1 ;
  /* Complete the updating of ZMAT when there is only 1.0 nonzero
   * element in the KNEW-th row of the new matrix ZMAT, but, ifIFLAG
   * is set to 1.0, then the first column of ZMAT will be exchanged
   * with another 1.0 later. */
  iflag = 0 ;

  /* ------------------------------------------------------------------------ */
  /*page*/
  /* ------------------------------------------------------------------------ */

  if(jl==0) 
  { temp = sqrt(fabs(denom)) ;
    tempb = tempa / temp ;
    tempa = tau / temp ;
    for(i=0;i<npt;i++) z[i][0] = tempa*z[i][0] - tempb*vlag[i] ;

    if(idz==0&&temp<0) idz = 1 ;
    if(idz>=1&&temp>=0) iflag = 1 ;
  } 
  else 
  { /* Complete the updating of ZMAT in the alternative case. */
    ja = 0 ;
    if(beta>=0) ja = jl ;
    jb = jl - ja ;
    temp = z[knew][jb] / denom ;
    tempa = temp * beta ;
    tempb = temp * tau ;
    temp = z[knew][ja] ;
    scala = 1.0 / sqrt(fabs(beta)*temp*temp+tausq) ;
    scalb = scala * sqrt((fabs(denom))) ;
    for(i=0;i<npt;i++)
    { z[i][ja] = scala * (tau * z[i][ja] - temp * vlag[i]) ;
      z[i][jb] = scalb * (z[i][jb] - tempa * w[i] - tempb * vlag[i]) ;
    }
    if(denom<=0) { if(beta<0) idz += 1 ; else iflag = 1 ; }
  }

  /* IDZ is reduced in the following case, and usually the first
   * column of ZMAT is exchanged with a later 1.0. */
  if(iflag==1) for(idz-=1,i=0;i<npt;i++) swap(z[i][0],z[i][idz]) ; 

  /* Finally, update the matrix BMAT. */
  for(j=0;j<n;j++)
  { jp = npt + j ; 
    w[jp] = b[knew][j] ;
    tempa = (alpha*vlag[jp] - tau*w[jp]) / denom ;
    tempb = -(beta*w[jp] + tau*vlag[jp]) / denom ;
    for(i=0;i<=jp;i++)
    { b[i][j] = b[i][j] + tempa*vlag[i] + tempb*w[i] ; // !! ** !!
      if(i>=npt) b[jp][i-npt] = b[i][j];
    }
  }
  free(w) ; 
  return idz ;
}
/* -------------------------------------------------------------------------- */
/*page*/
/* -------------------------------------------------------------------------- */

static double setrhodelta(double *rho,double rhoend,double *delta)
{ double ratio=rho[0]/rhoend ; 

  delta[0] = rho[0] / 2 ; 
  if(ratio<=16) rho[0] = rhoend ;
  else if(ratio<=250) rho[0] = sqrt(ratio) * rhoend ;
  else rho[0] = 0.1 * rho[0] ; // !! ** !!

  delta[0] = fmax(delta[0],rho[0]) ;
  return ratio ; 
}
static double setknewdistsq
  (int n,int npt,double delta,int *knew,double *xopt,double **xp) 
{ int j,k ;
  double sum,temp,distsq=4*delta*delta ;
  for(k=0;k<npt;k++)
  { for(sum=j=0;j<n;j++) { temp = xp[k][j] - xopt[j] ; sum += temp*temp ; }
    if(sum>distsq) { knew[0] = k ; distsq = sum ; } 
  }
  return distsq ; 
}
/* -------------------------------------------------------------------------- */

double uoamin(double (*func)(double*),double *x,int n,
              double rhobeg,double rhoend,int maxfun)
{
    /* This subroutine seeks the least value of a function of many
     * variables, by a trust region method that forms quadratic models
     * by interpolation. There can be some freedom in the interpolation
     * conditions, which is taken up by minimizing the Frobenius norm of
     * the change to the second derivative of the quadratic model,
     * beginning with a zero matrix. The arguments of the subroutine are
     * as follows. */

    /* N must be set to the number of variables and must be at least
     * two. NPT is the number of interpolation conditions. Its value
     * must be in the interval [N+2,(N+1)(N+2)/2]. Initial values of the
     * variables must be set in X(1),X(2),...,X(N). They will be changed
     * to the values that give the least calculated F. xinc and tol
     * must be set to the initial and final values of a trust region
     * radius, so both must be positive with tol<=xinc. Typically
     * xinc should be about one tenth of the greatest expected change
     * to a variable, and tol should indicate the accuracy that is
     * required in the final values of the variables. MAXFUN must be set
     * to an upper bound on the number of calls of CALFUN.  */

  /* SUBROUTINE func(x) must be provided by the user. */

  /* XBASE will hold a shift of origin that should reduce the contributions
   * from rounding errors to values of the model and Lagrange functions.
   * XOPT will be set to the displacement from XBASE of the vector of
     variables that provides the least calculated F so far.
   * XNEW will be set to the displacement from XBASE of the vector of
     variables for the current calculation of F. */

  /* ------------------------------------------------------------------------ */
  /*page*/
  /* ------------------------------------------------------------------------ */

  /* XPT will contain the interpolation point coordinates relative to XBASE.
   * FVAL will hold the values of F at the interpolation points.
   * GQ will hold the gradient of the quadratic model at XBASE.
   * HQ will hold the explicit second derivatives of the quadratic model.
   * PQ will contain the parameters of the implicit second derivatives
     of the quadratic model.
   * BMAT will hold the last N columns of H.
   * ZMAT will hold the factorization of the leading NPT by NPT submatrix
   * of H, this factorization being ZMAT times Diag(DZ) times ZMAT^T,
   * where the elements of DZ are plus or minus 1.0, asspecified by IDZ.
   * NDIM is the first dimension of BMAT and has the value NPT+N.
   * D is reserved for trial steps from XOPT.
   * VLAG will contain the values of the Lagrange functions at a new point X.
   * They are part of a product that requires VLAG to be of length NDIM. */

  int i,j,k,ih,nf,nh,nfm,idz,ipt,jpt,nfmm,knew,kopt,nptm,ksave,nfsav,itemp ; 
  int ktemp,itest,npt=2*n+1 ;
  double f,dx,dsq,rho,sum,fbeg,diff,beta,gisq,temp,suma,sumb,fopt,bsum,gqsq ; 
  double xipt,xjpt,sumz,diffa,diffb,diffc,hdiag,alpha,delta,recip,reciq,fsave ;
  double dnorm,ratio,dstep,vquad,tempq,rhosq,detrat,crvmin,distsq,xoptsq,qtemp ;

  double *xbase=vector(n),*xopt=vector(n),*xnew=vector(n),*fval=vector(npt) ; 
  double *gq=vector(n),*hq=vector((n*(n+1))/2),*pq=vector(npt) ;
  double *d=vector(n),*vlag=vector(npt+n),*w=vector(2*npt) ; 
  double **z=matrix(npt,npt-(n+1)),**b=matrix(npt+n,n),**xp=matrix(npt,n) ;

  nh = (n*(n+1)) / 2 ;
  nptm = npt - (n+1) ;
  if(maxfun<1) maxfun = 1 ; 
  for(j=0;j<n;j++) xbase[j] = x[j] ; 

  /* Begin the initialization procedure. NF becomes 1 more than the
   * number of function values so far. The coordinates of the
   * displacement of the next initial interpolation point from XBASE
   * are set in XPT(NF,.). */

  rhosq = rhobeg * rhobeg ;
  recip = 1 / rhosq ;
  reciq = sqrt(0.5) / rhosq ;

  /* ------------------------------------------------------------------------ */
  /*page*/
  /* ------------------------------------------------------------------------ */

  for(nf=0;nf<npt;nf++)
  { nfm = nf-1 ;
    nfmm = nfm-n ;

    if(nfm<2*n) // always happens when npt = 2*n+1
    { if(nfm>=0&&nfm<n) xp[nf][nfm] = rhobeg ;
      else if(nfm>=n) xp[nf][nfmm] = -rhobeg ;
    } 
    else        // the case which never happens
    { itemp = nfmm / n ;
      jpt = nfm - itemp * n - n ;
      ipt = jpt + itemp ;
      if(ipt>=n) { ipt -= n ; swap(ipt,jpt) ; } 
      xipt = rhobeg ;
      if(fval[ipt+n+1] < fval[ipt+1]) xipt = -xipt ;
      xjpt = rhobeg ;
      if(fval[jpt+n+1] < fval[jpt+1]) xjpt = -xjpt ;
      xp[nf][ipt] = xipt ;
      xp[nf][jpt] = xjpt ;
    }
    /* Calculate the next value of F. The least function value so far and its
     * index are required. */
    for(j=0;j<n;j++) x[j] = xp[nf][j] + xbase[j];
    fval[nf] = f = func(x) ; 

    if(nf==0) { fbeg = fopt = f ; kopt = 0 ; }
    else if(f<fopt) { fopt = f ; kopt = nf ; }

    /* Set the non0 initial elements of BMAT and the quadratic model
     * in the cases when NF is at most 2*N+1. */
    if(nfm<2*n) 
    { if(nfm>=0&&nfm<n) 
      { gq[nfm] = (f-fbeg) / rhobeg ;
        if(npt<=nf+n) 
        { b[0][nfm] = -1/rhobeg ; 
          b[nf][nfm] = 1/rhobeg ; 
          b[npt+nfm-1][nfm] = -rhosq/2 ; 
        }
      } 
      else if(nfm>=n) 
      { b[nf-n][nfmm] = 1/(2*rhobeg) ; 
        b[nf][nfmm] = -1/(2*rhobeg) ; 
        z[0][nfmm] = -(reciq+reciq) ;
        z[nf][nfmm] = z[nf-n][nfmm] = reciq ;
        ih = (nfmm * (nfmm+3)) / 2 ;
        temp = (fbeg-f) / rhobeg ;
        hq[ih] = (gq[nfmm]-temp) / rhobeg;
        gq[nfmm] = .5 * (gq[nfmm] + temp);
      }
    }
        /* Set the off-diagonal second derivatives of the Lagrange
         * functions and the initial quadratic model. */
    else 
    { ih = (ipt*(ipt+1))/2 + jpt ;
      if(xipt<0) ipt += n ;
      if(xjpt<0) jpt += n ;
      z[0][nfmm] = z[nf][nfmm] = recip ;
      z[ipt][nfmm] = z[jpt][nfmm] = -recip ;
      hq[ih] = (fbeg - fval[ipt+1] - fval[jpt+1] + f) / (xipt*xjpt) ;
    }
  }
  /* ------------------------------------------------------------------------ */
  /*page*/
  /* - Begin the iterative procedure, because the initial model is complete - */

  diffa = diffb = itest = idz = 0 ;
  for(xoptsq=i=0;i<n;i++) 
  { xopt[i] = xp[kopt][i] ; xoptsq += xopt[i]*xopt[i] ; }
    /* Generate the next trust region step and test its length. Set KNEW
     * to -1 ifthe purpose of the next F will be to improve the
     * model. */

  /* ------------------------------------------------------------------------ */

  for(knew=-1,delta=rho=rhobeg,nfsav=nf;;)
  { if(knew<0) for(;;)
    { crvmin = trsapp(n,npt,xopt,xp,gq,hq,pq,delta*delta,d) ;
      for(dsq=i=0;i<n;i++) dsq += d[i]*d[i] ; 
      dnorm = fmin(delta,sqrt(dsq)) ;
      if(dnorm>=rho/2) break ; // knew must be -1
      delta /= 10 ;
      ratio = -1 ;
      if(delta<=rho*1.5) delta = rho ;
      temp = crvmin * .125 * rho * rho ;
      if(nf<=nfsav+2||temp<=diffa||temp<=diffb||temp<=diffc) 
      { distsq = setknewdistsq(n,npt,delta,&knew,xopt,xp) ;
        /* If KNEW is positive, then set DSTEP, and branch back for the next
         * iteration, which will generate a "model step". */
        if(knew>=0) 
        { dstep = fmax(rho,fmin(sqrt(distsq)/10,delta/2)) ; 
          dsq = dstep*dstep ; 
          break ;
        }
        else if(delta>rho||dnorm>rho) continue ;
      }
      /* Return from the calculation, after another Newton-Raphson step,
       * ifit is too short to have been tried before. */
      if(rho<=rhoend) 
      { for(i=0;i<n;i++) 
        { xnew[i] = xopt[i] + d[i] ; x[i] = xbase[i] + xnew[i] ; }
        f = func(x) ;
        knew = -2 ; 
        break ;
      }
      /* The calculations with the current value of RHO are complete. Pick
       * the next values of RHO and DELTA. */
      ratio = setrhodelta(&rho,rhoend,&delta) ; 
      nfsav = nf ; 
    }
    if(knew==-2) { cond = 2 ; break ; }

    /* ---------------------------------------------------------------------- */
    /*page*/
    /* ---------------------------------------------------------------------- */

    /* Shift XBASE if XOPT may be too far from XBASE. First make the
     * changes to BMAT that do not depend on ZMAT. */
    if(dsq<=xoptsq/1000) 
    { tempq = xoptsq/4 ;
      for(k=0;k<npt;k++)
      { for(sum=i=0;i<n;i++) sum += xp[k][i] * xopt[i];
        temp = pq[k] * sum ;
        sum -= xoptsq/2 ;
        w[npt+k] = sum ;
        for(i=0;i<n;i++)
        { gq[i] += temp * xp[k][i] ;
          xp[k][i] -= xopt[i]/2 ;
          vlag[i] = b[k][i];
          w[i] = sum*xp[k][i] + tempq*xopt[i] ;
          for(j=0;j<=i;j++) // !! ** !!
            b[npt+i][j] = b[npt+i][j] + vlag[i]*w[j] + w[i]*vlag[j] ;
        }
      }
      /* Then the revisions of BMAT that depend on ZMAT are calculated. */
      for(k=0;k<nptm;k++)
      { for(sumz=i=0;i<npt;i++)
        { sumz += z[i][k] ; w[i] = w[npt+i] * z[i][k] ; }
        for(j=0;j<n;j++)
        { sum = tempq * sumz * xopt[j];
          for(i=0;i<npt;i++) sum += w[i] * xp[i][j];
          vlag[j] = sum;
          if(k<idz) sum = -sum;
          for(i=0;i<npt;i++) b[i][j] += sum * z[i][k];
        }
        for(i=0;i<n;i++)
        { if(k>=idz) temp = vlag[i] ; else temp = -vlag[i] ; 
          for(j=0;j<=i;j++) b[npt+i][j] += temp * vlag[j] ;
        }
      }
      /* The following instructions complete the shift of XBASE,
       * including the changes to the parameters of the quadratic
       * model. */
      for(ih=j=0;j<n;j++)
      { for(w[j]=k=0;k<npt;k++)
        { w[j] += pq[k] * xp[k][j] ; xp[k][j] -= xopt[j]/2 ; }
        for(i=0;i<=j;ih++,i++)
        { if(i<j) gq[j] += hq[ih] * xopt[i] ;
          gq[i] += hq[ih] * xopt[j] ;
          hq[ih] = hq[ih] + w[i]*xopt[j] + xopt[i]*w[j] ;
          b[npt+i][j] = b[npt+j][i] ;
        }
      }
      for(xoptsq=j=0;j<n;j++) { xbase[j] += xopt[j] ; xopt[j] = 0 ; }
    }

    /* Pick the model step ifKNEW is positive. A different choice of D
     * may be made later, ifthe choice of D by BIGLAG causes
     * substantial cancellation in DENOM. */
    if(knew >= 0) alpha = biglag(n,npt,xopt,xp,b,z,idz,knew,dstep,d) ;

    /* Calculate VLAG and BETA for the current choice of D. The first
     * NPT components of W_check will be held in W. */
    for(k=0;k<npt;k++)
    { for(vlag[k]=suma=sumb=j=0;j<n;j++)
      { suma += xp[k][j] * d[j] ;
        sumb += xp[k][j] * xopt[j] ;
        vlag[k] += b[k][j] * d[j] ;
      }
      w[k] = suma * (suma/2+sumb) ;
    }
    /* ---------------------------------------------------------------------- */
    /*page*/
    /* ---------------------------------------------------------------------- */

    for(beta=k=0;k<nptm;k++)
    { for(sum=i=0;i<npt;i++) sum += z[i][k] * w[i] ;
      if(k<idz) { beta += sum*sum ; sum = -sum ; } else beta -= sum*sum ; 
      for(i=0;i<npt;i++) vlag[i] += sum * z[i][k];
    }
    for(bsum=dx=j=0;j<n;j++)
    { for(sum=i=0;i<npt;i++) sum += w[i] * b[i][j];
      bsum += sum * d[j] ;
      for(k=0;k<n;k++) sum += b[npt+j][k] * d[k];
      vlag[npt+j] = sum ; 
      bsum += sum * d[j] ; 
      dx += d[j] * xopt[j] ; 
    }

    // more numerical instability here? 
    beta = dx*dx + dsq*((xoptsq+dx) + dx + dsq/2) + beta - bsum ;
    vlag[kopt] += 1 ;

    /* If KNEW is positive and if the cancellation in DENOM is unacceptable, 
     * then BIGDEN calculates an alternative model step. */
    if(knew>=0&&fabs(1 + alpha*beta / (vlag[knew]*vlag[knew]))<=0.8) 
      beta = bigden(n,npt,xopt,xp,b,z,idz,kopt,knew,d,w,vlag) ;

    /* Calculate the next value of the objective function. */
    for(i=0;i<n;i++) { xnew[i] = xopt[i] + d[i] ; x[i] = xbase[i] + xnew[i] ; }
    nf += 1 ; 
    if(nf>=maxfun) break ;
    f = func(x) ;

    /* Use the quadratic model to predict the change in F due to the
     * step D, and set DIFF to the error of this prediction. */
    for(vquad=ih=j=0;j<n;j++)
    { vquad += d[j] * gq[j] ; 
      for(i=0;i<=j;ih++,i++)
      { temp = d[i]*xnew[j] + d[j]*xopt[i] ;
        if(i==j) temp /= 2 ;
        vquad += temp * hq[ih] ;
      }
    }

    for(k=0;k<npt;k++) vquad += pq[k] * w[k] ;
    diff = f - fopt - vquad ;
    diffc = diffb ;
    diffb = diffa ;
    diffa = fabs(diff) ;
    if(dnorm>rho) nfsav = nf ;
    /* Update FOPT and XOPT ifthe new F is the least value of the
     * objective function so far. The branch when KNEW is positive
     * occurs ifD is not a trust region step. */
    fsave = fopt ;
    if(f<fopt) 
    { fopt = f ;
      for(xoptsq=i=0;i<n;i++)
      { xopt[i] = xnew[i] ; xoptsq += xopt[i] * xopt[i] ; }
    }
    ksave = knew ;

    /* ---------------------------------------------------------------------- */
    /*page*/
    /* ---------------------------------------------------------------------- */

    if(knew<0) 
    { /* Pick the next value of DELTA after a trust region step. */
      if(vquad>=0) { cond = 1 ; break ; }
      ratio = (f-fsave) / vquad ;
      if(ratio<=0.1) delta = dnorm/2 ;
      else if(ratio<=.7) delta = fmax(delta/2,dnorm) ;
      else delta = fmax(delta/2,2*dnorm) ;

      if(delta<=rho*1.5) delta = rho;
      /* Set KNEW to the index of the next interpolation point to be
       * deleted. */
      temp = fmax(delta/10, rho);
      rhosq = temp * temp ;
      if(f>=fsave) { ktemp = kopt ; detrat = 1 ; }
      else { ktemp = -1 ; detrat = 0 ; } 

      for(k=0;k<npt;k++)
      { for(hdiag=j=0;j<nptm;j++) 
          if(j<idz) hdiag -= z[k][j]*z[k][j] ; else hdiag += z[k][j]*z[k][j] ; 
        temp = fabs(beta*hdiag + vlag[k]*vlag[k]);
        for(distsq=j=0;j<n;j++) 
        { qtemp = xp[k][j] - xopt[j] ; distsq += qtemp*qtemp ; }
        if(distsq>rhosq) 
        { qtemp = distsq / rhosq ; temp *= qtemp * qtemp * qtemp ; }
        if(temp>detrat&&k!=ktemp) { detrat = temp ; knew = k ; } 
      }
    }
    if(knew>=0)
    { /* Update BMAT, ZMAT and IDZ, so that the KNEW-th interpolation
      * point can be moved. Begin the updating of the quadratic model,
      * starting with the explicit second derivative term. */
      idz = update(n,npt,b,z,idz,vlag,beta,knew) ;
      fval[knew] = f ;

      for(ih=i=0;i<n;i++)
      { temp = pq[knew] * xp[knew][i] ;
        for(j=0;j<=i;ih++,j++) hq[ih] += temp * xp[knew][j] ;
      }

      pq[knew] = 0 ;
      /* Update the other second derivative parameters, and then the
       * gradient vector of the model. Also include the new interpolation
       * point. */
      for(j=0;j<nptm;j++)
      { temp = diff * z[knew][j] ;
        if(j<idz) temp = -temp ; 
        for(k=0;k<npt;k++) pq[k] += temp * z[k][j] ;
      }

      for(gqsq=i=0;i<n;i++)
      { gq[i] += diff * b[knew][i];
        gqsq += gq[i] * gq[i];
        xp[knew][i] = xnew[i];
      }

      /* If a trust region step makes a small change to the objective
       * function, then calculate the gradient of the least Frobenius norm
       * interpolant at XBASE, and store it in W, using VLAG for a vector
       * of right hand sides. */
      if(ksave==-1&&delta==rho&&fabs(ratio)>.01) itest = 0 ;
      else if(ksave==-1&&delta==rho) 
      { for(k=0;k<npt;k++) vlag[k] = fval[k] - fval[kopt] ; 
        for(gisq=i=0;i<n;i++)
        { for(w[i]=k=0;k<npt;k++) w[i] += b[k][i] * vlag[k] ;
          gisq += w[i]*w[i] ; 
        }
        itest += 1 ; 
        if(gqsq<gisq*100) itest = 0 ;
        if(itest>=3)
        { for(i=0;i<n;i++) gq[i] = w[i] ; 
          for(ih=0;ih<nh;ih++) hq[ih] = 0 ; 
          for(j=0;j<nptm;j++) 
          { for(w[j]=k=0;k<npt;k++) w[j] += vlag[k] * z[k][j] ;
            if(j<idz) w[j] = -w[j] ; 
          }
          for(k=0;k<npt;k++) 
            for(pq[k]=j=0;j<nptm;j++) pq[k] += z[k][j] * w[j];
          itest = 0 ; 
        }
      }

      if(f<fsave) kopt = knew ;
      knew = -1 ; 
      /* If a trust region step has provided a sufficient decrease in F,
       * then branch for another trust region calculation. The case
       * KSAVE>0 occurs when the new function value was calculated by a
       * model step. */
      if(f<=fsave+vquad/10||ksave>=0) continue ;
      /* Alternatively, find out ifthe interpolation points are close
       * enough to the best point so far. */
    }
    distsq = setknewdistsq(n,npt,delta,&knew,xopt,xp) ;
      /* If KNEW is positive, then set DSTEP, and branch back for the next
       * iteration, which will generate a "model step". */
    if(knew>=0) 
    { dstep = fmax(rho,fmin(sqrt(distsq)/10,delta/2)) ; dsq = dstep*dstep ; }
    else if(ratio<=0&&delta<=rho&&dnorm<=rho) 
    /* The calculations with the current value of RHO are complete. Pick
     * the next values of RHO and DELTA. */
    { if(rho<=rhoend) { cond = 0 ; break ; }
      ratio = setrhodelta(&rho,rhoend,&delta) ; 
      nfsav = nf ; 
    }
  }
  /* Return from the calculation, after another Newton-Raphson step,
   * if it is too short to have been tried before. */
  if(fopt<=f) { for(i=0;i<n;i++) x[i] = xbase[i] + xopt[i] ; f = fopt ; }
 
  free(xbase,xopt,xnew,w,fval,gq) ; 
  free(hq,pq,d,vlag) ; 
  freematrix(z,b,xp) ; 
  return f ;
}
/* -------------------------------------------------------------------------- */

static double(*f)(double*) ;
static double myfunc(double *x) { return -f(x) ; }

double uoamax(double (*func)(double*),double *x,int n,
                 double xinc,double tol,int max_iter)
{ f = func ; return -uoamin(myfunc,x,n,xinc,tol,max_iter) ; }

Archived from optim.html


/*
  This is NEWUOA for unconstrained minimization. The codes were written
  by Powell in Fortran and then translated to C with f2c. I further
  modified the code to make it independent of libf2c and f2c.h. Please
  refer to "The NEWUOA software for unconstrained optimization without
  derivatives", which is available at www.damtp.cam.ac.uk, for more
  information.
 */
/*
  The original fortran codes are distributed without restrictions. The
  C++ codes are distributed under MIT license.
 */
/* The MIT License

   Copyright (c) 2004, by M.J.D. Powell <mjdp@cam.ac.uk>
                 2008, by Attractive Chaos <attractivechaos@aol.co.uk>

   Permission is hereby granted, free of charge, to any person obtaining
   a copy of this software and associated documentation files (the
   "Software"), to deal in the Software without restriction, including
   without limitation the rights to use, copy, modify, merge, publish,
   distribute, sublicense, and/or sell copies of the Software, and to
   permit persons to whom the Software is furnished to do so, subject to
   the following conditions:

   The above copyright notice and this permission notice shall be
   included in all copies or substantial portions of the Software.

   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
   MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
   NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
   BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
   ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
   CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
   SOFTWARE.
*/

#ifndef AC_NEWUOA_HH_
#define AC_NEWUOA_HH_
#include <math.h>
#include <stdlib.h>
#include <stdio.h>

template<class TYPE, class Func>
TYPE min_newuoa(int n, TYPE *x, Func &func, TYPE r_start=1e7, TYPE tol=1e-8, int max_iter=5000);

template<class TYPE, class Func>
static int biglag_(int n, int npt, TYPE *xopt, TYPE *xpt, TYPE *bmat, TYPE *zmat, int *idz,
                   int *ndim, int *knew, TYPE *delta, TYPE *d__, TYPE *alpha, TYPE *hcol, TYPE *gc,
                   TYPE *gd, TYPE *s, TYPE *w, Func &func)
{
    /* N is the number of variables. NPT is the number of interpolation
     * equations. XOPT is the best interpolation point so far. XPT
     * contains the coordinates of the current interpolation
     * points. BMAT provides the last N columns of H.  ZMAT and IDZ give
     * a factorization of the first NPT by NPT submatrix of H. NDIM is
     * the first dimension of BMAT and has the value NPT+N.  KNEW is the
     * index of the interpolation point that is going to be moved. DEBLLTA
     * is the current trust region bound. D will be set to the step from
     * XOPT to the new point. ABLLPHA will be set to the KNEW-th diagonal
     * element of the H matrix. HCOBLL, GC, GD, S and W will be used for
     * working space. */
    /* The step D is calculated in a way that attempts to maximize the
     * modulus of BLLFUNC(XOPT+D), subject to the bound ||D|| .BLLE. DEBLLTA,
     * where BLLFUNC is the KNEW-th BLLagrange function. */

    int xpt_dim1, xpt_offset, bmat_dim1, bmat_offset, zmat_dim1, zmat_offset,
        i__1, i__2, i__, j, k, iu, nptm, iterc, isave;
    TYPE sp, ss, cf1, cf2, cf3, cf4, cf5, dhd, cth, tau, sth, sum, temp, step,
        angle, scale, denom, delsq, tempa, tempb, twopi, taubeg, tauold, taumax,
        d__1, dd, gg;

    /* Parameter adjustments */
    tempa = tempb = 0.0;
    zmat_dim1 = npt;
    zmat_offset = 1 + zmat_dim1;
    zmat -= zmat_offset;
    xpt_dim1 = npt;
    xpt_offset = 1 + xpt_dim1;
    xpt -= xpt_offset;
    --xopt;
    bmat_dim1 = *ndim;
    bmat_offset = 1 + bmat_dim1;
    bmat -= bmat_offset;
    --d__; --hcol; --gc; --gd; --s; --w;
    /* Function Body */
    twopi = 2.0 * M_PI;
    delsq = *delta * *delta;
    nptm = npt - n - 1;
    /* Set the first NPT components of HCOBLL to the leading elements of
     * the KNEW-th column of H. */
    iterc = 0;
    i__1 = npt;
    for (k = 1; k <= i__1; ++k) hcol[k] = 0;
    i__1 = nptm;
    for (j = 1; j <= i__1; ++j) {
        temp = zmat[*knew + j * zmat_dim1];
        if (j < *idz) temp = -temp;
        i__2 = npt;
        for (k = 1; k <= i__2; ++k)
            hcol[k] += temp * zmat[k + j * zmat_dim1];
    }
    *alpha = hcol[*knew];
    /* Set the unscaled initial direction D. Form the gradient of BLLFUNC
     * atXOPT, and multiply D by the second derivative matrix of
     * BLLFUNC. */
    dd = 0;
    i__2 = n;
    for (i__ = 1; i__ <= i__2; ++i__) {
        d__[i__] = xpt[*knew + i__ * xpt_dim1] - xopt[i__];
        gc[i__] = bmat[*knew + i__ * bmat_dim1];
        gd[i__] = 0;
        /* Computing 2nd power */
        d__1 = d__[i__];
        dd += d__1 * d__1;
    }
    i__2 = npt;
    for (k = 1; k <= i__2; ++k) {
        temp = 0;
        sum = 0;
        i__1 = n;
        for (j = 1; j <= i__1; ++j) {
            temp += xpt[k + j * xpt_dim1] * xopt[j];
            sum += xpt[k + j * xpt_dim1] * d__[j];
        }
        temp = hcol[k] * temp;
        sum = hcol[k] * sum;
        i__1 = n;
        for (i__ = 1; i__ <= i__1; ++i__) {
            gc[i__] += temp * xpt[k + i__ * xpt_dim1];
            gd[i__] += sum * xpt[k + i__ * xpt_dim1];
        }
    }
    /* Scale D and GD, with a sign change if required. Set S to another
     * vector in the initial two dimensional subspace. */
    gg = sp = dhd = 0;
    i__1 = n;
    for (i__ = 1; i__ <= i__1; ++i__) {
        /* Computing 2nd power */
        d__1 = gc[i__];
        gg += d__1 * d__1;
        sp += d__[i__] * gc[i__];
        dhd += d__[i__] * gd[i__];
    }
    scale = *delta / sqrt(dd);
    if (sp * dhd < 0) scale = -scale;
    temp = 0;
    if (sp * sp > dd * .99 * gg) temp = 1.0;
    tau = scale * (fabs(sp) + 0.5 * scale * fabs(dhd));
    if (gg * delsq < tau * .01 * tau) temp = 1.0;
    i__1 = n;
    for (i__ = 1; i__ <= i__1; ++i__) {
        d__[i__] = scale * d__[i__];
        gd[i__] = scale * gd[i__];
        s[i__] = gc[i__] + temp * gd[i__];
    }
    /* Begin the iteration by overwriting S with a vector that has the
     * required length and direction, except that termination occurs if
     * the given D and S are nearly parallel. */
    for (iterc = 0; iterc != n; ++iterc) {
        dd = sp = ss = 0;
        i__1 = n;
        for (i__ = 1; i__ <= i__1; ++i__) {
            /* Computing 2nd power */
            d__1 = d__[i__];
            dd += d__1 * d__1;
            sp += d__[i__] * s[i__];
            /* Computing 2nd power */
            d__1 = s[i__];
            ss += d__1 * d__1;
        }
        temp = dd * ss - sp * sp;
        if (temp <= dd * 1e-8 * ss) return 0;
        denom = sqrt(temp);
        i__1 = n;
        for (i__ = 1; i__ <= i__1; ++i__) {
            s[i__] = (dd * s[i__] - sp * d__[i__]) / denom;
            w[i__] = 0;
        }
        /* Calculate the coefficients of the objective function on the
         * circle, beginning with the multiplication of S by the second
         * derivative matrix. */
        i__1 = npt;
        for (k = 1; k <= i__1; ++k) {
            sum = 0;
            i__2 = n;
            for (j = 1; j <= i__2; ++j)
                sum += xpt[k + j * xpt_dim1] * s[j];
            sum = hcol[k] * sum;
            i__2 = n;
            for (i__ = 1; i__ <= i__2; ++i__)
                w[i__] += sum * xpt[k + i__ * xpt_dim1];
        }
        cf1 = cf2 = cf3 = cf4 = cf5 = 0;
        i__2 = n;
        for (i__ = 1; i__ <= i__2; ++i__) {
            cf1 += s[i__] * w[i__];
            cf2 += d__[i__] * gc[i__];
            cf3 += s[i__] * gc[i__];
            cf4 += d__[i__] * gd[i__];
            cf5 += s[i__] * gd[i__];
        }
        cf1 = 0.5 * cf1;
        cf4 = 0.5 * cf4 - cf1;
        /* Seek the value of the angle that maximizes the modulus of TAU. */
        taubeg = cf1 + cf2 + cf4;
        taumax = tauold = taubeg;
        isave = 0;
        iu = 49;
        temp = twopi / (TYPE) (iu + 1);
        i__2 = iu;
        for (i__ = 1; i__ <= i__2; ++i__) {
            angle = (TYPE) i__ *temp;
            cth = cos(angle);
            sth = sin(angle);
            tau = cf1 + (cf2 + cf4 * cth) * cth + (cf3 + cf5 * cth) * sth;
            if (fabs(tau) > fabs(taumax)) {
                taumax = tau;
                isave = i__;
                tempa = tauold;
            } else if (i__ == isave + 1) tempb = tau;
            tauold = tau;
        }
        if (isave == 0) tempa = tau;
        if (isave == iu) tempb = taubeg;
        step = 0;
        if (tempa != tempb) {
            tempa -= taumax;
            tempb -= taumax;
            step = 0.5 * (tempa - tempb) / (tempa + tempb);
        }
        angle = temp * ((TYPE) isave + step);
        /* Calculate the new D and GD. Then test for convergence. */
        cth = cos(angle);
        sth = sin(angle);
        tau = cf1 + (cf2 + cf4 * cth) * cth + (cf3 + cf5 * cth) * sth;
        i__2 = n;
        for (i__ = 1; i__ <= i__2; ++i__) {
            d__[i__] = cth * d__[i__] + sth * s[i__];
            gd[i__] = cth * gd[i__] + sth * w[i__];
            s[i__] = gc[i__] + gd[i__];
        }
        if (fabs(tau) <= fabs(taubeg) * 1.1) return 0;
    }
    return 0;
}

template<class TYPE>
static int bigden_(int n, int npt, TYPE *xopt, TYPE *xpt, TYPE *bmat, TYPE *zmat, int *idz,
                   int *ndim, int *kopt, int *knew, TYPE *d__, TYPE *w, TYPE *vlag, TYPE *beta,
                   TYPE *s, TYPE *wvec, TYPE *prod)
{
    /* N is the number of variables.
     * NPT is the number of interpolation equations.
     * XOPT is the best interpolation point so far.
     * XPT contains the coordinates of the current interpolation points.
     * BMAT provides the last N columns of H.
     * ZMAT and IDZ give a factorization of the first NPT by NPT
       submatrix of H.
     * NDIM is the first dimension of BMAT and has the value NPT+N.
     * KOPT is the index of the optimal interpolation point.
     * KNEW is the index of the interpolation point that is going to be
       moved.
     * D will be set to the step from XOPT to the new point, and on
       entry it should be the D that was calculated by the last call of
       BIGBDLAG. The length of the initial D provides a trust region bound
       on the final D.
     * W will be set to Wcheck for the final choice of D.
     * VBDLAG will be set to Theta*Wcheck+e_b for the final choice of D.
     * BETA will be set to the value that will occur in the updating
       formula when the KNEW-th interpolation point is moved to its new
       position.
     * S, WVEC, PROD and the private arrays DEN, DENEX and PAR will be
       used for working space.
     * D is calculated in a way that should provide a denominator with a
       large modulus in the updating formula when the KNEW-th
       interpolation point is shifted to the new position XOPT+D. */

    int xpt_dim1, xpt_offset, bmat_dim1, bmat_offset, zmat_dim1, zmat_offset,
        wvec_dim1, wvec_offset, prod_dim1, prod_offset, i__1, i__2, i__, j, k,
        isave, iterc, jc, ip, iu, nw, ksav, nptm;
    TYPE dd, d__1, ds, ss, den[9], par[9], tau, sum, diff, temp, step,
        alpha, angle, denex[9], tempa, tempb, tempc, ssden, dtest, xoptd,
        twopi, xopts, denold, denmax, densav, dstemp, sumold, sstemp, xoptsq;

    /* Parameter adjustments */
    zmat_dim1 = npt;
    zmat_offset = 1 + zmat_dim1;
    zmat -= zmat_offset;
    xpt_dim1 = npt;
    xpt_offset = 1 + xpt_dim1;
    xpt -= xpt_offset;
    --xopt;
    prod_dim1 = *ndim;
    prod_offset = 1 + prod_dim1;
    prod -= prod_offset;
    wvec_dim1 = *ndim;
    wvec_offset = 1 + wvec_dim1;
    wvec -= wvec_offset;
    bmat_dim1 = *ndim;
    bmat_offset = 1 + bmat_dim1;
    bmat -= bmat_offset;
    --d__; --w; --vlag; --s;
    /* Function Body */
    twopi = atan(1.0) * 8.;
    nptm = npt - n - 1;
    /* Store the first NPT elements of the KNEW-th column of H in W(N+1)
     * to W(N+NPT). */
    i__1 = npt;
    for (k = 1; k <= i__1; ++k) w[n + k] = 0;
    i__1 = nptm;
    for (j = 1; j <= i__1; ++j) {
        temp = zmat[*knew + j * zmat_dim1];
        if (j < *idz) temp = -temp;
        i__2 = npt;
        for (k = 1; k <= i__2; ++k)
            w[n + k] += temp * zmat[k + j * zmat_dim1];
    }
    alpha = w[n + *knew];
    /* The initial search direction D is taken from the last call of
     * BIGBDLAG, and the initial S is set below, usually to the direction
     * from X_OPT to X_KNEW, but a different direction to an
     * interpolation point may be chosen, in order to prevent S from
     * being nearly parallel to D. */
    dd = ds = ss = xoptsq = 0;
    i__2 = n;
    for (i__ = 1; i__ <= i__2; ++i__) {
        /* Computing 2nd power */
        d__1 = d__[i__];
        dd += d__1 * d__1;
        s[i__] = xpt[*knew + i__ * xpt_dim1] - xopt[i__];
        ds += d__[i__] * s[i__];
        /* Computing 2nd power */
        d__1 = s[i__];
        ss += d__1 * d__1;
        /* Computing 2nd power */
        d__1 = xopt[i__];
        xoptsq += d__1 * d__1;
    }
    if (ds * ds > dd * .99 * ss) {
        ksav = *knew;
        dtest = ds * ds / ss;
        i__2 = npt;
        for (k = 1; k <= i__2; ++k) {
            if (k != *kopt) {
                dstemp = 0;
                sstemp = 0;
                i__1 = n;
                for (i__ = 1; i__ <= i__1; ++i__) {
                    diff = xpt[k + i__ * xpt_dim1] - xopt[i__];
                    dstemp += d__[i__] * diff;
                    sstemp += diff * diff;
                }
                if (dstemp * dstemp / sstemp < dtest) {
                    ksav = k;
                    dtest = dstemp * dstemp / sstemp;
                    ds = dstemp;
                    ss = sstemp;
                }
            }
        }
        i__2 = n;
        for (i__ = 1; i__ <= i__2; ++i__)
            s[i__] = xpt[ksav + i__ * xpt_dim1] - xopt[i__];
    }
    ssden = dd * ss - ds * ds;
    iterc = 0;
    densav = 0;
    /* Begin the iteration by overwriting S with a vector that has the
     * required length and direction. */
BDL70:
    ++iterc;
    temp = 1.0 / sqrt(ssden);
    xoptd = xopts = 0;
    i__2 = n;
    for (i__ = 1; i__ <= i__2; ++i__) {
        s[i__] = temp * (dd * s[i__] - ds * d__[i__]);
        xoptd += xopt[i__] * d__[i__];
        xopts += xopt[i__] * s[i__];
    }
    /* Set the coefficients of the first 2.0 terms of BETA. */
    tempa = 0.5 * xoptd * xoptd;
    tempb = 0.5 * xopts * xopts;
    den[0] = dd * (xoptsq + 0.5 * dd) + tempa + tempb;
    den[1] = 2.0 * xoptd * dd;
    den[2] = 2.0 * xopts * dd;
    den[3] = tempa - tempb;
    den[4] = xoptd * xopts;
    for (i__ = 6; i__ <= 9; ++i__) den[i__ - 1] = 0;
    /* Put the coefficients of Wcheck in WVEC. */
    i__2 = npt;
    for (k = 1; k <= i__2; ++k) {
        tempa = tempb = tempc = 0;
        i__1 = n;
        for (i__ = 1; i__ <= i__1; ++i__) {
            tempa += xpt[k + i__ * xpt_dim1] * d__[i__];
            tempb += xpt[k + i__ * xpt_dim1] * s[i__];
            tempc += xpt[k + i__ * xpt_dim1] * xopt[i__];
        }
        wvec[k + wvec_dim1] = 0.25 * (tempa * tempa + tempb * tempb);
        wvec[k + (wvec_dim1 << 1)] = tempa * tempc;
        wvec[k + wvec_dim1 * 3] = tempb * tempc;
        wvec[k + (wvec_dim1 << 2)] = 0.25 * (tempa * tempa - tempb * tempb);
        wvec[k + wvec_dim1 * 5] = 0.5 * tempa * tempb;
    }
    i__2 = n;
    for (i__ = 1; i__ <= i__2; ++i__) {
        ip = i__ + npt;
        wvec[ip + wvec_dim1] = 0;
        wvec[ip + (wvec_dim1 << 1)] = d__[i__];
        wvec[ip + wvec_dim1 * 3] = s[i__];
        wvec[ip + (wvec_dim1 << 2)] = 0;
        wvec[ip + wvec_dim1 * 5] = 0;
    }
    /* Put the coefficents of THETA*Wcheck in PROD. */
    for (jc = 1; jc <= 5; ++jc) {
        nw = npt;
        if (jc == 2 || jc == 3) nw = *ndim;
        i__2 = npt;
        for (k = 1; k <= i__2; ++k) prod[k + jc * prod_dim1] = 0;
        i__2 = nptm;
        for (j = 1; j <= i__2; ++j) {
            sum = 0;
            i__1 = npt;
            for (k = 1; k <= i__1; ++k) sum += zmat[k + j * zmat_dim1] * wvec[k + jc * wvec_dim1];
            if (j < *idz) sum = -sum;
            i__1 = npt;
            for (k = 1; k <= i__1; ++k)
                prod[k + jc * prod_dim1] += sum * zmat[k + j * zmat_dim1];
        }
        if (nw == *ndim) {
            i__1 = npt;
            for (k = 1; k <= i__1; ++k) {
                sum = 0;
                i__2 = n;
                for (j = 1; j <= i__2; ++j)
                    sum += bmat[k + j * bmat_dim1] * wvec[npt + j + jc * wvec_dim1];
                prod[k + jc * prod_dim1] += sum;
            }
        }
        i__1 = n;
        for (j = 1; j <= i__1; ++j) {
            sum = 0;
            i__2 = nw;
            for (i__ = 1; i__ <= i__2; ++i__)
                sum += bmat[i__ + j * bmat_dim1] * wvec[i__ + jc * wvec_dim1];
            prod[npt + j + jc * prod_dim1] = sum;
        }
    }
    /* Include in DEN the part of BETA that depends on THETA. */
    i__1 = *ndim;
    for (k = 1; k <= i__1; ++k) {
        sum = 0;
        for (i__ = 1; i__ <= 5; ++i__) {
            par[i__ - 1] = 0.5 * prod[k + i__ * prod_dim1] * wvec[k + i__ * wvec_dim1];
            sum += par[i__ - 1];
        }
        den[0] = den[0] - par[0] - sum;
        tempa = prod[k + prod_dim1] * wvec[k + (wvec_dim1 << 1)] + prod[k + (
                     prod_dim1 << 1)] * wvec[k + wvec_dim1];
        tempb = prod[k + (prod_dim1 << 1)] * wvec[k + (wvec_dim1 << 2)] +
            prod[k + (prod_dim1 << 2)] * wvec[k + (wvec_dim1 << 1)];
        tempc = prod[k + prod_dim1 * 3] * wvec[k + wvec_dim1 * 5] + prod[k +
                   prod_dim1 * 5] * wvec[k + wvec_dim1 * 3];
        den[1] = den[1] - tempa - 0.5 * (tempb + tempc);
        den[5] -= 0.5 * (tempb - tempc);
        tempa = prod[k + prod_dim1] * wvec[k + wvec_dim1 * 3] + prod[k +
                       prod_dim1 * 3] * wvec[k + wvec_dim1];
        tempb = prod[k + (prod_dim1 << 1)] * wvec[k + wvec_dim1 * 5] + prod[k
                  + prod_dim1 * 5] * wvec[k + (wvec_dim1 << 1)];
        tempc = prod[k + prod_dim1 * 3] * wvec[k + (wvec_dim1 << 2)] + prod[k
                  + (prod_dim1 << 2)] * wvec[k + wvec_dim1 * 3];
        den[2] = den[2] - tempa - 0.5 * (tempb - tempc);
        den[6] -= 0.5 * (tempb + tempc);
        tempa = prod[k + prod_dim1] * wvec[k + (wvec_dim1 << 2)] + prod[k + (
                     prod_dim1 << 2)] * wvec[k + wvec_dim1];
        den[3] = den[3] - tempa - par[1] + par[2];
        tempa = prod[k + prod_dim1] * wvec[k + wvec_dim1 * 5] + prod[k +
                       prod_dim1 * 5] * wvec[k + wvec_dim1];
        tempb = prod[k + (prod_dim1 << 1)] * wvec[k + wvec_dim1 * 3] + prod[k
                  + prod_dim1 * 3] * wvec[k + (wvec_dim1 << 1)];
        den[4] = den[4] - tempa - 0.5 * tempb;
        den[7] = den[7] - par[3] + par[4];
        tempa = prod[k + (prod_dim1 << 2)] * wvec[k + wvec_dim1 * 5] + prod[k
                  + prod_dim1 * 5] * wvec[k + (wvec_dim1 << 2)];
        den[8] -= 0.5 * tempa;
    }
    /* Extend DEN so that it holds all the coefficients of DENOM. */
    sum = 0;
    for (i__ = 1; i__ <= 5; ++i__) {
        /* Computing 2nd power */
        d__1 = prod[*knew + i__ * prod_dim1];
        par[i__ - 1] = 0.5 * (d__1 * d__1);
        sum += par[i__ - 1];
    }
    denex[0] = alpha * den[0] + par[0] + sum;
    tempa = 2.0 * prod[*knew + prod_dim1] * prod[*knew + (prod_dim1 << 1)];
    tempb = prod[*knew + (prod_dim1 << 1)] * prod[*knew + (prod_dim1 << 2)];
    tempc = prod[*knew + prod_dim1 * 3] * prod[*knew + prod_dim1 * 5];
    denex[1] = alpha * den[1] + tempa + tempb + tempc;
    denex[5] = alpha * den[5] + tempb - tempc;
    tempa = 2.0 * prod[*knew + prod_dim1] * prod[*knew + prod_dim1 * 3];
    tempb = prod[*knew + (prod_dim1 << 1)] * prod[*knew + prod_dim1 * 5];
    tempc = prod[*knew + prod_dim1 * 3] * prod[*knew + (prod_dim1 << 2)];
    denex[2] = alpha * den[2] + tempa + tempb - tempc;
    denex[6] = alpha * den[6] + tempb + tempc;
    tempa = 2.0 * prod[*knew + prod_dim1] * prod[*knew + (prod_dim1 << 2)];
    denex[3] = alpha * den[3] + tempa + par[1] - par[2];
    tempa = 2.0 * prod[*knew + prod_dim1] * prod[*knew + prod_dim1 * 5];
    denex[4] = alpha * den[4] + tempa + prod[*knew + (prod_dim1 << 1)] * prod[
                             *knew + prod_dim1 * 3];
    denex[7] = alpha * den[7] + par[3] - par[4];
    denex[8] = alpha * den[8] + prod[*knew + (prod_dim1 << 2)] * prod[*knew +
                                 prod_dim1 * 5];
    /* Seek the value of the angle that maximizes the modulus of DENOM. */
    sum = denex[0] + denex[1] + denex[3] + denex[5] + denex[7];
    denold = denmax = sum;
    isave = 0;
    iu = 49;
    temp = twopi / (TYPE) (iu + 1);
    par[0] = 1.0;
    i__1 = iu;
    for (i__ = 1; i__ <= i__1; ++i__) {
        angle = (TYPE) i__ *temp;
        par[1] = cos(angle);
        par[2] = sin(angle);
        for (j = 4; j <= 8; j += 2) {
            par[j - 1] = par[1] * par[j - 3] - par[2] * par[j - 2];
            par[j] = par[1] * par[j - 2] + par[2] * par[j - 3];
        }
        sumold = sum;
        sum = 0;
        for (j = 1; j <= 9; ++j)
            sum += denex[j - 1] * par[j - 1];
        if (fabs(sum) > fabs(denmax)) {
            denmax = sum;
            isave = i__;
            tempa = sumold;
        } else if (i__ == isave + 1) {
            tempb = sum;
        }
    }
    if (isave == 0) tempa = sum;
    if (isave == iu) tempb = denold;
    step = 0;
    if (tempa != tempb) {
        tempa -= denmax;
        tempb -= denmax;
        step = 0.5 * (tempa - tempb) / (tempa + tempb);
    }
    angle = temp * ((TYPE) isave + step);
    /* Calculate the new parameters of the denominator, the new VBDLAG
     * vector and the new D. Then test for convergence. */
    par[1] = cos(angle);
    par[2] = sin(angle);
    for (j = 4; j <= 8; j += 2) {
        par[j - 1] = par[1] * par[j - 3] - par[2] * par[j - 2];
        par[j] = par[1] * par[j - 2] + par[2] * par[j - 3];
    }
    *beta = 0;
    denmax = 0;
    for (j = 1; j <= 9; ++j) {
        *beta += den[j - 1] * par[j - 1];
        denmax += denex[j - 1] * par[j - 1];
    }
    i__1 = *ndim;
    for (k = 1; k <= i__1; ++k) {
        vlag[k] = 0;
        for (j = 1; j <= 5; ++j)
            vlag[k] += prod[k + j * prod_dim1] * par[j - 1];
    }
    tau = vlag[*knew];
    dd = tempa = tempb = 0;
    i__1 = n;
    for (i__ = 1; i__ <= i__1; ++i__) {
        d__[i__] = par[1] * d__[i__] + par[2] * s[i__];
        w[i__] = xopt[i__] + d__[i__];
        /* Computing 2nd power */
        d__1 = d__[i__];
        dd += d__1 * d__1;
        tempa += d__[i__] * w[i__];
        tempb += w[i__] * w[i__];
    }
    if (iterc >= n) goto BDL340;
    if (iterc > 1) densav = fmax(densav, denold);
    if (fabs(denmax) <= fabs(densav) * 1.1) goto BDL340;
    densav = denmax;
    /* Set S to 0.5 the gradient of the denominator with respect to
     * D. Then branch for the next iteration. */
    i__1 = n;
    for (i__ = 1; i__ <= i__1; ++i__) {
        temp = tempa * xopt[i__] + tempb * d__[i__] - vlag[npt + i__];
        s[i__] = tau * bmat[*knew + i__ * bmat_dim1] + alpha * temp;
    }
    i__1 = npt;
    for (k = 1; k <= i__1; ++k) {
        sum = 0;
        i__2 = n;
        for (j = 1; j <= i__2; ++j)
            sum += xpt[k + j * xpt_dim1] * w[j];
        temp = (tau * w[n + k] - alpha * vlag[k]) * sum;
        i__2 = n;
        for (i__ = 1; i__ <= i__2; ++i__)
            s[i__] += temp * xpt[k + i__ * xpt_dim1];
    }
    ss = 0;
    ds = 0;
    i__2 = n;
    for (i__ = 1; i__ <= i__2; ++i__) {
        /* Computing 2nd power */
        d__1 = s[i__];
        ss += d__1 * d__1;
        ds += d__[i__] * s[i__];
    }
    ssden = dd * ss - ds * ds;
    if (ssden >= dd * 1e-8 * ss) goto BDL70;
    /* Set the vector W before the RETURN from the subroutine. */
BDL340:
    i__2 = *ndim;
    for (k = 1; k <= i__2; ++k) {
        w[k] = 0;
        for (j = 1; j <= 5; ++j) w[k] += wvec[k + j * wvec_dim1] * par[j - 1];
    }
    vlag[*kopt] += 1.0;
    return 0;
}

template<class TYPE>
int trsapp_(int n, int npt, TYPE * xopt, TYPE * xpt, TYPE * gq, TYPE * hq, TYPE * pq,
            TYPE * delta, TYPE * step, TYPE * d__, TYPE * g, TYPE * hd, TYPE * hs, TYPE * crvmin)
{
    /* The arguments NPT, XOPT, XPT, GQ, HQ and PQ have their usual
     * meanings, in order to define the current quadratic model Q.
     * DETRLTA is the trust region radius, and has to be positive. STEP
     * will be set to the calculated trial step. The arrays D, G, HD and
     * HS will be used for working space. CRVMIN will be set to the
     * least curvature of H aint the conjugate directions that occur,
     * except that it is set to 0 if STEP goes all the way to the trust
     * region boundary. The calculation of STEP begins with the
     * truncated conjugate gradient method. If the boundary of the trust
     * region is reached, then further changes to STEP may be made, each
     * one being in the 2D space spanned by the current STEP and the
     * corresponding gradient of Q. Thus STEP should provide a
     * substantial reduction to Q within the trust region. */

    int xpt_dim1, xpt_offset, i__1, i__2, i__, j, k, ih, iu, iterc,
        isave, itersw, itermax;
    TYPE d__1, d__2, dd, cf, dg, gg, ds, sg, ss, dhd, dhs,
        cth, sgk, shs, sth, qadd, qbeg, qred, qmin, temp,
        qsav, qnew, ggbeg, alpha, angle, reduc, ggsav, delsq,
        tempa, tempb, bstep, ratio, twopi, angtest;

    /* Parameter adjustments */
    tempa = tempb = shs = sg = bstep = ggbeg = gg = qred = dd = 0.0;
    xpt_dim1 = npt;
    xpt_offset = 1 + xpt_dim1;
    xpt -= xpt_offset;
    --xopt; --gq; --hq; --pq; --step; --d__; --g; --hd; --hs;
    /* Function Body */
    twopi = 2.0 * M_PI;
    delsq = *delta * *delta;
    iterc = 0;
    itermax = n;
    itersw = itermax;
    i__1 = n;
    for (i__ = 1; i__ <= i__1; ++i__) d__[i__] = xopt[i__];
    goto TRL170;
    /* Prepare for the first line search. */
TRL20:
    qred = dd = 0;
    i__1 = n;
    for (i__ = 1; i__ <= i__1; ++i__) {
        step[i__] = 0;
        hs[i__] = 0;
        g[i__] = gq[i__] + hd[i__];
        d__[i__] = -g[i__];
        /* Computing 2nd power */
        d__1 = d__[i__];
        dd += d__1 * d__1;
    }
    *crvmin = 0;
    if (dd == 0) goto TRL160;
    ds = ss = 0;
    gg = dd;
    ggbeg = gg;
    /* Calculate the step to the trust region boundary and the product
     * HD. */
TRL40:
    ++iterc;
    temp = delsq - ss;
    bstep = temp / (ds + sqrt(ds * ds + dd * temp));
    goto TRL170;
TRL50:
    dhd = 0;
    i__1 = n;
    for (j = 1; j <= i__1; ++j) dhd += d__[j] * hd[j];
    /* Update CRVMIN and set the step-length ATRLPHA. */
    alpha = bstep;
    if (dhd > 0) {
        temp = dhd / dd;
        if (iterc == 1) *crvmin = temp;
        *crvmin = fmin(*crvmin, temp);
        /* Computing MIN */
        d__1 = alpha, d__2 = gg / dhd;
        alpha = fmin(d__1, d__2);
    }
    qadd = alpha * (gg - 0.5 * alpha * dhd);
    qred += qadd;
    /* Update STEP and HS. */
    ggsav = gg;
    gg = 0;
    i__1 = n;
    for (i__ = 1; i__ <= i__1; ++i__) {
        step[i__] += alpha * d__[i__];
        hs[i__] += alpha * hd[i__];
        /* Computing 2nd power */
        d__1 = g[i__] + hs[i__];
        gg += d__1 * d__1;
    }
    /* Begin another conjugate direction iteration if required. */
    if (alpha < bstep) {
        if (qadd <= qred * .01 || gg <= ggbeg * 1e-4 || iterc == itermax) goto TRL160;
        temp = gg / ggsav;
        dd = ds = ss = 0;
        i__1 = n;
        for (i__ = 1; i__ <= i__1; ++i__) {
            d__[i__] = temp * d__[i__] - g[i__] - hs[i__];
            /* Computing 2nd power */
            d__1 = d__[i__];
            dd += d__1 * d__1;
            ds += d__[i__] * step[i__];
            /* Computing 2nd power */
            d__1 = step[i__];
            ss += d__1 * d__1;
        }
        if (ds <= 0) goto TRL160;
        if (ss < delsq) goto TRL40;
    }
    *crvmin = 0;
    itersw = iterc;
    /* Test whether an alternative iteration is required. */
TRL90:
    if (gg <= ggbeg * 1e-4) goto TRL160;
    sg = 0;
    shs = 0;
    i__1 = n;
    for (i__ = 1; i__ <= i__1; ++i__) {
        sg += step[i__] * g[i__];
        shs += step[i__] * hs[i__];
    }
    sgk = sg + shs;
    angtest = sgk / sqrt(gg * delsq);
    if (angtest <= -.99) goto TRL160;
    /* Begin the alternative iteration by calculating D and HD and some
     * scalar products. */
    ++iterc;
    temp = sqrt(delsq * gg - sgk * sgk);
    tempa = delsq / temp;
    tempb = sgk / temp;
    i__1 = n;
    for (i__ = 1; i__ <= i__1; ++i__)
        d__[i__] = tempa * (g[i__] + hs[i__]) - tempb * step[i__];
    goto TRL170;
TRL120:
    dg = dhd = dhs = 0;
    i__1 = n;
    for (i__ = 1; i__ <= i__1; ++i__) {
        dg += d__[i__] * g[i__];
        dhd += hd[i__] * d__[i__];
        dhs += hd[i__] * step[i__];
    }
    /* Seek the value of the angle that minimizes Q. */
    cf = 0.5 * (shs - dhd);
    qbeg = sg + cf;
    qsav = qmin = qbeg;
    isave = 0;
    iu = 49;
    temp = twopi / (TYPE) (iu + 1);
    i__1 = iu;
    for (i__ = 1; i__ <= i__1; ++i__) {
        angle = (TYPE) i__ *temp;
        cth = cos(angle);
        sth = sin(angle);
        qnew = (sg + cf * cth) * cth + (dg + dhs * cth) * sth;
        if (qnew < qmin) {
            qmin = qnew;
            isave = i__;
            tempa = qsav;
        } else if (i__ == isave + 1) tempb = qnew;
        qsav = qnew;
    }
    if ((TYPE) isave == 0) tempa = qnew;
    if (isave == iu) tempb = qbeg;
    angle = 0;
    if (tempa != tempb) {
        tempa -= qmin;
        tempb -= qmin;
        angle = 0.5 * (tempa - tempb) / (tempa + tempb);
    }
    angle = temp * ((TYPE) isave + angle);
    /* Calculate the new STEP and HS. Then test for convergence. */
    cth = cos(angle);
    sth = sin(angle);
    reduc = qbeg - (sg + cf * cth) * cth - (dg + dhs * cth) * sth;
    gg = 0;
    i__1 = n;
    for (i__ = 1; i__ <= i__1; ++i__) {
        step[i__] = cth * step[i__] + sth * d__[i__];
        hs[i__] = cth * hs[i__] + sth * hd[i__];
        /* Computing 2nd power */
        d__1 = g[i__] + hs[i__];
        gg += d__1 * d__1;
    }
    qred += reduc;
    ratio = reduc / qred;
    if (iterc < itermax && ratio > .01) goto TRL90;
TRL160:
    return 0;
    /* The following instructions act as a subroutine for setting the
     * vector HD to the vector D multiplied by the second derivative
     * matrix of Q.  They are called from three different places, which
     * are distinguished by the value of ITERC. */
TRL170:
    i__1 = n;
    for (i__ = 1; i__ <= i__1; ++i__) hd[i__] = 0;
    i__1 = npt;
    for (k = 1; k <= i__1; ++k) {
        temp = 0;
        i__2 = n;
        for (j = 1; j <= i__2; ++j)
            temp += xpt[k + j * xpt_dim1] * d__[j];
        temp *= pq[k];
        i__2 = n;
        for (i__ = 1; i__ <= i__2; ++i__)
            hd[i__] += temp * xpt[k + i__ * xpt_dim1];
    }
    ih = 0;
    i__2 = n;
    for (j = 1; j <= i__2; ++j) {
        i__1 = j;
        for (i__ = 1; i__ <= i__1; ++i__) {
            ++ih;
            if (i__ < j) hd[j] += hq[ih] * d__[i__];
            hd[i__] += hq[ih] * d__[j];
        }
    }
    if (iterc == 0) goto TRL20;
    if (iterc <= itersw) goto TRL50;
    goto TRL120;
}

template<class TYPE>
static int update_(int n, int npt, TYPE *bmat, TYPE *zmat, int *idz, int *ndim, TYPE *vlag,
                   TYPE *beta, int *knew, TYPE *w)
{
    /* The arrays BMAT and ZMAT with IDZ are updated, in order to shift
     * the interpolation point that has index KNEW. On entry, VLAG
     * contains the components of the vector Theta*Wcheck+e_b of the
     * updating formula (6.11), and BETA holds the value of the
     * parameter that has this name. The vector W is used for working
     * space. */

    int bmat_dim1, bmat_offset, zmat_dim1, zmat_offset, i__1, i__2, i__,
        j, ja, jb, jl, jp, nptm, iflag;
    TYPE d__1, d__2, tau, temp, scala, scalb, alpha, denom, tempa, tempb, tausq;

    /* Parameter adjustments */
    tempb = 0.0;
    zmat_dim1 = npt;
    zmat_offset = 1 + zmat_dim1;
    zmat -= zmat_offset;
    bmat_dim1 = *ndim;
    bmat_offset = 1 + bmat_dim1;
    bmat -= bmat_offset;
    --vlag;
    --w;
    /* Function Body */
    nptm = npt - n - 1;
    /* Apply the rotations that put zeros in the KNEW-th row of ZMAT. */
    jl = 1;
    i__1 = nptm;
    for (j = 2; j <= i__1; ++j) {
        if (j == *idz) {
            jl = *idz;
        } else if (zmat[*knew + j * zmat_dim1] != 0) {
            /* Computing 2nd power */
            d__1 = zmat[*knew + jl * zmat_dim1];
            /* Computing 2nd power */
            d__2 = zmat[*knew + j * zmat_dim1];
            temp = sqrt(d__1 * d__1 + d__2 * d__2);
            tempa = zmat[*knew + jl * zmat_dim1] / temp;
            tempb = zmat[*knew + j * zmat_dim1] / temp;
            i__2 = npt;
            for (i__ = 1; i__ <= i__2; ++i__) {
                temp = tempa * zmat[i__ + jl * zmat_dim1] + tempb * zmat[i__
                               + j * zmat_dim1];
                zmat[i__ + j * zmat_dim1] = tempa * zmat[i__ + j * zmat_dim1]
                    - tempb * zmat[i__ + jl * zmat_dim1];
                zmat[i__ + jl * zmat_dim1] = temp;
            }
            zmat[*knew + j * zmat_dim1] = 0;
        }
    }
    /* Put the first NPT components of the KNEW-th column of HLAG into
     * W, and calculate the parameters of the updating formula. */
    tempa = zmat[*knew + zmat_dim1];
    if (*idz >= 2) tempa = -tempa;
    if (jl > 1) tempb = zmat[*knew + jl * zmat_dim1];
    i__1 = npt;
    for (i__ = 1; i__ <= i__1; ++i__) {
        w[i__] = tempa * zmat[i__ + zmat_dim1];
        if (jl > 1) w[i__] += tempb * zmat[i__ + jl * zmat_dim1];
    }
    alpha = w[*knew];
    tau = vlag[*knew];
    tausq = tau * tau;
    denom = alpha * *beta + tausq;
    vlag[*knew] -= 1.0;
    /* Complete the updating of ZMAT when there is only 1.0 nonzero
     * element in the KNEW-th row of the new matrix ZMAT, but, if IFLAG
     * is set to 1.0, then the first column of ZMAT will be exchanged
     * with another 1.0 later. */
    iflag = 0;
    if (jl == 1) {
        temp = sqrt((fabs(denom)));
        tempb = tempa / temp;
        tempa = tau / temp;
        i__1 = npt;
        for (i__ = 1; i__ <= i__1; ++i__)
            zmat[i__ + zmat_dim1] = tempa * zmat[i__ + zmat_dim1] - tempb *
                vlag[i__];
        if (*idz == 1 && temp < 0) *idz = 2;
        if (*idz >= 2 && temp >= 0) iflag = 1;
    } else {
        /* Complete the updating of ZMAT in the alternative case. */
        ja = 1;
        if (*beta >= 0) {
            ja = jl;
        }
        jb = jl + 1 - ja;
        temp = zmat[*knew + jb * zmat_dim1] / denom;
        tempa = temp * *beta;
        tempb = temp * tau;
        temp = zmat[*knew + ja * zmat_dim1];
        scala = 1.0 / sqrt(fabs(*beta) * temp * temp + tausq);
        scalb = scala * sqrt((fabs(denom)));
        i__1 = npt;
        for (i__ = 1; i__ <= i__1; ++i__) {
            zmat[i__ + ja * zmat_dim1] = scala * (tau * zmat[i__ + ja *
                         zmat_dim1] - temp * vlag[i__]);
            zmat[i__ + jb * zmat_dim1] = scalb * (zmat[i__ + jb * zmat_dim1]
                      - tempa * w[i__] - tempb * vlag[i__]);
        }
        if (denom <= 0) {
            if (*beta < 0) ++(*idz);
            if (*beta >= 0) iflag = 1;
        }
    }
    /* IDZ is reduced in the following case, and usually the first
     * column of ZMAT is exchanged with a later 1.0. */
    if (iflag == 1) {
        --(*idz);
        i__1 = npt;
        for (i__ = 1; i__ <= i__1; ++i__) {
            temp = zmat[i__ + zmat_dim1];
            zmat[i__ + zmat_dim1] = zmat[i__ + *idz * zmat_dim1];
            zmat[i__ + *idz * zmat_dim1] = temp;
        }
    }
    /* Finally, update the matrix BMAT. */
    i__1 = n;
    for (j = 1; j <= i__1; ++j) {
        jp = npt + j;
        w[jp] = bmat[*knew + j * bmat_dim1];
        tempa = (alpha * vlag[jp] - tau * w[jp]) / denom;
        tempb = (-(*beta) * w[jp] - tau * vlag[jp]) / denom;
        i__2 = jp;
        for (i__ = 1; i__ <= i__2; ++i__) {
            bmat[i__ + j * bmat_dim1] = bmat[i__ + j * bmat_dim1] + tempa *
                vlag[i__] + tempb * w[i__];
            if (i__ > npt) {
                bmat[jp + (i__ - npt) * bmat_dim1] = bmat[i__ + j *
                                 bmat_dim1];
            }
        }
    }
    return 0;
}

template<class TYPE, class Func>
static TYPE newuob_(int n, int npt, TYPE *x,
                    TYPE rhobeg, TYPE rhoend, int *ret_nf, int maxfun,
                    TYPE *xbase, TYPE *xopt, TYPE *xnew,
                    TYPE *xpt, TYPE *fval, TYPE *gq, TYPE *hq,
                    TYPE *pq, TYPE *bmat, TYPE *zmat, int *ndim,
                    TYPE *d__, TYPE *vlag, TYPE *w, Func &func)
{
    /* XBASE will hold a shift of origin that should reduce the
       contributions from rounding errors to values of the model and
       Lagrange functions.
     * XOPT will be set to the displacement from XBASE of the vector of
       variables that provides the least calculated F so far.
     * XNEW will be set to the displacement from XBASE of the vector of
       variables for the current calculation of F.
     * XPT will contain the interpolation point coordinates relative to
       XBASE.
     * FVAL will hold the values of F at the interpolation points.
     * GQ will hold the gradient of the quadratic model at XBASE.
     * HQ will hold the explicit second derivatives of the quadratic
       model.
     * PQ will contain the parameters of the implicit second derivatives
       of the quadratic model.
     * BMAT will hold the last N columns of H.
     * ZMAT will hold the factorization of the leading NPT by NPT
       submatrix of H, this factorization being ZMAT times Diag(DZ)
       times ZMAT^T, where the elements of DZ are plus or minus 1.0, as
       specified by IDZ.
     * NDIM is the first dimension of BMAT and has the value NPT+N.
     * D is reserved for trial steps from XOPT.
     * VLAG will contain the values of the Lagrange functions at a new
       point X.  They are part of a product that requires VLAG to be of
       length NDIM.
     * The array W will be used for working space. Its length must be at
       least 10*NDIM = 10*(NPT+N). Set some constants. */

    int xpt_dim1, xpt_offset, bmat_dim1, bmat_offset, zmat_dim1, zmat_offset,
        i__1, i__2, i__3, i__, j, k, ih, nf, nh, ip, jp, np, nfm, idz, ipt, jpt,
        nfmm, knew, kopt, nptm, ksave, nfsav, itemp, ktemp, itest, nftest;
    TYPE d__1, d__2, d__3, f, dx, dsq, rho, sum, fbeg, diff, beta, gisq,
        temp, suma, sumb, fopt, bsum, gqsq, xipt, xjpt, sumz, diffa, diffb,
        diffc, hdiag, alpha, delta, recip, reciq, fsave, dnorm, ratio, dstep,
        vquad, tempq, rhosq, detrat, crvmin, distsq, xoptsq;

    /* Parameter adjustments */
    diffc = ratio = dnorm = nfsav = diffa = diffb = xoptsq = f = 0.0;
    rho = fbeg = fopt = xjpt = xipt = 0.0;
    itest = ipt = jpt = 0;
    alpha = dstep = 0.0;
    zmat_dim1 = npt;
    zmat_offset = 1 + zmat_dim1;
    zmat -= zmat_offset;
    xpt_dim1 = npt;
    xpt_offset = 1 + xpt_dim1;
    xpt -= xpt_offset;
    --x; --xbase; --xopt; --xnew; --fval; --gq; --hq; --pq;
    bmat_dim1 = *ndim;
    bmat_offset = 1 + bmat_dim1;
    bmat -= bmat_offset;
    --d__;
    --vlag;
    --w;
    /* Function Body */
    np = n + 1;
    nh = n * np / 2;
    nptm = npt - np;
    nftest = (maxfun > 1)? maxfun : 1;
    /* Set the initial elements of XPT, BMAT, HQ, PQ and ZMAT to 0. */
    i__1 = n;
    for (j = 1; j <= i__1; ++j) {
        xbase[j] = x[j];
        i__2 = npt;
        for (k = 1; k <= i__2; ++k)
            xpt[k + j * xpt_dim1] = 0;
        i__2 = *ndim;
        for (i__ = 1; i__ <= i__2; ++i__)
            bmat[i__ + j * bmat_dim1] = 0;
    }
    i__2 = nh;
    for (ih = 1; ih <= i__2; ++ih) hq[ih] = 0;
    i__2 = npt;
    for (k = 1; k <= i__2; ++k) {
        pq[k] = 0;
        i__1 = nptm;
        for (j = 1; j <= i__1; ++j)
            zmat[k + j * zmat_dim1] = 0;
    }
    /* Begin the initialization procedure. NF becomes 1.0 more than the
     * number of function values so far. The coordinates of the
     * displacement of the next initial interpolation point from XBASE
     * are set in XPT(NF,.). */
    rhosq = rhobeg * rhobeg;
    recip = 1.0 / rhosq;
    reciq = sqrt(.5) / rhosq;
    nf = 0;
L50:
    nfm = nf;
    nfmm = nf - n;
    ++nf;
    if (nfm <= n << 1) {
        if (nfm >= 1 && nfm <= n) {
            xpt[nf + nfm * xpt_dim1] = rhobeg;
        } else if (nfm > n) {
            xpt[nf + nfmm * xpt_dim1] = -(rhobeg);
        }
    } else {
        itemp = (nfmm - 1) / n;
        jpt = nfm - itemp * n - n;
        ipt = jpt + itemp;
        if (ipt > n) {
            itemp = jpt;
            jpt = ipt - n;
            ipt = itemp;
        }
        xipt = rhobeg;
        if (fval[ipt + np] < fval[ipt + 1]) xipt = -xipt;
        xjpt = rhobeg;
        if (fval[jpt + np] < fval[jpt + 1]) xjpt = -xjpt;
        xpt[nf + ipt * xpt_dim1] = xipt;
        xpt[nf + jpt * xpt_dim1] = xjpt;
    }
    /* Calculate the next value of F, label 70 being reached immediately
     * after this calculation. The least function value so far and its
     * index are required. */
    i__1 = n;
    for (j = 1; j <= i__1; ++j)
        x[j] = xpt[nf + j * xpt_dim1] + xbase[j];
    goto L310;
L70:
    fval[nf] = f;
    if (nf == 1) {
        fbeg = fopt = f;
        kopt = 1;
    } else if (f < fopt) {
        fopt = f;
        kopt = nf;
    }
    /* Set the non0 initial elements of BMAT and the quadratic model
     * in the cases when NF is at most 2*N+1. */
    if (nfm <= n << 1) {
        if (nfm >= 1 && nfm <= n) {
            gq[nfm] = (f - fbeg) / rhobeg;
            if (npt < nf + n) {
                bmat[nfm * bmat_dim1 + 1] = -1.0 / rhobeg;
                bmat[nf + nfm * bmat_dim1] = 1.0 / rhobeg;
                bmat[npt + nfm + nfm * bmat_dim1] = -.5 * rhosq;
            }
        } else if (nfm > n) {
            bmat[nf - n + nfmm * bmat_dim1] = .5 / rhobeg;
            bmat[nf + nfmm * bmat_dim1] = -.5 / rhobeg;
            zmat[nfmm * zmat_dim1 + 1] = -reciq - reciq;
            zmat[nf - n + nfmm * zmat_dim1] = reciq;
            zmat[nf + nfmm * zmat_dim1] = reciq;
            ih = nfmm * (nfmm + 1) / 2;
            temp = (fbeg - f) / rhobeg;
            hq[ih] = (gq[nfmm] - temp) / rhobeg;
            gq[nfmm] = .5 * (gq[nfmm] + temp);
        }
        /* Set the off-diagonal second derivatives of the Lagrange
         * functions and the initial quadratic model. */
    } else {
        ih = ipt * (ipt - 1) / 2 + jpt;
        if (xipt < 0) ipt += n;
        if (xjpt < 0) jpt += n;
        zmat[nfmm * zmat_dim1 + 1] = recip;
        zmat[nf + nfmm * zmat_dim1] = recip;
        zmat[ipt + 1 + nfmm * zmat_dim1] = -recip;
        zmat[jpt + 1 + nfmm * zmat_dim1] = -recip;
        hq[ih] = (fbeg - fval[ipt + 1] - fval[jpt + 1] + f) / (xipt * xjpt);
    }
    if (nf < npt) goto L50;
    /* Begin the iterative procedure, because the initial model is
     * complete. */
    rho = rhobeg;
    delta = rho;
    idz = 1;
    diffa = diffb = itest = xoptsq = 0;
    i__1 = n;
    for (i__ = 1; i__ <= i__1; ++i__) {
        xopt[i__] = xpt[kopt + i__ * xpt_dim1];
        /* Computing 2nd power */
        d__1 = xopt[i__];
        xoptsq += d__1 * d__1;
    }
L90:
    nfsav = nf;
    /* Generate the next trust region step and test its length. Set KNEW
     * to -1 if the purpose of the next F will be to improve the
     * model. */
L100:
    knew = 0;
    trsapp_(n, npt, &xopt[1], &xpt[xpt_offset], &gq[1], &hq[1], &pq[1], &
       delta, &d__[1], &w[1], &w[np], &w[np + n], &w[np + (n << 1)], &
        crvmin);
    dsq = 0;
    i__1 = n;
    for (i__ = 1; i__ <= i__1; ++i__) {
        /* Computing 2nd power */
        d__1 = d__[i__];
        dsq += d__1 * d__1;
    }
    /* Computing MIN */
    d__1 = delta, d__2 = sqrt(dsq);
    dnorm = fmin(d__1, d__2);
    if (dnorm < .5 * rho) {
        knew = -1;
        delta = 0.1 * delta;
        ratio = -1.;
        if (delta <= rho * 1.5) delta = rho;
        if (nf <= nfsav + 2) goto L460;
        temp = crvmin * .125 * rho * rho;
        /* Computing MAX */
        d__1 = fmax(diffa, diffb);
        if (temp <= fmax(d__1, diffc)) goto L460;
        goto L490;
    }
    /* Shift XBASE if XOPT may be too far from XBASE. First make the
     * changes to BMAT that do not depend on ZMAT. */
L120:
    if (dsq <= xoptsq * .001) {
        tempq = xoptsq * .25;
        i__1 = npt;
        for (k = 1; k <= i__1; ++k) {
            sum = 0;
            i__2 = n;
            for (i__ = 1; i__ <= i__2; ++i__)
                sum += xpt[k + i__ * xpt_dim1] * xopt[i__];
            temp = pq[k] * sum;
            sum -= .5 * xoptsq;
            w[npt + k] = sum;
            i__2 = n;
            for (i__ = 1; i__ <= i__2; ++i__) {
                gq[i__] += temp * xpt[k + i__ * xpt_dim1];
                xpt[k + i__ * xpt_dim1] -= .5 * xopt[i__];
                vlag[i__] = bmat[k + i__ * bmat_dim1];
                w[i__] = sum * xpt[k + i__ * xpt_dim1] + tempq * xopt[i__];
                ip = npt + i__;
                i__3 = i__;
                for (j = 1; j <= i__3; ++j)
                    bmat[ip + j * bmat_dim1] = bmat[ip + j * bmat_dim1] +
                        vlag[i__] * w[j] + w[i__] * vlag[j];
            }
        }
        /* Then the revisions of BMAT that depend on ZMAT are
         * calculated. */
        i__3 = nptm;
        for (k = 1; k <= i__3; ++k) {
            sumz = 0;
            i__2 = npt;
            for (i__ = 1; i__ <= i__2; ++i__) {
                sumz += zmat[i__ + k * zmat_dim1];
                w[i__] = w[npt + i__] * zmat[i__ + k * zmat_dim1];
            }
            i__2 = n;
            for (j = 1; j <= i__2; ++j) {
                sum = tempq * sumz * xopt[j];
                i__1 = npt;
                for (i__ = 1; i__ <= i__1; ++i__)
                    sum += w[i__] * xpt[i__ + j * xpt_dim1];
                vlag[j] = sum;
                if (k < idz) sum = -sum;
                i__1 = npt;
                for (i__ = 1; i__ <= i__1; ++i__)
                    bmat[i__ + j * bmat_dim1] += sum * zmat[i__ + k * zmat_dim1];
            }
            i__1 = n;
            for (i__ = 1; i__ <= i__1; ++i__) {
                ip = i__ + npt;
                temp = vlag[i__];
                if (k < idz) temp = -temp;
                i__2 = i__;
                for (j = 1; j <= i__2; ++j)
                    bmat[ip + j * bmat_dim1] += temp * vlag[j];
            }
        }
        /* The following instructions complete the shift of XBASE,
         * including the changes to the parameters of the quadratic
         * model. */
        ih = 0;
        i__2 = n;
        for (j = 1; j <= i__2; ++j) {
            w[j] = 0;
            i__1 = npt;
            for (k = 1; k <= i__1; ++k) {
                w[j] += pq[k] * xpt[k + j * xpt_dim1];
                xpt[k + j * xpt_dim1] -= .5 * xopt[j];
            }
            i__1 = j;
            for (i__ = 1; i__ <= i__1; ++i__) {
                ++ih;
                if (i__ < j) gq[j] += hq[ih] * xopt[i__];
                gq[i__] += hq[ih] * xopt[j];
                hq[ih] = hq[ih] + w[i__] * xopt[j] + xopt[i__] * w[j];
                bmat[npt + i__ + j * bmat_dim1] = bmat[npt + j + i__ *
                                 bmat_dim1];
            }
        }
        i__1 = n;
        for (j = 1; j <= i__1; ++j) {
            xbase[j] += xopt[j];
            xopt[j] = 0;
        }
        xoptsq = 0;
    }
    /* Pick the model step if KNEW is positive. A different choice of D
     * may be made later, if the choice of D by BIGLAG causes
     * substantial cancellation in DENOM. */
    if (knew > 0) {
        biglag_(n, npt, &xopt[1], &xpt[xpt_offset], &bmat[bmat_offset], &zmat[zmat_offset], &idz,
                ndim, &knew, &dstep, &d__[1], &alpha, &vlag[1], &vlag[npt + 1], &w[1], &w[np], &w[np + n], func);
    }
    /* Calculate VLAG and BETA for the current choice of D. The first
     * NPT components of W_check will be held in W. */
    i__1 = npt;
    for (k = 1; k <= i__1; ++k) {
        suma = 0;
        sumb = 0;
        sum = 0;
        i__2 = n;
        for (j = 1; j <= i__2; ++j) {
            suma += xpt[k + j * xpt_dim1] * d__[j];
            sumb += xpt[k + j * xpt_dim1] * xopt[j];
            sum += bmat[k + j * bmat_dim1] * d__[j];
        }
        w[k] = suma * (.5 * suma + sumb);
        vlag[k] = sum;
    }
    beta = 0;
    i__1 = nptm;
    for (k = 1; k <= i__1; ++k) {
        sum = 0;
        i__2 = npt;
        for (i__ = 1; i__ <= i__2; ++i__)
            sum += zmat[i__ + k * zmat_dim1] * w[i__];
        if (k < idz) {
            beta += sum * sum;
            sum = -sum;
        } else beta -= sum * sum;
        i__2 = npt;
        for (i__ = 1; i__ <= i__2; ++i__)
            vlag[i__] += sum * zmat[i__ + k * zmat_dim1];
    }
    bsum = 0;
    dx = 0;
    i__2 = n;
    for (j = 1; j <= i__2; ++j) {
        sum = 0;
        i__1 = npt;
        for (i__ = 1; i__ <= i__1; ++i__)
            sum += w[i__] * bmat[i__ + j * bmat_dim1];
        bsum += sum * d__[j];
        jp = npt + j;
        i__1 = n;
        for (k = 1; k <= i__1; ++k)
            sum += bmat[jp + k * bmat_dim1] * d__[k];
        vlag[jp] = sum;
        bsum += sum * d__[j];
        dx += d__[j] * xopt[j];
    }
    beta = dx * dx + dsq * (xoptsq + dx + dx + .5 * dsq) + beta - bsum;
    vlag[kopt] += 1.0;
    /* If KNEW is positive and if the cancellation in DENOM is
     * unacceptable, then BIGDEN calculates an alternative model step,
     * XNEW being used for working space. */
    if (knew > 0) {
        /* Computing 2nd power */
        d__1 = vlag[knew];
        temp = 1.0 + alpha * beta / (d__1 * d__1);
        if (fabs(temp) <= .8) {
            bigden_(n, npt, &xopt[1], &xpt[xpt_offset], &bmat[bmat_offset], &
                zmat[zmat_offset], &idz, ndim, &kopt, &knew, &d__[1], &w[
                                             1], &vlag[1], &beta, &xnew[1], &w[*ndim + 1], &w[*ndim *
                                    6 + 1]);
        }
    }
    /* Calculate the next value of the objective function. */
L290:
    i__2 = n;
    for (i__ = 1; i__ <= i__2; ++i__) {
        xnew[i__] = xopt[i__] + d__[i__];
        x[i__] = xbase[i__] + xnew[i__];
    }
    ++nf;
L310:
    if (nf > nftest) {
        --nf;
        fprintf(stderr, "++ Return from NEWUOA because CALFUN has been called MAXFUN times.\n");
        goto L530;
    }
    f = func(n, &x[1]);
    if (nf <= npt) goto L70;
    if (knew == -1) goto L530;
    /* Use the quadratic model to predict the change in F due to the
     * step D, and set DIFF to the error of this prediction. */
    vquad = ih = 0;
    i__2 = n;
    for (j = 1; j <= i__2; ++j) {
        vquad += d__[j] * gq[j];
        i__1 = j;
        for (i__ = 1; i__ <= i__1; ++i__) {
            ++ih;
            temp = d__[i__] * xnew[j] + d__[j] * xopt[i__];
            if (i__ == j) temp = .5 * temp;
            vquad += temp * hq[ih];
        }
    }
    i__1 = npt;
    for (k = 1; k <= i__1; ++k) vquad += pq[k] * w[k];
    diff = f - fopt - vquad;
    diffc = diffb;
    diffb = diffa;
    diffa = fabs(diff);
    if (dnorm > rho) nfsav = nf;
    /* Update FOPT and XOPT if the new F is the least value of the
     * objective function so far. The branch when KNEW is positive
     * occurs if D is not a trust region step. */
    fsave = fopt;
    if (f < fopt) {
        fopt = f;
        xoptsq = 0;
        i__1 = n;
        for (i__ = 1; i__ <= i__1; ++i__) {
            xopt[i__] = xnew[i__];
            /* Computing 2nd power */
            d__1 = xopt[i__];
            xoptsq += d__1 * d__1;
        }
    }
    ksave = knew;
    if (knew > 0) goto L410;
    /* Pick the next value of DELTA after a trust region step. */
    if (vquad >= 0) {
        fprintf(stderr, "++ Return from NEWUOA because a trust region step has failed to reduce Q.\n");
        goto L530;
    }
    ratio = (f - fsave) / vquad;
    if (ratio <= 0.1) {
        delta = .5 * dnorm;
    } else if (ratio <= .7) {
        /* Computing MAX */
        d__1 = .5 * delta;
        delta = fmax(d__1, dnorm);
    } else {
        /* Computing MAX */
        d__1 = .5 * delta, d__2 = dnorm + dnorm;
        delta = fmax(d__1, d__2);
    }
    if (delta <= rho * 1.5) delta = rho;
    /* Set KNEW to the index of the next interpolation point to be
     * deleted. */
    /* Computing MAX */
    d__2 = 0.1 * delta;
    /* Computing 2nd power */
    d__1 = fmax(d__2, rho);
    rhosq = d__1 * d__1;
    ktemp = detrat = 0;
    if (f >= fsave) {
        ktemp = kopt;
        detrat = 1.0;
    }
    i__1 = npt;
    for (k = 1; k <= i__1; ++k) {
        hdiag = 0;
        i__2 = nptm;
        for (j = 1; j <= i__2; ++j) {
            temp = 1.0;
            if (j < idz) temp = -1.0;
            /* Computing 2nd power */
            d__1 = zmat[k + j * zmat_dim1];
            hdiag += temp * (d__1 * d__1);
        }
        /* Computing 2nd power */
        d__2 = vlag[k];
        temp = (d__1 = beta * hdiag + d__2 * d__2, fabs(d__1));
        distsq = 0;
        i__2 = n;
        for (j = 1; j <= i__2; ++j) {
            /* Computing 2nd power */
            d__1 = xpt[k + j * xpt_dim1] - xopt[j];
            distsq += d__1 * d__1;
        }
        if (distsq > rhosq) {
            /* Computing 3rd power */
            d__1 = distsq / rhosq;
            temp *= d__1 * (d__1 * d__1);
        }
        if (temp > detrat && k != ktemp) {
            detrat = temp;
            knew = k;
        }
    }
    if (knew == 0) goto L460;
    /* Update BMAT, ZMAT and IDZ, so that the KNEW-th interpolation
     * point can be moved. Begin the updating of the quadratic model,
     * starting with the explicit second derivative term. */
L410:
    update_(n, npt, &bmat[bmat_offset], &zmat[zmat_offset], &idz, ndim, &vlag[1], &beta, &knew, &w[1]);
    fval[knew] = f;
    ih = 0;
    i__1 = n;
    for (i__ = 1; i__ <= i__1; ++i__) {
        temp = pq[knew] * xpt[knew + i__ * xpt_dim1];
        i__2 = i__;
        for (j = 1; j <= i__2; ++j) {
            ++ih;
            hq[ih] += temp * xpt[knew + j * xpt_dim1];
        }
    }
    pq[knew] = 0;
    /* Update the other second derivative parameters, and then the
     * gradient vector of the model. Also include the new interpolation
     * point. */
    i__2 = nptm;
    for (j = 1; j <= i__2; ++j) {
        temp = diff * zmat[knew + j * zmat_dim1];
        if (j < idz) temp = -temp;
        i__1 = npt;
        for (k = 1; k <= i__1; ++k) {
            pq[k] += temp * zmat[k + j * zmat_dim1];
        }
    }
    gqsq = 0;
    i__1 = n;
    for (i__ = 1; i__ <= i__1; ++i__) {
        gq[i__] += diff * bmat[knew + i__ * bmat_dim1];
        /* Computing 2nd power */
        d__1 = gq[i__];
        gqsq += d__1 * d__1;
        xpt[knew + i__ * xpt_dim1] = xnew[i__];
    }
    /* If a trust region step makes a small change to the objective
     * function, then calculate the gradient of the least Frobenius norm
     * interpolant at XBASE, and store it in W, using VLAG for a vector
     * of right hand sides. */
    if (ksave == 0 && delta == rho) {
        if (fabs(ratio) > .01) {
            itest = 0;
        } else {
            i__1 = npt;
            for (k = 1; k <= i__1; ++k)
                vlag[k] = fval[k] - fval[kopt];
            gisq = 0;
            i__1 = n;
            for (i__ = 1; i__ <= i__1; ++i__) {
                sum = 0;
                i__2 = npt;
                for (k = 1; k <= i__2; ++k)
                    sum += bmat[k + i__ * bmat_dim1] * vlag[k];
                gisq += sum * sum;
                w[i__] = sum;
            }
            /* Test whether to replace the new quadratic model by the
             * least Frobenius norm interpolant, making the replacement
             * if the test is satisfied. */
            ++itest;
            if (gqsq < gisq * 100.) itest = 0;
            if (itest >= 3) {
                i__1 = n;
                for (i__ = 1; i__ <= i__1; ++i__) gq[i__] = w[i__];
                i__1 = nh;
                for (ih = 1; ih <= i__1; ++ih) hq[ih] = 0;
                i__1 = nptm;
                for (j = 1; j <= i__1; ++j) {
                    w[j] = 0;
                    i__2 = npt;
                    for (k = 1; k <= i__2; ++k)
                        w[j] += vlag[k] * zmat[k + j * zmat_dim1];
                    if (j < idz) w[j] = -w[j];
                }
                i__1 = npt;
                for (k = 1; k <= i__1; ++k) {
                    pq[k] = 0;
                    i__2 = nptm;
                    for (j = 1; j <= i__2; ++j)
                        pq[k] += zmat[k + j * zmat_dim1] * w[j];
                }
                itest = 0;
            }
        }
    }
    if (f < fsave) kopt = knew;
    /* If a trust region step has provided a sufficient decrease in F,
     * then branch for another trust region calculation. The case
     * KSAVE>0 occurs when the new function value was calculated by a
     * model step. */
    if (f <= fsave + 0.1 * vquad) goto L100;
    if (ksave > 0) goto L100;
    /* Alternatively, find out if the interpolation points are close
     * enough to the best point so far. */
    knew = 0;
L460:
    distsq = delta * 4. * delta;
    i__2 = npt;
    for (k = 1; k <= i__2; ++k) {
        sum = 0;
        i__1 = n;
        for (j = 1; j <= i__1; ++j) {
            /* Computing 2nd power */
            d__1 = xpt[k + j * xpt_dim1] - xopt[j];
            sum += d__1 * d__1;
        }
        if (sum > distsq) {
            knew = k;
            distsq = sum;
        }
    }
    /* If KNEW is positive, then set DSTEP, and branch back for the next
     * iteration, which will generate a "model step". */
    if (knew > 0) {
        /* Computing MAX and MIN*/
        d__2 = 0.1 * sqrt(distsq), d__3 = .5 * delta;
        d__1 = fmin(d__2, d__3);
        dstep = fmax(d__1, rho);
        dsq = dstep * dstep;
        goto L120;
    }
    if (ratio > 0) goto L100;
    if (fmax(delta, dnorm) > rho) goto L100;
    /* The calculations with the current value of RHO are complete. Pick
     * the next values of RHO and DELTA. */
L490:
    if (rho > rhoend) {
        delta = .5 * rho;
        ratio = rho / rhoend;
        if (ratio <= 16.) rho = rhoend;
        else if (ratio <= 250.) rho = sqrt(ratio) * rhoend;
        else rho = 0.1 * rho;
        delta = fmax(delta, rho);
        goto L90;
    }
    /* Return from the calculation, after another Newton-Raphson step,
     * if it is too short to have been tried before. */
    if (knew == -1) goto L290;
L530:
    if (fopt <= f) {
        i__2 = n;
        for (i__ = 1; i__ <= i__2; ++i__)
            x[i__] = xbase[i__] + xopt[i__];
        f = fopt;
    }
    *ret_nf = nf;
    return f;
}

template<class TYPE, class Func>
static TYPE newuoa_(int n, int npt, TYPE *x, TYPE rhobeg, TYPE rhoend, int *ret_nf, int maxfun, TYPE *w, Func &func)
{
    /* This subroutine seeks the least value of a function of many
     * variables, by a trust region method that forms quadratic models
     * by interpolation. There can be some freedom in the interpolation
     * conditions, which is taken up by minimizing the Frobenius norm of
     * the change to the second derivative of the quadratic model,
     * beginning with a zero matrix. The arguments of the subroutine are
     * as follows. */

    /* N must be set to the number of variables and must be at least
     * two. NPT is the number of interpolation conditions. Its value
     * must be in the interval [N+2,(N+1)(N+2)/2]. Initial values of the
     * variables must be set in X(1),X(2),...,X(N). They will be changed
     * to the values that give the least calculated F. RHOBEG and RHOEND
     * must be set to the initial and final values of a trust region
     * radius, so both must be positive with RHOEND<=RHOBEG. Typically
     * RHOBEG should be about one tenth of the greatest expected change
     * to a variable, and RHOEND should indicate the accuracy that is
     * required in the final values of the variables. MAXFUN must be set
     * to an upper bound on the number of calls of CALFUN.  The array W
     * will be used for working space. Its length must be at least
     * (NPT+13)*(NPT+N)+3*N*(N+3)/2. */

    /* SUBROUTINE CALFUN (N,X,F) must be provided by the user. It must
     * set F to the value of the objective function for the variables
     * X(1),X(2),...,X(N). Partition the working space array, so that
     * different parts of it can be treated separately by the subroutine
     * that performs the main calculation. */

    int id, np, iw, igq, ihq, ixb, ifv, ipq, ivl, ixn,
        ixo, ixp, ndim, nptm, ibmat, izmat;

    /* Parameter adjustments */
    --w; --x;
    /* Function Body */
    np = n + 1;
    nptm = npt - np;
    if (npt < n + 2 || npt > (n + 2) * np / 2) {
        fprintf(stderr, "** Return from NEWUOA because NPT is not in the required interval.\n");
        return 1;
    }
    ndim = npt + n;
    ixb = 1;
    ixo = ixb + n;
    ixn = ixo + n;
    ixp = ixn + n;
    ifv = ixp + n * npt;
    igq = ifv + npt;
    ihq = igq + n;
    ipq = ihq + n * np / 2;
    ibmat = ipq + npt;
    izmat = ibmat + ndim * n;
    id = izmat + npt * nptm;
    ivl = id + n;
    iw = ivl + ndim;
    /* The above settings provide a partition of W for subroutine
     * NEWUOB. The partition requires the first NPT*(NPT+N)+5*N*(N+3)/2
     * elements of W plus the space that is needed by the last array of
     * NEWUOB. */
    return newuob_(n, npt, &x[1], rhobeg, rhoend, ret_nf, maxfun, &w[ixb], &w[ixo], &w[ixn],
                   &w[ixp], &w[ifv], &w[igq], &w[ihq], &w[ipq], &w[ibmat], &w[izmat],
                   &ndim, &w[id], &w[ivl], &w[iw], func);
}

template<class TYPE, class Func>
TYPE min_newuoa(int n, TYPE *x, Func &func, TYPE rb, TYPE tol, int max_iter)
{
    int npt = 2 * n + 1, rnf;
    TYPE ret;
    TYPE *w = (TYPE*)calloc((npt+13)*(npt+n) + 3*n*(n+3)/2 + 11, sizeof(TYPE));
    ret = newuoa_(n, 2*n+1, x, rb, tol, &rnf, max_iter, w, func);
    free(w);
    return ret;
}

#endif