!----- AGPL --------------------------------------------------------------------
!
! Copyright (C) Stichting Deltares, 2015.
!
! This file is part of Delft3D (D-Flow Flexible Mesh component).
!
! Delft3D is free software: you can redistribute it and/or modify
! it under the terms of the GNU Affero General Public License as
! published by the Free Software Foundation version 3.
!
! Delft3D is distributed in the hope that it will be useful,
! but WITHOUT ANY WARRANTY; without even the implied warranty of
! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
! GNU Affero General Public License for more details.
!
! You should have received a copy of the GNU Affero General Public License
! along with Delft3D. If not, see .
!
! contact: delft3d.support@deltares.nl
! Stichting Deltares
! P.O. Box 177
! 2600 MH Delft, The Netherlands
!
! All indications and logos of, and references to, "Delft3D",
! "D-Flow Flexible Mesh" and "Deltares" are registered trademarks of Stichting
! Deltares, and remain the property of Stichting Deltares. All rights reserved.
!
!-------------------------------------------------------------------------------
! $Id: unstruc_opengis.f90 42642 2015-10-21 11:34:20Z dam_ar $
! $HeadURL: https://repos.deltares.nl/repos/ds/trunk/additional/unstruc/src/unstruc_opengis.f90 $
!> @file unstruc_opengis.f90
!! Output Unstruc data in KML format.
!> Two types of KML output:
!! * Unstructured grid as line network.
!! * Depth values of unstructured grid cells.
!!
!! Line network (fast):
!! An unstructured net is exported as a set of objects.
!! For improved performance, we traverse paths of connected net links
!! (greedily). This reduces numl links to #LineStrings by 1 or 2 orders
!! of magnitude, giving much smoother behavior in Google Earth.
!!
!! Depth grid:
!! All grid cells are exported as objects.
!! Not much performance tuning is possible, since every patch has its
!! own colour (i.e. style reference).
module unstruc_opengis
use precision
implicit none
private
public :: kml_write_net
contains
!> Writes a D-Flow FM net to a new kml-file.
!! Overwrites an existing file, if present.
!!
!! Export parameters are taken from the m_kml_parameters module.
subroutine kml_write_net(filename)
use network_data, only: numl, numl1d, numk, xk, yk, zk, kn, tnod, nod, nmk, nump, netcell
use m_missing, only: dmiss
use m_sferic
use m_kml_parameters
character(len=*), intent(in) :: filename
real(kind=hp), allocatable :: xloc(:), yloc(:), zloc(:)
integer, allocatable :: lc(:)
real(kind=hp) :: xmin, xmax, ymin, ymax, zmin, zmax, zp, half
integer :: i, n, numls, numlt, kcur, knext, lcur, L, iloc, kn3cur, LMOD
integer :: kmlunit=1234, ios
open(kmlunit, file=trim(filename), status='replace', action='write', iostat=ios)
if (ios /= 0) then
return
end if
if (numk <= 0 .or. numl <= 0) then
return
end if
CALL READYY ('SAVE KML',0d0)
allocate(xloc(numl+1), yloc(numl+1), zloc(numl+1), lc(numl))
lc = 0
call kml_write_header(kmlunit)
call kml_write_netstyles(kmlunit)
if (kml_jadepth == 1) then
half = .5d0
else
half = 1d0
end if
if (kml_janet == 1) then
LMOD = max(1,NUML/100)
iloc = 0
! Trivial loop to separate 1D and 2D links, to give them a different style
! in the KML-file.
do kn3cur=1,2
if (kn3cur == 1) then
numls = 1
numlt = numl1d
else
numls = numl1d+1
numlt = numl
end if
if (numlt - numls < 0) then
cycle
else
write(kmlunit, '(a)') ''
write(kmlunit, '(a)') 'FM unstructured grid'
write(kmlunit, '(a,i1,a)') '#link', kn3cur, 'D'
write(kmlunit, '(a)') ''
end if
! Make sure all links are written to output file (but trace most of
! them as connected path in inner do-loop.
do i=numls,numlt
! Check whether link was already written to file.
if (lc(i) == 1) then
cycle
end if
kcur = kn(1,i)
xloc(1) = xk(kcur)
yloc(1) = yk(kcur)
!if (zk(kcur) /= dmiss) then
! zloc(1) = transform_altitude(zk(kcur))
!else
zloc(1) = 0d0
!end if
kcur = kn(2,i)
xloc(2) = xk(kcur)
yloc(2) = yk(kcur)
!if (zk(kcur) /= dmiss) then
! zloc(2) = transform_altitude(zk(kcur))
!else
zloc(2) = 0d0
!end if
iloc = 2
lc(i) = 1
! We started a new path, now trace connected links as long as possible.
do
IF (MOD(i+iloc-2,LMOD) == 1) CALL READYY ('SAVE KML',half*dble(i+iloc-2)/dble(NUML))
lcur = 0
! Find an outgoing link of current net node that wasn't yet written and has correct link type.
do L=1,nmk(kcur)
if (lc(nod(kcur)%lin(L)) == 0 .and. kn(3,nod(kcur)%lin(L)) == kn3cur) then
lcur = nod(kcur)%lin(L)
exit
end if
end do
if (lcur == 0) then ! no further links in string found, leave this loop and write it.
exit
end if
! lcur is new link: add it to linestring
iloc = iloc+1
call othernode(kcur, lcur, knext)
xloc(iloc) = xk(knext)
yloc(iloc) = yk(knext)
!if (zk(knext) /= dmiss) then
! zloc(iloc) = transform_altitude(zk(knext))
!else
zloc(iloc) = 0d0
!end if
lc(lcur) = 1
kcur = knext
end do
! Write the traced path of net links
call kml_write_linestring(kmlunit, xloc(1:iloc), yloc(1:iloc), zloc(1:iloc))
end do
write(kmlunit, '(a)') ''
write(kmlunit, '(a)') ''
end do
CALL READYY ('SAVE KML',half)
else
half = 0d0 ! Nothing done yet, start progress bar of next (depths) step at 0.
end if ! kml_janet == 1
if (kml_jadepth == 1) then
zmin = +huge(1d0)
zmax = -huge(1d0)
do kcur=1,numk
if (zk(kcur) /= dmiss) then
zmin = min(zmin, zk(kcur))
zmax = max(zmax, zk(kcur))
end if
end do
if (kml_zmin == 0d0 .and. kml_zmax == 0d0) then
kml_zmin = zmin
kml_zmax = zmax
end if
call findcells(0)
write(kmlunit, '(a)') ''
write(kmlunit, '(a)') ' FM depth grid'
LMOD = max(1,NUML/100)
do n=1,nump
IF (MOD(n,LMOD) == 1) CALL READYY ('SAVE KML',half+(1d0-half)*dble(n)/dble(nump))
zp = 0d0
do i=1,netcell(n)%n
kcur = netcell(n)%nod(i)
if (zk(kcur) == dmiss) then
zp = dmiss
exit
end if
zp = zp + zk(kcur)
end do
if (zp /= dmiss) then
zp = zp/netcell(n)%n
end if
write(kmlunit, '(a)') ''
if (zp == dmiss) then
write(kmlunit, '(a)') '#markmiss'
else
i = max(1,min(30,floor((zp-kml_zmin)/(kml_zmax-kml_zmin)*30)+1))
write(kmlunit, '(a,i2.2,a)') '#mark', i, ''
end if
write(kmlunit, '(a)') ''
if (kml_jadepth3d == 1) then
write(kmlunit, '(a)') ' absolute'
else
write(kmlunit, '(a)') ' clampToGround'
end if
write(kmlunit, '(a)') ' '
write(kmlunit, '(a)') ' '
write(kmlunit, '(a)') ' '
do i=0,netcell(n)%n
if (i==0) then
kcur = netcell(n)%nod(netcell(n)%n)
else
kcur = netcell(n)%nod(i)
end if
call kml_write_point_coords(kmlunit, xk(kcur), yk(kcur), transform_altitude(zk(kcur), zmin))
end do
write(kmlunit, '(a)') ' '
write(kmlunit, '(a)') ' '
write(kmlunit, '(a)') ' '
write(kmlunit, '(a)') ''
write(kmlunit, '(a)') ''
end do
write(kmlunit, '(a)') ''
end if ! kml_jadepth == 1
call kml_write_footer(kmlunit)
close(kmlunit)
deallocate(xloc, yloc, zloc, lc)
CALL READYY ('SAVE KML',-1d0)
end subroutine kml_write_net
!!! Private routines
!> Transforms an actual altitude into an altitude that displays nicely:
!! Optionally add offset to lift bathymetry surface above the sea surface.
!! Optionally scale altitude differences by a factor (>= 5 recommended).
!! @see module m_kml_parameters
function transform_altitude(altin, altmin) result(altout)
use m_kml_parameters
use m_missing
real(kind=hp), intent(in) :: altin !< Original altitude
real(kind=hp), intent(in) :: altmin !< Global minimal altitude (should be pre-computed at call site)
real(kind=hp) :: altout !< Transformed altitude
if (altin == dmiss) then
altout = kml_dmiss
return
end if
altout = kml_altfact*altin+kml_useroffset
if (kml_jaoffsetzk == 1) then
altout = altout-kml_altfact*altmin ! Do NOT offset, so: add the altmin back again, which was substracted in previous multiplication step.
end if
end function transform_altitude
!> Write a single line with coordinates of a single point into a kml file.
subroutine kml_write_point_coords(kmlunit, lat, lon, alt)
integer, intent(in) :: kmlunit !< Unit number of already opened KML file.
real(kind=hp), intent(in) :: lat !< Point's latitude coordinate (deg)
real(kind=hp), intent(in) :: lon !< Point's longitude coordinate (deg)
real(kind=hp), intent(in) :: alt !< Point's altitude (m)
character(len=22) :: tmpstring
tmpstring = ' '
write(tmpstring, '(f22.6)') lat
write(kmlunit, '(a,a,a)', advance='no') ' ', trim(adjustl(tmpstring)), ','
tmpstring = ' '
write(tmpstring, '(f22.6)') lon
write(kmlunit, '(a,a)', advance='no') trim(adjustl(tmpstring)), ','
tmpstring = ' '
write(tmpstring, '(f22.6)') alt
write(kmlunit, '(a)' ) trim(adjustl(tmpstring))
end subroutine kml_write_point_coords
!> Write standard KML header to file (version+namespace+Document)
subroutine kml_write_header(kmlunit)
integer, intent(in) :: kmlunit !< File unit number connected to output file.
write(kmlunit, '(a)') ''
write(kmlunit, '(a)') ''
write(kmlunit, '(a)') ''
!AvD: TODO: name
end subroutine kml_write_header
!> Write standard KML footer to file (Document+xml).
subroutine kml_write_footer(kmlunit)
integer, intent(in) :: kmlunit !< File unit number connected to output file.
write(kmlunit, '(a)') ''
write(kmlunit, '(a)') ''
end subroutine kml_write_footer
!> Write a linestyle for each netlink type to file.
subroutine kml_write_netstyles(kmlunit)
integer, intent(in) :: kmlunit !< File unit number connected to output file.
integer :: i
real(kind=sp) :: r, g, b
! Color: aabbggrr, aa=alpha (00 to ff); bb=blue (00 to ff); gg=green (00 to ff); rr=red (00 to ff).
write(kmlunit, '(a)') ''
write(kmlunit, '(a)') ''
write(kmlunit, '(a,i2.2,a)') ''
do i=1,30
write(kmlunit, '(a,i2.2,a)') ''
end do
end subroutine kml_write_netstyles
!> Write a line string (list of connected points) to file.
subroutine kml_write_linestring(kmlunit, lat, lon, height)
integer, intent(in) :: kmlunit !< File unit number connected to output file.
real(kind=hp), intent(in) :: lat(:), lon(:), height(:) !< Horizontal and vertical coordinates of all points.
character(len=22) :: tmpstring
integer :: i, npts
npts = size(lat)
write(kmlunit, '(a)') ' '
write(kmlunit, '(a)') ' 0'
write(kmlunit, '(a)') ' 1'
write(kmlunit, '(a)') ' absolute'
write(kmlunit, '(a)') ' '
do i=1,npts
! write(kmlunit, '(a,3(f12.6,a2))') ' ', lat(i), ', ', lon(i), ', ', height(i)
tmpstring = ' '
write(tmpstring, '(f22.6)') lat(i)
write(kmlunit, '(a,a,a)', advance='no') ' ', trim(adjustl(tmpstring)), ','
tmpstring = ' '
write(tmpstring, '(f22.6)') lon(i)
write(kmlunit, '(a,a)', advance='no') trim(adjustl(tmpstring)), ','
tmpstring = ' '
write(tmpstring, '(f22.6)') height(i)
write(kmlunit, '(a)' ) trim(adjustl(tmpstring))
end do
write(kmlunit, '(a)') ' '
write(kmlunit, '(a)') ' '
end subroutine kml_write_linestring
!-------------
!subroutine hvsrgb(h,v,s,r,g,b)
!
! SYNOPSIS
! HVSRGB() calculates the red, green, & blue components for a
! color given in hue, value, & saturation values.
!
! DESCRIPTION
!
! real, intent=(in) :: h
! H is the hue value in the range of 0 to 360 degrees
!
! real, intent=(in) :: v
! V is the "value" as a percent value from 0 to 100.
!
! real, intent=(in) :: s
! S is the saturation as a percent from 0 to 100.
!
! real, intent=(out) :: r
! R is the red component as a value of 0 to 100.
!
! real, intent=(out) :: g
! G is the green component as a value of 0 to 100.
!
! real, intent=(out) :: b
! B is the blue component as a value of 0 to 100.
!
! DEPENDENCIES
!
! + NONE
!
! SEE ALSO
! see JUCOLOR().
!
! REFERENCES
!
! + This is heavily based on chapter 17 of "Fundamentals of
! Interactive Computer Graphics"; J. D. Foley and A. Van Dam.
!
! AUTHOR
!
! + John S. Urban
! + Kevin Kendall
! PUBLIC DOMAIN, taken from: http://fortranwiki.org/fortran/show/jucolor
! _________________________________________________________________
subroutine hvsrgb(h0,v0,s0,r,g,b)
!@(#) given hue, saturation, value calculate red, green, & blue components
!
! given : h as value of 0 to 360 degrees.
! . s and v each as a value of 0 to 100.
! desired: r, g, and b as a value of 0 to 100.
! this particular algorithm was taken from foley and van dam.
!
real(kind=sp), intent(in) :: h0
real(kind=sp), intent(in) :: v0
real(kind=sp), intent(in) :: s0
real(kind=sp), intent(out) :: r
real(kind=sp), intent(out) :: g
real(kind=sp), intent(out) :: b
real(kind=sp) :: h,v,s
integer :: ifloor
real(kind=sp) :: f,p,q,t
h=h0
v=v0
s=s0
v=v/100.0
s=s/100.0
if(s.eq.0.0) then
r=v
g=v
b=v
endif
if(h.eq.360.0) then
h=0.0
endif
h=h/60.0
ifloor=int(h)
f=h-ifloor
p=v*(1.0-s)
q=v*(1.0-(s*f))
t=v*(1.0-(s*(1-f)))
if(ifloor.eq.0) then
r=v
g=t
b=p
else if(ifloor.eq.1) then
r=q
g=v
b=p
else if(ifloor.eq.2) then
r=p
g=v
b=t
else if(ifloor.eq.3) then
r=p
g=q
b=v
else if(ifloor.eq.4) then
r=t
g=p
b=v
else if(ifloor.eq.5) then
r=v
g=p
b=q
endif
r=r*100.0
g=g*100.0
b=b*100.0
return
end subroutine hvsrgb
end module unstruc_opengis
module io_openfoam
implicit none
contains
!> Write D-Flow FM info+version as an OpenFOAM header into an ASCII file.
subroutine foam_write_dflowfminfo(mout)
use unstruc_version_module
integer, intent(in) :: mout !< File unit nr for output.
character(len=20) :: rundat
call datum(rundat)
write(mout, '(a)') '/*---------------------------------------------------------------------------*\ '
write(mout, '(a,a,a,a)') '| Generated on ', trim(rundat), repeat(' ', 79-16-len_trim(rundat)), '|'
write(mout, '(a,a,a,a)') '| ', trim(unstruc_version_full), repeat(' ', 79-3-len_trim(unstruc_version_full)), '|'
write(mout, '(a)') '\*---------------------------------------------------------------------------*/ '
end subroutine foam_write_dflowfminfo
!> Writes an OpenFOAM data file header.
!!
!! The type of data should be described by the headdict dictionary,
!! according to http://www.openfoam.org/docs/user/basic-file-format.php
subroutine foam_write_datafile_header(mout, headdict)
use properties
integer, intent(in) :: mout !< File unit nr for output.
type(TREE_DATA), pointer :: headdict !< Tree structure containing dictionary entries (should be a list/flat tree)
call foam_write_dflowfminfo(mout)
call foam_write_dictionary(mout, 'FoamFile', headdict)
write (mout, '(a)') '// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //'
write (mout, '(a)') ''
write (mout, '(a)') ''
end subroutine foam_write_datafile_header
!> Generic writer for OpenFOAM dictionary (list of key-value pairs)
!! Used for printing the 'FoamFile' file header dictionary.
subroutine foam_write_dictionary(mout, name, dicttree)
use properties
integer, intent(in) :: mout !< File unit number for output.
character(len=*), intent(in) :: name !< name of the dictionary
type(TREE_DATA), pointer :: dicttree !< Tree structure containing dictionary entries (should be a list/flat tree)
character(len=1), allocatable :: lenmaxdata(:)
logical :: dummylog
integer :: keymaxlen
write(mout, '(a)') name
write(mout, '(a)') '{'
allocate(lenmaxdata(size(transfer(123, node_value)))) ! Fit a single integer into char array (generally 4 bytes)
! Determine maximum key stringlength (used for prettyprinting/alignment in print_foam_dict)
call tree_fold(dicttree, max_keylength, leaf_keylength, lenmaxdata, dummylog)
keymaxlen = transfer(lenmaxdata, 123)
! Print the tree by traversing it depth-first, pass mout and lenmax by transfer into data variable.
call tree_traverse(dicttree, print_foam_dict, transfer((/ mout, keymaxlen /), node_value), dummylog)
write(mout, '(a)') '}'
end subroutine foam_write_dictionary
!> Prints the root of a tree (as a whitespace-separated key-value pair)
!! to be used in call to tree_traverse
subroutine print_foam_dict( tree, data, stop )
use properties
type(TREE_DATA), pointer :: tree !< Tree whose root should be printed.
character(len=1), dimension(:), intent(in) :: data !< Help data (max key length, used for alignment, and output file unit nr).
logical, intent(inout) :: stop !< Whether to continue or stop.
integer, dimension(2) :: inputdata
integer :: mout
integer :: maxkeylength
character(len=1), dimension(:),pointer :: data_ptr
character(len=256) :: string
character(len=40) :: type_string
logical :: success
integer :: level
inputdata = transfer(data, inputdata)
mout = inputdata(1) !< File pointer
maxkeylength = inputdata(2)
level = tree_traverse_level()
if (level == 0) return
call tree_get_data_ptr( tree, data_ptr, type_string )
if (associated(data_ptr)) then
string = tree_get_name(tree)
write(mout, '(a,a,a)', advance='no') &
' ', trim(string), repeat(' ', max(0,maxkeylength-len_trim(string))+4)
end if
select case (type_string)
case ('STRING')
string = ''
call tree_get_data_string( tree, string, success )
write(mout,'(a,a)') trim(string), ';'
case default
string = '(unknown data type)'
write(mout,'(a,a,a,a)') '# ', trim(string), ' -- ', trim(type_string)
end select
end subroutine print_foam_dict
!> Writes the current network to a set of OpenFOAM polyMesh files.
subroutine foam_write_polymesh(filename)
use m_flowgeom
use unstruc_files
use properties
character(len=*), intent(in) :: filename !< TODO: Output file names
integer, external :: numuni
integer :: mfil
integer :: L
type(tree_data), pointer :: headdict
character(len=16) :: strtmp
call tree_create('FoamFile', headdict)
call prop_set(headdict, '', 'version', '2.0')
call prop_set(headdict, '', 'format', 'ascii')
L = len_trim(filename)
! -- Step 1. points file ----------
mfil = numuni()
call newfil(mfil, filename)
call prop_set(headdict, '', 'location', '"bla/points"')
call prop_set(headdict, '', 'class', 'vectorField')
call prop_set(headdict, '', 'object', 'points')
call foam_write_datafile_header(mfil, headdict)
call write_points_(mfil)
call doclose(mfil)
! -- Step 2. faces file ----------
contains
subroutine write_points_(mout)
use network_data
integer, intent(in) :: mout !< File unit nr for output.
integer :: k
write (strtmp, '(i10)') numk
write (mout, '(a)') adjustl(strtmp)
write (mout, '(a)') '('
do k=1,numk
write (mout, '(a,3(f25.16),a)') '(', xk(k), yk(k), zk(k), ')'
end do
write (mout, '(a)') ')'
end subroutine write_points_
subroutine write_faces_(mout)
use network_data
integer, intent(in) :: mout !< File unit nr for output.
integer :: k
write (strtmp, '(i10)') numk
write (mout, '(a)') adjustl(strtmp)
write (mout, '(a)') '('
do k=1,numk
write (mout, '(a,3(f25.16),a)') '(', xk(k), yk(k), zk(k), ')'
end do
write (mout, '(a)') ')'
end subroutine write_faces_
end subroutine foam_write_polymesh
end module io_openfoam