Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
mpim-sw
libcdi
Commits
170c3132
Commit
170c3132
authored
May 16, 2014
by
Uwe Schulzweida
Browse files
netCDF: added write support of lat/lon coordinates without dimension
parent
35ce26d8
Changes
5
Hide whitespace changes
Inline
Side-by-side
ChangeLog
View file @
170c3132
...
...
@@ -4,6 +4,10 @@
* using EXSE library version 1.3.2
* using CGRIBEX library version 1.6.4
2014-05-16 Uwe Schulzweida
* netCDF: added write support of lat/lon coordinates without dimension
2014-05-15 Uwe Schulzweida
* cgribexGetGrid: correct last lon when last lon < first lon
...
...
@@ -14,7 +18,7 @@
2014-05-13 Uwe Schulzweida
* netCDF: added support for lon/lat coordinates without dimension
* netCDF: added
read
support for lon/lat coordinates without dimension
2014-05-12 Uwe Schulzweida
...
...
src/cdi_int.h
View file @
170c3132
...
...
@@ -281,9 +281,11 @@ void stream_check_ptr(const char *caller, stream_t *streamptr);
int
streamInqFileID
(
int
streamID
);
int
zaxisInqLevelID
(
int
zaxisID
,
double
level
);
void
gridDefHasDims
(
int
gridID
,
int
hasdims
);
int
gridInqHasDims
(
int
gridID
);
const
char
*
gridNamePtr
(
int
gridtype
);
char
*
zaxisNamePtr
(
int
leveltype
);
int
zaxisInqLevelID
(
int
zaxisID
,
double
level
);
void
streamCheckID
(
const
char
*
caller
,
int
streamID
);
...
...
src/grid.c
View file @
170c3132
...
...
@@ -128,6 +128,7 @@ void grid_init(grid_t *gridptr)
gridptr
->
angle
=
0
.
0
;
gridptr
->
locked
=
FALSE
;
gridptr
->
lcomplex
=
0
;
gridptr
->
hasdims
=
TRUE
;
gridptr
->
xname
[
0
]
=
0
;
gridptr
->
yname
[
0
]
=
0
;
gridptr
->
xlongname
[
0
]
=
0
;
...
...
@@ -4050,15 +4051,13 @@ void gridInqLcc2(int gridID, double *earth_radius, double *lon_0, double *lat_0,
void
gridDefLaea
(
int
gridID
,
double
earth_radius
,
double
lon_0
,
double
lat_0
)
{
grid_t
*
gridptr
;
if
(
reshGetStatus
(
gridID
,
&
gridOps
)
==
RESH_CLOSED
)
{
Warning
(
"%s"
,
"Operation not executed."
);
return
;
}
gridptr
=
(
grid_t
*
)
reshGetVal
(
gridID
,
&
gridOps
);
grid_t
*
gridptr
=
(
grid_t
*
)
reshGetVal
(
gridID
,
&
gridOps
);
grid_check_ptr
(
gridID
,
gridptr
);
...
...
@@ -4077,9 +4076,7 @@ void gridDefLaea(int gridID, double earth_radius, double lon_0, double lat_0)
void
gridInqLaea
(
int
gridID
,
double
*
earth_radius
,
double
*
lon_0
,
double
*
lat_0
)
{
grid_t
*
gridptr
;
gridptr
=
(
grid_t
*
)
reshGetVal
(
gridID
,
&
gridOps
);
grid_t
*
gridptr
=
(
grid_t
*
)
reshGetVal
(
gridID
,
&
gridOps
);
grid_check_ptr
(
gridID
,
gridptr
);
...
...
@@ -4102,15 +4099,13 @@ void gridInqLaea(int gridID, double *earth_radius, double *lon_0, double *lat_0)
void
gridDefComplexPacking
(
int
gridID
,
int
lcomplex
)
{
grid_t
*
gridptr
;
if
(
reshGetStatus
(
gridID
,
&
gridOps
)
==
RESH_CLOSED
)
{
Warning
(
"%s"
,
"Operation not executed."
);
return
;
}
gridptr
=
(
grid_t
*
)
reshGetVal
(
gridID
,
&
gridOps
);
grid_t
*
gridptr
=
(
grid_t
*
)
reshGetVal
(
gridID
,
&
gridOps
);
grid_check_ptr
(
gridID
,
gridptr
);
...
...
@@ -4120,16 +4115,37 @@ void gridDefComplexPacking(int gridID, int lcomplex)
int
gridInqComplexPacking
(
int
gridID
)
{
int
lcomplex
;
grid_t
*
gridptr
;
grid_t
*
gridptr
=
(
grid_t
*
)
reshGetVal
(
gridID
,
&
gridOps
);
gridptr
=
(
grid_t
*
)
reshGetVal
(
gridID
,
&
gridOps
);
grid_check_ptr
(
gridID
,
gridptr
);
return
(
gridptr
->
lcomplex
);
}
void
gridDefHasDims
(
int
gridID
,
int
hasdims
)
{
if
(
reshGetStatus
(
gridID
,
&
gridOps
)
==
RESH_CLOSED
)
{
Warning
(
"%s"
,
"Operation not executed."
);
return
;
}
grid_t
*
gridptr
=
(
grid_t
*
)
reshGetVal
(
gridID
,
&
gridOps
);
grid_check_ptr
(
gridID
,
gridptr
);
lcomplex
=
gridptr
->
lcomplex
;
gridptr
->
hasdims
=
hasdims
;
}
int
gridInqHasDims
(
int
gridID
)
{
grid_t
*
gridptr
=
(
grid_t
*
)
reshGetVal
(
gridID
,
&
gridOps
);
grid_check_ptr
(
gridID
,
gridptr
);
return
(
lcomplex
);
return
(
gridptr
->
hasdims
);
}
/*
...
...
@@ -4148,15 +4164,13 @@ The function @func{gridDefNumber} defines the reference number for an unstructur
*/
void
gridDefNumber
(
int
gridID
,
const
int
number
)
{
grid_t
*
gridptr
;
if
(
reshGetStatus
(
gridID
,
&
gridOps
)
==
RESH_CLOSED
)
{
Warning
(
"%s"
,
"Operation not executed."
);
return
;
}
gridptr
=
(
grid_t
*
)
reshGetVal
(
gridID
,
&
gridOps
);
grid_t
*
gridptr
=
(
grid_t
*
)
reshGetVal
(
gridID
,
&
gridOps
);
grid_check_ptr
(
gridID
,
gridptr
);
...
...
@@ -4180,9 +4194,7 @@ The function @func{gridInqNumber} returns the reference number to an unstructure
*/
int
gridInqNumber
(
int
gridID
)
{
grid_t
*
gridptr
;
gridptr
=
(
grid_t
*
)
reshGetVal
(
gridID
,
&
gridOps
);
grid_t
*
gridptr
=
(
grid_t
*
)
reshGetVal
(
gridID
,
&
gridOps
);
grid_check_ptr
(
gridID
,
gridptr
);
...
...
@@ -4205,15 +4217,13 @@ The function @func{gridDefPosition} defines the position of grid in the referenc
*/
void
gridDefPosition
(
int
gridID
,
int
position
)
{
grid_t
*
gridptr
;
if
(
reshGetStatus
(
gridID
,
&
gridOps
)
==
RESH_CLOSED
)
{
Warning
(
"%s"
,
"Operation not executed."
);
return
;
}
gridptr
=
(
grid_t
*
)
reshGetVal
(
gridID
,
&
gridOps
);
grid_t
*
gridptr
=
(
grid_t
*
)
reshGetVal
(
gridID
,
&
gridOps
);
grid_check_ptr
(
gridID
,
gridptr
);
...
...
@@ -4237,9 +4247,7 @@ The function @func{gridInqPosition} returns the position of grid in the referenc
*/
int
gridInqPosition
(
int
gridID
)
{
grid_t
*
gridptr
;
gridptr
=
(
grid_t
*
)
reshGetVal
(
gridID
,
&
gridOps
);
grid_t
*
gridptr
=
(
grid_t
*
)
reshGetVal
(
gridID
,
&
gridOps
);
grid_check_ptr
(
gridID
,
gridptr
);
...
...
@@ -4262,15 +4270,13 @@ The function @func{gridDefReference} defines the reference URI for an unstructur
*/
void
gridDefReference
(
int
gridID
,
const
char
*
reference
)
{
grid_t
*
gridptr
;
if
(
reshGetStatus
(
gridID
,
&
gridOps
)
==
RESH_CLOSED
)
{
Warning
(
"%s"
,
"Operation not executed."
);
return
;
}
gridptr
=
(
grid_t
*
)
reshGetVal
(
gridID
,
&
gridOps
);
grid_t
*
gridptr
=
(
grid_t
*
)
reshGetVal
(
gridID
,
&
gridOps
);
grid_check_ptr
(
gridID
,
gridptr
);
...
...
@@ -4303,10 +4309,8 @@ The function @func{gridInqReference} returns the reference URI to an unstructure
*/
int
gridInqReference
(
int
gridID
,
char
*
reference
)
{
grid_t
*
gridptr
;
int
len
=
0
;
gridptr
=
(
grid_t
*
)
reshGetVal
(
gridID
,
&
gridOps
);
grid_t
*
gridptr
=
(
grid_t
*
)
reshGetVal
(
gridID
,
&
gridOps
);
grid_check_ptr
(
gridID
,
gridptr
);
...
...
@@ -4337,21 +4341,17 @@ The function @func{gridDefUUID} defines the UUID for an unstructured grid.
*/
void
gridDefUUID
(
int
gridID
,
const
char
*
uuid
)
{
grid_t
*
gridptr
;
if
(
reshGetStatus
(
gridID
,
&
gridOps
)
==
RESH_CLOSED
)
{
Warning
(
"%s"
,
"Operation not executed."
);
return
;
}
gridptr
=
(
grid_t
*
)
reshGetVal
(
gridID
,
&
gridOps
);
grid_t
*
gridptr
=
(
grid_t
*
)
reshGetVal
(
gridID
,
&
gridOps
);
grid_check_ptr
(
gridID
,
gridptr
);
memcpy
(
gridptr
->
uuid
,
uuid
,
16
);
return
;
}
/*
...
...
@@ -4371,9 +4371,7 @@ The function @func{gridInqUUID} returns the UUID to an unstructured grid.
*/
void
gridInqUUID
(
int
gridID
,
char
*
uuid
)
{
grid_t
*
gridptr
;
gridptr
=
(
grid_t
*
)
reshGetVal
(
gridID
,
&
gridOps
);
grid_t
*
gridptr
=
(
grid_t
*
)
reshGetVal
(
gridID
,
&
gridOps
);
grid_check_ptr
(
gridID
,
gridptr
);
...
...
src/grid.h
View file @
170c3132
...
...
@@ -57,6 +57,7 @@ typedef struct {
int
np
;
/* number of parallels between a pole and the equator */
int
locked
;
int
lcomplex
;
int
hasdims
;
char
xname
[
CDI_MAX_NAME
];
char
yname
[
CDI_MAX_NAME
];
char
xlongname
[
CDI_MAX_NAME
];
...
...
src/stream_cdf.c
View file @
170c3132
...
...
@@ -1332,7 +1332,7 @@ int checkGridName(int type, char *axisname, int fileID, int vlistID, int gridID,
#if defined (HAVE_LIBNETCDF)
static
void
cdfDefXaxis
(
stream_t
*
streamptr
,
int
gridID
)
void
cdfDefXaxis
(
stream_t
*
streamptr
,
int
gridID
,
int
ndims
)
{
char
units
[
CDI_MAX_NAME
];
char
longname
[
CDI_MAX_NAME
];
...
...
@@ -1343,7 +1343,7 @@ void cdfDefXaxis(stream_t *streamptr, int gridID)
int
gridID0
,
gridtype0
,
gridindex
;
int
dimID
=
UNDEFID
;
int
dimIDs
[
2
];
int
ngrids
;
int
ngrids
=
0
;
int
fileID
;
int
dimlen
,
dimlen0
;
size_t
len
;
...
...
@@ -1357,7 +1357,7 @@ void cdfDefXaxis(stream_t *streamptr, int gridID)
vlistID
=
streamptr
->
vlistID
;
fileID
=
streamptr
->
fileID
;
ngrids
=
vlistNgrids
(
vlistID
);
if
(
ndims
)
ngrids
=
vlistNgrids
(
vlistID
);
dimlen
=
gridInqXsize
(
gridID
);
gridindex
=
vlistGridIndex
(
vlistID
,
gridID
);
...
...
@@ -1402,22 +1402,25 @@ void cdfDefXaxis(stream_t *streamptr, int gridID)
{
int
status
;
status
=
checkGridName
(
'V'
,
axisname
,
fileID
,
vlistID
,
gridID
,
ngrids
,
'X'
);
if
(
status
==
0
)
if
(
status
==
0
&&
ndims
)
status
=
checkGridName
(
'D'
,
axisname
,
fileID
,
vlistID
,
gridID
,
ngrids
,
'X'
);
if
(
streamptr
->
ncmode
==
2
)
cdf_redef
(
fileID
);
cdf_def_dim
(
fileID
,
axisname
,
dimlen
,
&
dimID
);
if
(
gridInqXboundsPtr
(
gridID
)
||
gridInqYboundsPtr
(
gridID
)
)
if
(
ndims
)
{
if
(
nc_inq_dimid
(
fileID
,
"nb2"
,
&
nvdimID
)
!=
NC_NOERR
)
cdf_def_dim
(
fileID
,
"nb2"
,
nvertex
,
&
nvdimID
);
cdf_def_dim
(
fileID
,
axisname
,
dimlen
,
&
dimID
);
if
(
gridInqXboundsPtr
(
gridID
)
||
gridInqYboundsPtr
(
gridID
)
)
{
if
(
nc_inq_dimid
(
fileID
,
"nb2"
,
&
nvdimID
)
!=
NC_NOERR
)
cdf_def_dim
(
fileID
,
"nb2"
,
nvertex
,
&
nvdimID
);
}
}
if
(
gridInqXvalsPtr
(
gridID
)
)
{
cdf_def_var
(
fileID
,
axisname
,
(
nc_type
)
xtype
,
1
,
&
dimID
,
&
ncvarid
);
cdf_def_var
(
fileID
,
axisname
,
(
nc_type
)
xtype
,
ndims
,
&
dimID
,
&
ncvarid
);
if
(
(
len
=
strlen
(
stdname
))
)
cdf_put_att_text
(
fileID
,
ncvarid
,
"standard_name"
,
len
,
stdname
);
...
...
@@ -1450,6 +1453,8 @@ void cdfDefXaxis(stream_t *streamptr, int gridID)
if
(
ncvarid
!=
UNDEFID
)
cdf_put_var_double
(
fileID
,
ncvarid
,
gridInqXvalsPtr
(
gridID
));
if
(
ncbvarid
!=
UNDEFID
)
cdf_put_var_double
(
fileID
,
ncbvarid
,
gridInqXboundsPtr
(
gridID
));
if
(
ndims
==
0
)
streamptr
->
ncxvarID
[
gridindex
]
=
ncvarid
;
}
streamptr
->
xdimID
[
gridindex
]
=
dimID
;
...
...
@@ -1458,7 +1463,7 @@ void cdfDefXaxis(stream_t *streamptr, int gridID)
#if defined (HAVE_LIBNETCDF)
static
void
cdfDefYaxis
(
stream_t
*
streamptr
,
int
gridID
)
void
cdfDefYaxis
(
stream_t
*
streamptr
,
int
gridID
,
int
ndims
)
{
char
units
[
CDI_MAX_NAME
];
char
longname
[
CDI_MAX_NAME
];
...
...
@@ -1469,7 +1474,7 @@ void cdfDefYaxis(stream_t *streamptr, int gridID)
int
gridID0
,
gridtype0
,
gridindex
;
int
dimID
=
UNDEFID
;
int
dimIDs
[
2
];
int
ngrids
;
int
ngrids
=
0
;
int
fileID
;
int
dimlen
,
dimlen0
;
size_t
len
;
...
...
@@ -1483,7 +1488,7 @@ void cdfDefYaxis(stream_t *streamptr, int gridID)
vlistID
=
streamptr
->
vlistID
;
fileID
=
streamptr
->
fileID
;
ngrids
=
vlistNgrids
(
vlistID
);
if
(
ndims
)
ngrids
=
vlistNgrids
(
vlistID
);
dimlen
=
gridInqYsize
(
gridID
);
gridindex
=
vlistGridIndex
(
vlistID
,
gridID
);
...
...
@@ -1528,22 +1533,25 @@ void cdfDefYaxis(stream_t *streamptr, int gridID)
{
int
status
;
status
=
checkGridName
(
'V'
,
axisname
,
fileID
,
vlistID
,
gridID
,
ngrids
,
'Y'
);
if
(
status
==
0
)
if
(
status
==
0
&&
ndims
)
status
=
checkGridName
(
'D'
,
axisname
,
fileID
,
vlistID
,
gridID
,
ngrids
,
'Y'
);
if
(
streamptr
->
ncmode
==
2
)
cdf_redef
(
fileID
);
cdf_def_dim
(
fileID
,
axisname
,
dimlen
,
&
dimID
);
if
(
gridInqXboundsPtr
(
gridID
)
||
gridInqYboundsPtr
(
gridID
)
)
if
(
ndims
)
{
if
(
nc_inq_dimid
(
fileID
,
"nb2"
,
&
nvdimID
)
!=
NC_NOERR
)
cdf_def_dim
(
fileID
,
"nb2"
,
nvertex
,
&
nvdimID
);
cdf_def_dim
(
fileID
,
axisname
,
dimlen
,
&
dimID
);
if
(
gridInqXboundsPtr
(
gridID
)
||
gridInqYboundsPtr
(
gridID
)
)
{
if
(
nc_inq_dimid
(
fileID
,
"nb2"
,
&
nvdimID
)
!=
NC_NOERR
)
cdf_def_dim
(
fileID
,
"nb2"
,
nvertex
,
&
nvdimID
);
}
}
if
(
gridInqYvalsPtr
(
gridID
)
)
{
cdf_def_var
(
fileID
,
axisname
,
(
nc_type
)
xtype
,
1
,
&
dimID
,
&
ncvarid
);
cdf_def_var
(
fileID
,
axisname
,
(
nc_type
)
xtype
,
ndims
,
&
dimID
,
&
ncvarid
);
if
(
(
len
=
strlen
(
stdname
))
)
cdf_put_att_text
(
fileID
,
ncvarid
,
"standard_name"
,
len
,
stdname
);
...
...
@@ -1576,6 +1584,8 @@ void cdfDefYaxis(stream_t *streamptr, int gridID)
if
(
ncvarid
!=
UNDEFID
)
cdf_put_var_double
(
fileID
,
ncvarid
,
gridInqYvalsPtr
(
gridID
));
if
(
ncbvarid
!=
UNDEFID
)
cdf_put_var_double
(
fileID
,
ncbvarid
,
gridInqYboundsPtr
(
gridID
));
if
(
ndims
==
0
)
streamptr
->
ncyvarID
[
gridindex
]
=
ncvarid
;
}
streamptr
->
ydimID
[
gridindex
]
=
dimID
;
...
...
@@ -2682,13 +2692,13 @@ void cdfDefGrid(stream_t *streamptr, int gridID)
int
lx
=
0
,
ly
=
0
;
if
(
gridInqXsize
(
gridID
)
>
0
/*&& gridInqXvals(gridID, NULL) > 0*/
)
{
cdfDefXaxis
(
streamptr
,
gridID
);
cdfDefXaxis
(
streamptr
,
gridID
,
1
);
lx
=
1
;
}
if
(
gridInqYsize
(
gridID
)
>
0
/*&& gridInqYvals(gridID, NULL) > 0*/
)
{
cdfDefYaxis
(
streamptr
,
gridID
);
cdfDefYaxis
(
streamptr
,
gridID
,
1
);
ly
=
1
;
}
...
...
@@ -2697,8 +2707,12 @@ void cdfDefGrid(stream_t *streamptr, int gridID)
}
else
{
if
(
gridInqXsize
(
gridID
)
>
0
)
cdfDefXaxis
(
streamptr
,
gridID
);
if
(
gridInqYsize
(
gridID
)
>
0
)
cdfDefYaxis
(
streamptr
,
gridID
);
int
ndims
=
1
;
if
(
gridtype
==
GRID_LONLAT
&&
size
==
1
&&
gridInqHasDims
(
gridID
)
==
FALSE
)
ndims
=
0
;
if
(
gridInqXsize
(
gridID
)
>
0
)
cdfDefXaxis
(
streamptr
,
gridID
,
ndims
);
if
(
gridInqYsize
(
gridID
)
>
0
)
cdfDefYaxis
(
streamptr
,
gridID
,
ndims
);
}
if
(
gridIsRotated
(
gridID
)
)
cdfDefPole
(
streamptr
,
gridID
);
...
...
@@ -2732,8 +2746,8 @@ void cdfDefGrid(stream_t *streamptr, int gridID)
}
else
if
(
gridtype
==
GRID_SINUSOIDAL
||
gridtype
==
GRID_LAEA
||
gridtype
==
GRID_LCC2
)
{
cdfDefXaxis
(
streamptr
,
gridID
);
cdfDefYaxis
(
streamptr
,
gridID
);
cdfDefXaxis
(
streamptr
,
gridID
,
1
);
cdfDefYaxis
(
streamptr
,
gridID
,
1
);
cdfDefMapping
(
streamptr
,
gridID
);
}
...
...
@@ -3058,8 +3072,7 @@ int cdfDefVar(stream_t *streamptr, int varID)
cdf_put_att_int
(
fileID
,
ncvarid
,
"table"
,
NC_INT
,
1
,
&
tablenum
);
}
if
(
gridtype
!=
GRID_GENERIC
&&
gridtype
!=
GRID_LONLAT
&&
gridtype
!=
GRID_CURVILINEAR
)
if
(
gridtype
!=
GRID_GENERIC
&&
gridtype
!=
GRID_LONLAT
&&
gridtype
!=
GRID_CURVILINEAR
)
{
len
=
strlen
(
gridNamePtr
(
gridtype
));
if
(
len
>
0
)
...
...
@@ -3091,6 +3104,28 @@ int cdfDefVar(stream_t *streamptr, int varID)
{
cdf_put_att_text
(
fileID
,
ncvarid
,
"coordinates"
,
9
,
"tlon tlat"
);
}
else
if
(
gridtype
==
GRID_LONLAT
&&
xid
==
UNDEFID
&&
yid
==
UNDEFID
&&
gridsize
==
1
)
{
char
coordinates
[
CDI_MAX_NAME
]
=
""
;
int
ncxvarID
,
ncyvarID
;
int
gridindex
;
size_t
len
;
gridindex
=
vlistGridIndex
(
vlistID
,
gridID
);
ncxvarID
=
streamptr
->
ncxvarID
[
gridindex
];
ncyvarID
=
streamptr
->
ncyvarID
[
gridindex
];
if
(
ncxvarID
!=
CDI_UNDEFID
)
cdf_inq_varname
(
fileID
,
ncxvarID
,
coordinates
);
len
=
strlen
(
coordinates
);
if
(
ncyvarID
!=
CDI_UNDEFID
)
{
if
(
len
)
coordinates
[
len
++
]
=
' '
;
cdf_inq_varname
(
fileID
,
ncyvarID
,
coordinates
+
len
);
}
len
=
strlen
(
coordinates
);
if
(
len
)
cdf_put_att_text
(
fileID
,
ncvarid
,
"coordinates"
,
len
,
coordinates
);
}
else
if
(
gridtype
==
GRID_UNSTRUCTURED
||
gridtype
==
GRID_CURVILINEAR
)
{
char
coordinates
[
CDI_MAX_NAME
]
=
""
;
...
...
@@ -6260,6 +6295,8 @@ void define_all_grids(stream_t *streamptr, int vlistID, ncdim_t *ncdims, int nva
gridindex
=
vlistGridIndex
(
vlistID
,
ncvars
[
ncvarid
].
gridID
);
streamptr
->
xdimID
[
gridindex
]
=
xdimid
;
streamptr
->
ydimID
[
gridindex
]
=
ydimid
;
if
(
xdimid
==
-
1
&&
ydimid
==
-
1
&&
grid
.
size
==
1
)
gridDefHasDims
(
ncvars
[
ncvarid
].
gridID
,
FALSE
);
if
(
CDI_Debug
)
Message
(
"gridID %d %d %s"
,
ncvars
[
ncvarid
].
gridID
,
ncvarid
,
ncvars
[
ncvarid
].
name
);
...
...
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment