Skip to content

Commit 57e5f02

Browse files
mahmud1sourcery-ai[bot]yunjunz
authored
add support for InSAR Explorer (#1330)
+ add `save_explorer` to support exporting to GRD format, compatible with QGIS InSAR Explorer plugin. + `docs/QGIS.md`: update documentation for InSAR Explorer --------- Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Co-authored-by: Zhang Yunjun <yunjunz@outlook.com>
1 parent 3b0005e commit 57e5f02

File tree

6 files changed

+253
-7
lines changed

6 files changed

+253
-7
lines changed

docs/QGIS.md

Lines changed: 78 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,82 @@
1-
Displacement time-series can be exported as QGIS PS Time Series Viewer plugin compatible *.shp file via `save_qgis.py`.
1+
The displacement time-series result can be exported as a QGIS-compatible format for visualization via the **InSAR Explorer** plugin or the **PS Time Series Viewer** plugin.
2+
3+
### 1. QGIS with [InSAR Explorer](https://insar-explorer.readthedocs.io/) ###
4+
5+
The InSAR Explorer plugin supports both the GRD format and the shapefile format.
6+
7+
#### a. Setup ####
8+
9+
1. Download and install [QGIS](https://qgis.org/en/site/) if you have not done so.
10+
2. Install the plugin:
11+
- Install within QGIS via "Plugins -> Manage and Install Plugins", then search for "InSAR Explorer".
12+
- Alternatively, download the plugin as a *.zip file from [the plugin page](https://plugins.qgis.org/plugins/insar_explorer-dev/), and install it through "Plugins -> Manage and Install Plugins -> Install from ZIP".
13+
3. Launch the plugin: Access it from the toolbar or through "Plugins -> InSAR Explorer -> InSAR Explorer".
14+
15+
#### b. Usage for GRD files ####
16+
17+
1. Export MintPy results to GRD files using `save_explorer.py`:
18+
19+
```bash
20+
save_explorer.py geo_timeseries.h5 -v geo_velocity.h5 -o geo_maskTempCoh.h5 -o timeseries/
21+
```
22+
23+
2. Load data in QGIS: Open one of the exported GRD files, e.g., `geo_velocity_mm.h5`, in QGIS.
24+
3. Launch InSAR Explorer and click on any point to plot the time series.
25+
26+
<p align="left">
27+
<img width="1000" src="https://insarlab.github.io/figs/docs/mintpy/QGIS-InSAR-Explorer-point.png">
28+
</p>
29+
30+
#### c. Using shapefile ###
31+
32+
1. Export MintPy results to a shapefile using `save_qgis.py`:
33+
34+
```bash
35+
save_qgis.py timeseries_ERA5_ramp_demErr.h5 -g inputs/geometryRadar.h5
36+
save_qgis.py geo/geo_timeseries_ERA5_ramp_demErr.h5 -g geo/geo_geometryRadar.h5
37+
```
38+
39+
2. Load data in QGIS: Browse to the *.shp file generated by `save_qgis.py` and add it to the layer. Then open "View -> Panels -> Layer Styling", and on the right choose "Fill color -> Edit" and add the command below to color-code the map based on velocity value.
40+
41+
```
42+
ramp_color('RdBu', scale_linear(VEL, -20, 20, 0, 1))
43+
```
44+
45+
<p align="left">
46+
<img width="1000" src="https://insarlab.github.io/figs/docs/mintpy/QGIS-PS-TSV-map.png">
47+
</p>
48+
49+
3. Launch InSAR Explorer and click on any point to plot the time series.
50+
51+
<p align="left">
52+
<img width="1000" src="https://insarlab.github.io/figs/docs/mintpy/QGIS-InSAR-Explorer-point.png">
53+
</p>
54+
55+
### d. More information ###
56+
57+
For more details on using the plugin, please refer to the [InSAR Explorer documentation](https://insar-explorer.readthedocs.io/).
58+
59+
### 2. QGIS with [PS Time Series Viewer](https://plugins.qgis.org/plugins/pstimeseries/)
60+
61+
The PS Time Series Viewer plugin supports the shapefile format only.
62+
63+
#### a. Setup
264

365
1. Download and install [QGIS](https://qgis.org/en/site/) if you have not done so.
4-
2. Download [ps-time-series](https://plugins.qgis.org/plugins/pstimeseries/) plugin as a *.zip file; and install it to QGIS through "Plugins -> Manage and install Plugins -> install from ZIP".
5-
3. On the left side of QGIS window, browse to the *.shp file generated by `save_qgis.py` and add to the layer.
6-
4. Open "View -> Panels -> Layer Styling", then on the right choose "Fill color -> Edit" and add the command below to color code the map based on velocity value.
66+
2. Install the plugin:
67+
- Install within QGIS via "Plugins -> Manage and Install Plugins", then search "PS Time Series Viewer".
68+
- Alternatively, download the plugin as a *.zip file from [the plugin page](https://plugins.qgis.org/plugins/pstimeseries/), and install it through “Plugins -> Manage and Install Plugins -> Install from ZIP”.
69+
70+
#### b. Usage
71+
72+
1. Export MintPy results to shapefiles using `save_qgis.py`:
73+
74+
```bash
75+
save_qgis.py timeseries_ERA5_ramp_demErr.h5 -g inputs/geometryRadar.h5
76+
save_qgis.py geo/geo_timeseries_ERA5_ramp_demErr.h5 -g geo/geo_geometryRadar.h5
77+
```
78+
79+
2. Load data in QGIS: Browse to the *.shp file generated by `save_qgis.py` and add it to the layer. Then open "View -> Panels -> Layer Styling", and on the right choose "Fill color -> Edit" and add the command below to color-code the map based on velocity value.
780

881
```
982
ramp_color('RdBu', scale_linear(VEL, -20, 20, 0, 1))
@@ -13,7 +86,7 @@ ramp_color('RdBu', scale_linear(VEL, -20, 20, 0, 1))
1386
<img width="1000" src="https://insarlab.github.io/figs/docs/mintpy/QGIS-PS-TSV-map.png">
1487
</p>
1588

16-
5. Click the [PS Time Series Viewer logo](https://gitlab.com/faunalia/ps-speed/blob/master/icons/logo.png) to activate the tool, and click/play on the map to display the time-series!
89+
3. Launch PS Time Series Viewer: Access it from the toolbar or through “Plugins -> Permanent Scatterers -> PS Time Series Viewer”.
1790

1891
<p align="left">
1992
<img width="800" src="https://insarlab.github.io/figs/docs/mintpy/QGIS-PS-TSV-point.png">

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ Issues = "https://github.com/insarlab/MintPy/issues"
8585
"save_kmz_timeseries.py" = "mintpy.cli.save_kmz_timeseries:main"
8686
"save_qgis.py" = "mintpy.cli.save_qgis:main"
8787
"save_roipac.py" = "mintpy.cli.save_roipac:main"
88+
"save_explorer.py" = "mintpy.cli.save_explorer:main"
8889
"smallbaselineApp.py" = "mintpy.cli.smallbaselineApp:main"
8990
"solid_earth_tides.py" = "mintpy.cli.solid_earth_tides:main"
9091
"spatial_average.py" = "mintpy.cli.spatial_average:main"

src/mintpy/__main__.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,13 @@ def get_save_roipac_parser(subparsers=None):
413413
return parser
414414

415415

416+
def get_save_explorer_parser(subparsers=None):
417+
from mintpy.cli import save_explorer
418+
parser = save_explorer.create_parser(subparsers)
419+
parser.set_defaults(func=save_explorer.main)
420+
return parser
421+
422+
416423
def get_smallbaselineApp_parser(subparsers=None):
417424
from mintpy.cli import smallbaselineApp
418425
parser = smallbaselineApp.create_parser(subparsers)
@@ -660,6 +667,7 @@ def get_parser():
660667
get_save_kmz_parser(sp)
661668
get_save_qgis_parser(sp)
662669
get_save_roipac_parser(sp)
670+
get_save_explorer_parser(sp)
663671

664672
# visualization
665673
get_info_parser(sp)

src/mintpy/cli/save_explorer.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
#!/usr/bin/env python3
2+
############################################################
3+
# Program is part of MintPy #
4+
# Copyright (c) 2013, Zhang Yunjun, Heresh Fattahi #
5+
# Author: Mahmud Haghighi, Mar 2025 #
6+
############################################################
7+
8+
9+
import sys
10+
11+
from mintpy.utils import readfile
12+
from mintpy.utils.arg_utils import create_argument_parser
13+
14+
####################################################################################
15+
EXAMPLE = """example:
16+
save_explorer.py geo_timeseries_demErr.h5
17+
save_explorer.py geo_timeseries_demErr.h5 -v geo_velocity.h5 -m geo_maskTempCoh.h5
18+
save_explorer.py timeseries_demErr.h5 -v velocity.h5 -m maskTempCoh.h5 -o timeseries
19+
# check more info on InSAR Explorer documentation: https://luhipi.github.io/insar-explorer
20+
"""
21+
22+
23+
def create_parser(subparsers=None):
24+
synopsis = 'Convert time series to GRD files for QGIS InSAR Explorer plugin.'
25+
epilog = EXAMPLE
26+
name = __name__.split('.')[-1]
27+
parser = create_argument_parser(
28+
name, synopsis=synopsis, description=synopsis, epilog=epilog, subparsers=subparsers)
29+
30+
parser.add_argument('ts_file',
31+
help='Time series file to be converted, in geo coordinate.')
32+
parser.add_argument('-v', '--vel', dest='vel_file',
33+
help='velocity file to be converted, in geo coordinate.')
34+
parser.add_argument('-m', '--mask', dest='msk_file',
35+
help='mask file, in geo coordinates.')
36+
parser.add_argument('-o', '--output', dest='outdir', default='InSAR-Explorer',
37+
help='Output directory for GRD files (default: %(default)s).')
38+
return parser
39+
40+
41+
def cmd_line_parse(iargs=None):
42+
'''Command line parser.'''
43+
# parse
44+
parser = create_parser()
45+
inps = parser.parse_args(args=iargs)
46+
47+
# check: input time series file is geocoded time series
48+
atr = readfile.read_attribute(inps.ts_file)
49+
ftype = atr['FILE_TYPE']
50+
if 'Y_FIRST' not in atr.keys():
51+
raise Exception('Input file is NOT geocoded! Only geocoded files are supported.')
52+
if ftype not in ['timeseries']:
53+
raise Exception(f'Input file ({ftype}) is NOT time series!')
54+
55+
return inps
56+
57+
58+
####################################################################################
59+
def main(iargs=None):
60+
# parse
61+
inps = cmd_line_parse(iargs)
62+
63+
# import
64+
from mintpy.save_explorer import save_explorer
65+
66+
# run
67+
save_explorer(inps)
68+
69+
70+
####################################################################################
71+
if __name__ == '__main__':
72+
main(sys.argv[1:])

src/mintpy/save_explorer.py

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
############################################################
2+
# Program is part of MintPy #
3+
# Copyright (c) 2013, Zhang Yunjun, Heresh Fattahi #
4+
# Author: Mahmud Haghighi, Mar 2025 #
5+
############################################################
6+
7+
8+
import os
9+
10+
import numpy as np
11+
12+
from mintpy.objects import timeseries
13+
from mintpy.save_gmt import write_grd_file
14+
from mintpy.utils import ptime, readfile
15+
16+
17+
####################################################################################
18+
def convert2mm(data, atr):
19+
"""Convert displacement (velocity) data to mm or mm/year."""
20+
if atr['UNIT'] in ['m', 'm/year']:
21+
return data * 1000
22+
elif atr['UNIT'] in ['cm', 'cm/year']:
23+
return data * 10
24+
elif atr['UNIT'] in ['mm', 'mm/year']:
25+
return data
26+
else:
27+
raise ValueError(f"ERROR: unit {atr['UNIT']} is not supported!")
28+
29+
30+
def save_explorer(inps):
31+
32+
# create output directory
33+
inps.outdir = os.path.abspath(inps.outdir)
34+
print(f'output directory: {inps.outdir}')
35+
if not os.path.isdir(inps.outdir):
36+
print('output directory does not exist, creating directory: '+inps.outdir)
37+
os.makedirs(inps.outdir)
38+
39+
# grab aux files'name based on input ts_file file name
40+
inps.ts_file = os.path.abspath(inps.ts_file)
41+
ts_dir = os.path.dirname(inps.ts_file)
42+
prefix = 'geo_' if os.path.basename(inps.ts_file).startswith('geo_') else ''
43+
inps.vel_file = inps.vel_file or os.path.join(ts_dir, f'{prefix}velocity.h5')
44+
inps.msk_file = inps.msk_file or os.path.join(ts_dir, f'{prefix}maskTempCoh.h5')
45+
46+
# check aux file existence
47+
inps.vel_file = os.path.abspath(inps.vel_file) if os.path.isfile(inps.vel_file) else None
48+
inps.msk_file = os.path.abspath(inps.msk_file) if os.path.isfile(inps.msk_file) else None
49+
print(f'time series file: {inps.ts_file}')
50+
print(f'velocity file: {inps.vel_file}')
51+
print(f'mask file: {inps.msk_file}')
52+
53+
# read mask file
54+
if inps.msk_file and os.path.isfile(inps.msk_file):
55+
print(f'read mask data from file: {inps.msk_file}')
56+
mask = readfile.read(inps.msk_file)[0]
57+
else:
58+
mask = None
59+
60+
# export velocity file
61+
if inps.vel_file and os.path.isfile(inps.vel_file):
62+
data, atr = readfile.read(inps.vel_file)
63+
data = convert2mm(data, atr) # convert to mm
64+
if mask is not None:
65+
data[~mask] = np.nan
66+
67+
out_base = os.path.splitext(os.path.basename(inps.vel_file))[0]
68+
out_file = os.path.join(inps.outdir, f'{out_base}_mm.grd')
69+
write_grd_file(data, atr, out_file, print_msg=True)
70+
71+
# export time series to a list of grd files
72+
print('writing time series to a list of timeseries-{YYYYMMDD}_mm.grd files ...')
73+
date_list = timeseries(inps.ts_file).get_date_list()
74+
num_date = len(date_list)
75+
prog_bar = ptime.progressBar(maxValue=num_date)
76+
for i, date_str in enumerate(date_list):
77+
prog_bar.update(i+1, suffix=f'{i+1}/{num_date} {date_str}')
78+
79+
# read
80+
data, atr = readfile.read(inps.ts_file, datasetName=date_str)
81+
data = convert2mm(data, atr) # convert to mm
82+
if mask is not None:
83+
data[mask==0] = np.nan
84+
85+
# write
86+
out_file = os.path.join(inps.outdir, f'timeseries-{date_str}_mm.grd')
87+
write_grd_file(data, atr, out_file, print_msg=False)
88+
prog_bar.close()
89+
90+
print('Done.')
91+
return

src/mintpy/save_gmt.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ def get_geo_lat_lon(atr):
101101
return Y, X
102102

103103

104-
def write_grd_file(data, atr, out_file=None):
104+
def write_grd_file(data, atr, out_file=None, print_msg=True):
105105
"""Write GMT .grd file for input data matrix, using giant._gmt module.
106106
Parameters: data - 2D np.ndarray in int/float, data matrix to write
107107
atr - dict, attributes of input data matrix
@@ -112,7 +112,8 @@ def write_grd_file(data, atr, out_file=None):
112112
lats, lons = get_geo_lat_lon(atr)
113113

114114
# writing
115-
print('writing >>> '+out_file)
115+
if print_msg:
116+
print('writing >>> '+out_file)
116117
write_gmt_simple(
117118
lons=lons,
118119
lats=np.flipud(lats),

0 commit comments

Comments
 (0)